//
// Copyright 2016 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
//    names, trademarks, service marks, or product names of the Licensor
//    and its affiliates, except as required to comply with Section 4(c) of
//    the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "pxr/imaging/glf/glew.h"

#include "pxr/imaging/hd/unitTestGLDrawing.h"
#include "pxr/imaging/glf/diagnostic.h"
#include "pxr/imaging/glf/drawTarget.h"
#include "pxr/imaging/garch/glDebugWindow.h"

#include "pxr/base/gf/frustum.h"
#include "pxr/base/gf/matrix4d.h"
#include "pxr/base/gf/rotation.h"
#include "pxr/base/gf/vec2i.h"
#include "pxr/base/gf/vec4d.h"

#include <cstdlib>

PXR_NAMESPACE_OPEN_SCOPE


class Hd_UnitTestWindow : public GarchGLDebugWindow {
public:
    typedef Hd_UnitTestWindow This;

public:
    Hd_UnitTestWindow(Hd_UnitTestGLDrawing * unitTest,
                                int width, int height);
    virtual ~Hd_UnitTestWindow();

    void OffscreenTest();

    bool WriteToFile(std::string const & attachment, 
                     std::string const & filename);

    void StartTimer();

    // GarchGLDebugWindow overrides
    virtual void OnInitializeGL();
    virtual void OnUninitializeGL();
    virtual void OnIdle();
    virtual void OnPaintGL();
    virtual void OnKeyRelease(int key);
    virtual void OnMousePress(int button, int x, int y, int modKeys);
    virtual void OnMouseRelease(int button, int x, int y, int modKeys);
    virtual void OnMouseMove(int x, int y, int modKeys);

private:
    Hd_UnitTestGLDrawing *_unitTest;
    GlfDrawTargetRefPtr _drawTarget;
    bool _animate;
};

Hd_UnitTestWindow::Hd_UnitTestWindow(
    Hd_UnitTestGLDrawing * unitTest, int w, int h)
    : GarchGLDebugWindow("Hd Test", w, h)
    , _unitTest(unitTest)
    , _animate(false)
{
}

Hd_UnitTestWindow::~Hd_UnitTestWindow()
{
}

/* virtual */
void
Hd_UnitTestWindow::OnInitializeGL()
{
    GlfGlewInit();
    GlfRegisterDefaultDebugOutputMessageCallback();

    std::cout << glGetString(GL_VENDOR) << "\n";
    std::cout << glGetString(GL_RENDERER) << "\n";
    std::cout << glGetString(GL_VERSION) << "\n";

    //
    // Create an offscreen draw target which is the same size as this
    // widget and initialize the unit test with the draw target bound.
    //
    _drawTarget = GlfDrawTarget::New(GfVec2i(GetWidth(), GetHeight()));
    _drawTarget->Bind();
    _drawTarget->AddAttachment("color", GL_RGBA, GL_FLOAT, GL_RGBA);
    _drawTarget->AddAttachment("depth", GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8,
                               GL_DEPTH24_STENCIL8);
    _unitTest->InitTest();

    _drawTarget->Unbind();
}

/* virtual */
void
Hd_UnitTestWindow::OnUninitializeGL()
{
    _drawTarget = GlfDrawTargetRefPtr();
}

/* virtual */
void
Hd_UnitTestWindow::OnPaintGL()
{
    //
    // Update the draw target's size and execute the unit test with
    // the draw target bound.
    //
    _drawTarget->Bind();
    _drawTarget->SetSize(GfVec2i(GetWidth(), GetHeight()));

    _unitTest->DrawTest();

    _drawTarget->Unbind();

    //
    // Blit the resulting color buffer to the window (this is a noop
    // if we're drawing offscreen).
    //
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, _drawTarget->GetFramebufferId());

    glBlitFramebuffer(0, 0, GetWidth(), GetHeight(),
                      0, 0, GetWidth(), GetHeight(),
                      GL_COLOR_BUFFER_BIT,
                      GL_NEAREST);

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}

void
Hd_UnitTestWindow::OffscreenTest()
{
    _drawTarget->Bind();
    _drawTarget->SetSize(GfVec2i(GetWidth(), GetHeight()));

    _unitTest->OffscreenTest();

    _drawTarget->Unbind();
}

void
Hd_UnitTestWindow::StartTimer()
{
    _animate = true;
}

/* virtual */
void
Hd_UnitTestWindow::OnIdle()
{
    if (_animate) {
        _unitTest->Idle();
    }
}

bool
Hd_UnitTestWindow::WriteToFile(std::string const & attachment,
        std::string const & filename)
{
    _drawTarget->Unbind();
    bool ret = _drawTarget->WriteToFile(attachment, filename);
    _drawTarget->Bind();
    return ret;
}

/* virtual */
void
Hd_UnitTestWindow::OnKeyRelease(int key)
{
    switch (key) {
    case 'q':
        ExitApp();
        return;
    }
    _unitTest->KeyRelease(key);
}

/* virtual */
void
Hd_UnitTestWindow::OnMousePress(int button, int x, int y, int modKeys)
{
    _unitTest->MousePress(button, x, y);
}

/* virtual */
void
Hd_UnitTestWindow::OnMouseRelease(int button, int x, int y, int modKeys)
{
    _unitTest->MouseRelease(button, x, y);
}

/* virtual */
void
Hd_UnitTestWindow::OnMouseMove(int x, int y, int modKeys)
{
    _unitTest->MouseMove(x, y);
}

////////////////////////////////////////////////////////////

Hd_UnitTestGLDrawing::Hd_UnitTestGLDrawing()
    : _widget(NULL)
{
    _rotate[0] = _rotate[1] = 0;
    _translate[0] = _translate[1] = _translate[2] = 0;

    _mousePos[0] = _mousePos[1] = 0;
    _mouseButton[0] = _mouseButton[1] = _mouseButton[2] = false;
}

Hd_UnitTestGLDrawing::~Hd_UnitTestGLDrawing()
{
}

int
Hd_UnitTestGLDrawing::GetWidth() const
{
    return _widget->GetWidth();
}

int
Hd_UnitTestGLDrawing::GetHeight() const
{
    return _widget->GetHeight();
}

bool
Hd_UnitTestGLDrawing::WriteToFile(std::string const & attachment,
        std::string const & filename) const
{
    return _widget->WriteToFile(attachment, filename);
}

void
Hd_UnitTestGLDrawing::RunTest(int argc, char *argv[])
{
    bool offscreen = false;
    bool animate = false;
    for (int i=0; i<argc; ++i) {
        if (std::string(argv[i]) == "--offscreen") {
            offscreen = true;
        } else
        if (std::string(argv[i]) == "--animate") {
            animate = true;
        }
    }

    this->ParseArgs(argc, argv);

    _widget = new Hd_UnitTestWindow(this, 640, 480);
    _widget->Init();

    if (offscreen) {
        // no GUI mode (automated test)
        _widget->OffscreenTest();
    } else {
        // Interactive mode
        if (animate) _widget->StartTimer();
        _widget->Run();
    }
}

/* virtual */
void
Hd_UnitTestGLDrawing::Idle()
{
}

/* virtual */
void
Hd_UnitTestGLDrawing::ParseArgs(int argc, char *argv[])
{
}

/* virtual */
void
Hd_UnitTestGLDrawing::MousePress(int button, int x, int y)
{
    _mouseButton[button] = true;
    _mousePos[0] = x;
    _mousePos[1] = y;
}

/* virtual */
void
Hd_UnitTestGLDrawing::MouseRelease(int button, int x, int y)
{
    _mouseButton[button] = false;
}

/* virtual */
void
Hd_UnitTestGLDrawing::MouseMove(int x, int y)
{
    int dx = x - _mousePos[0];
    int dy = y - _mousePos[1];

    if (_mouseButton[0]) {
        _rotate[1] += dx;
        _rotate[0] += dy;
    } else if (_mouseButton[1]) {
        _translate[0] += 0.1*dx;
        _translate[1] -= 0.1*dy;
    } else if (_mouseButton[2]) {
        _translate[2] += 0.1*dx;
    }

    _mousePos[0] = x;
    _mousePos[1] = y;
}

/* virtual */
void
Hd_UnitTestGLDrawing::KeyRelease(int key)
{
}

GfMatrix4d
Hd_UnitTestGLDrawing::GetViewMatrix() const
{
    GfMatrix4d viewMatrix;
    viewMatrix.SetIdentity();
    // rotate from z-up to y-up
    viewMatrix *= GfMatrix4d().SetRotate(GfRotation(GfVec3d(1.0,0.0,0.0), -90.0));
    viewMatrix *= GfMatrix4d().SetRotate(GfRotation(GfVec3d(0, 1, 0), _rotate[1]));
    viewMatrix *= GfMatrix4d().SetRotate(GfRotation(GfVec3d(1, 0, 0), _rotate[0]));
    viewMatrix *= GfMatrix4d().SetTranslate(GfVec3d(_translate[0], _translate[1], _translate[2]));

    return viewMatrix;
}

GfMatrix4d
Hd_UnitTestGLDrawing::GetProjectionMatrix() const
{
    return GetFrustum().ComputeProjectionMatrix();
}

GfFrustum
Hd_UnitTestGLDrawing::GetFrustum() const
{
    int width = GetWidth();
    int height = GetHeight();
    double aspectRatio = double(width)/height;

    GfFrustum frustum;
    frustum.SetPerspective(45.0, aspectRatio, 1, 100000.0);
    return frustum;
}

PXR_NAMESPACE_CLOSE_SCOPE

