/**
 @file HUDUI_Window.cpp

 Dojo Game Engine

 Copyright 2005, Morgan McGuire
 All rights reserved.
 */

#include "dojo/DApp.h"
#include "dojo/HUDUI.h"
#include "dojo/G3UI.h"

namespace dojo {

/** Shading algorithm used for bevels in drawTimeBreakdown */
static Color3 shade(float angle, const Color3& base) {
    // Shift lighting to a 45-degree angle
    angle += G3D_PI / 4;

    float y = sin(angle) * 0.7;

    if (y < 0) {
        return base.lerp(Color3::white(), abs(y));
    } else {
        return base.lerp(Color3::black(), y);
    }
}

void HUDUI::Window::draw(bool inFocus) {
    G3UI::drawWindow(hudUI->app->renderDevice, hudUI->smallFont, title, rect, inFocus);
    G3UI::drawCloseButton(hudUI->app->renderDevice, rect, false);
}


HUDUI::PhysicsDebugger::PhysicsDebugger(HUDUI* h) : Window(h) {
    title = "Simulation"; 
    rect = Rect2D::xywh(400, 500, 300, 100);
    visible = true;
    resizable = false;
}


HUDUI::GraphicsDebugger::GraphicsDebugger(HUDUI* h) : Window(h) {
    title = "Graphics"; 
    rect = Rect2D::xywh(0, 500, 400, 100);
    visible = true;
    resizable = false;
}


HUDUI::Window* HUDUI::Window::fromType(Type t, HUDUI* h) {
    switch (t) {
    case WINDOW_PHYSICS:
        return new PhysicsDebugger(h);

    case WINDOW_GRAPHICS:
        return new GraphicsDebugger(h);

    default:
        alwaysAssertM(false, "Fell through switch");
        return NULL;
    }
}

void HUDUI::GraphicsDebugger::draw(bool inFocus) {
    Window::draw(inFocus);
    Rect2D clientRect = G3UI::windowClientArea(rect);

    RenderDevice* rd = hudUI->app->renderDevice;

    // Rendering calls
    int majGL  = rd->debugNumMajorOpenGLStateChanges();
    int majAll = rd->debugNumMajorStateChanges();
    int minGL  = rd->debugNumMinorOpenGLStateChanges();
    int minAll = rd->debugNumMinorStateChanges();
    int push   = rd->debugNumPushStateCalls();

    Vector2 pos = clientRect.x0y0() + Vector2(4, 4);

    const std::string system = rd->getCardDescription();
    pos.y += hudUI->smallFont->draw2D(
        rd, system, pos, 10, Color3::black()).y;

    pos.y += hudUI->smallFont->draw2D(
        rd, format("Minor: %d/%d;  Major: %d/%d;  Push: %d", minGL, minAll, majGL, majAll, push),
        pos, 10, Color3::black()).y;

    std::string s = format(
            "% 4.1gM tris % 4.1gM tris/s", 
            iRound(rd->getTrianglesPerFrame() / 1e5) * .1,
            iRound(rd->getTrianglesPerFrame() / 1e5) * .1);

    pos.y += hudUI->smallFont->draw2D(rd, s, pos, 10, Color3::black()).y;

    s = format(
            "% 4.1gMB Texture, % 4.1gMB VAR", 
            iRound(Texture::sizeOfAllTexturesInMemory() / 1e5) * .1,
            iRound(VARArea::sizeOfAllVARAreasInMemory() / 1e5) * .1);

    pos.y += hudUI->smallFont->draw2D(rd, s, pos, 10, Color3::black()).y;

    s = format("%d opaque, %d transparent", World::world()->debugNumOpaqueRendered, World::world()->debugNumTransparentRendered);
    pos.y += hudUI->smallFont->draw2D(rd, s, pos, 10, Color3::black()).y;

    /*
    app->debugPrintf("Tone Map %s\n", toneMap.enabled() ? "On" : "Off");
    app->debugPrintf("%s Profile %s\n", toString(ArticulatedModel::profile()),
        #ifdef _DEBUG
                "(DEBUG mode)"
        #else
                ""
        #endif
        );
        */
}


void HUDUI::PhysicsDebugger::draw(bool inFocus) {
    Window::draw(inFocus);

    RenderDevice* rd = hudUI->app->renderDevice;

    Rect2D clientRect = G3UI::windowClientArea(rect);

    float buttonSize = 24;
    Rect2D playRect  = Rect2D::xywh(clientRect.x0() + 6, clientRect.y0() + 6, buttonSize, buttonSize);
    Rect2D pauseRect = playRect + Vector2(buttonSize + 6, 0);
    Rect2D stepRect  = pauseRect + Vector2(buttonSize + 6, 0);

    Rect2D sliderRect= Rect2D::xywh(playRect.x0y0() + Vector2(0, buttonSize + 10), Vector2(100,20));

    bool paused = hudUI->app->debugPaused();
    G3UI::drawButton(rd, playRect, hudUI->playIcon, ! paused);
    G3UI::drawButton(rd, pauseRect, hudUI->pauseIcon, paused);
    // TODO: the button activation should be independent of this
    G3UI::drawButton(rd, stepRect, hudUI->stepIcon, hudUI->app->debugSingleStep());

    // Time slider
    float timeRate = 1;
    const float maxTimeRate = 3;

    hudUI->smallFont->draw2D(
        rd, 
        format("%3.1fx", timeRate), 
        Vector2(sliderRect.x1() + 2, sliderRect.center().y), 
        10, 
        Color3::black(),
        Color4::clear(),
        GFont::XALIGN_LEFT,
        GFont::YALIGN_CENTER);

    G3UI::drawSlider(
        rd, 
        sliderRect, 
        timeRate / maxTimeRate, 
        false);
}



void HUDUI::drawTimeBreakdown(const Vector2& pos, float radius) const {
    const Color3 outlineColor = Color3::black();

    RenderDevice* rd = app->renderDevice;    

    rd->pushState();

    Vector2 mouse = app->userInput->mouseXY();
    float fps = app->graphicsWatch().smoothFPS();
    if ((app->currentApplet != NULL) &&
        (abs(fps - app->currentApplet->desiredFrameRate()) <= 1)) {
        // Round the FPS off to the desired one;
        // it is probably only a roundoff error and
        // will confuse users.
        fps = app->currentApplet->desiredFrameRate();
    }

    static const int N = 6;

    // Relative time for each component.
    static const char* name[N] = {"Gfx", "AI", "Net", "Sim", "UI", "wait"};
    static const Color3 color[N] =
        {Color3::fromARGB(0xFFFDCC05), 
         Color3::fromARGB(0xFFF7921E), 
         Color3::fromARGB(0xFFEF1C25),

         Color3::fromARGB(0xFFB554A3), 
         Color3::fromARGB(0xFF0072BB),
         Color3::black()};

    float pct[N];

    pct[0] = app->graphicsWatch().smoothElapsedTime();
    pct[1] = app->logicWatch().smoothElapsedTime();
    pct[2] = app->networkWatch().smoothElapsedTime();
    pct[3] = app->simulationWatch().smoothElapsedTime();
    pct[4] = app->userInputWatch().smoothElapsedTime();
    pct[5] = app->waitWatch().smoothElapsedTime();

    // Compute the wait time
    {
        float sum = 0.0f;
        for (int i = 0; i < N; ++i) {
            sum += pct[i];
        }

        // Convert times to percentages
        for (int i = 0; i < N; ++i) {
            pct[i] /= sum;
        }
    }


    // Now draw the pie wedges
    static const int totalSlices = 36;

    // Distance to rotate the zero position
    float shift = -G3D_PI / 2;

    // Index of the first label to display on the left side of the pie
    int split = N;

    float innerRadius = radius * 0.85;
    float outerRadius = radius;

    // Draw wedges
    rd->beginPrimitive(RenderDevice::TRIANGLES);
    {
        float sum = 0;
        for (int i = 0; i < N; ++i) {
            if ((sum + pct[i] / 2 > 0.5) && (split > i)) {
                // This is the first label that needs to be shown on the left.
                split = i;
            }

            const float a0 = sum * G3D_PI * 2.0 + shift;
            const float a1 = (sum + pct[i]) * G3D_PI * 2.0 + shift;
            sum += pct[i];

            // Number of subdivisions we need to make this slice look
            // like a smooth curve.
            const int M = iMax(1, (int)(pct[i] * totalSlices));
            for (int j = 0; j < M; ++j) {
                float f0 = G3D::lerp(a0, a1, j /(float)M);
                float f1 = G3D::lerp(a0, a1, (j + 1) /(float)M);
                rd->setColor(color[i]);
                rd->sendVertex(pos);
                rd->sendVertex(Vector2(cos(f0), sin(f0)) * innerRadius + pos);
                rd->sendVertex(Vector2(cos(f1), sin(f1)) * innerRadius + pos);
            }
        }
    }
    rd->endPrimitive();

    // Go back around making quads for a bevelled edge
    rd->setShadeMode(RenderDevice::SHADE_SMOOTH);
    rd->beginPrimitive(RenderDevice::QUADS);
    {
        float sum = 0;
        for (int i = 0; i < N; ++i) {
            if ((sum + pct[i] / 2 > 0.5) && (split > i)) {
                // This is the first label that needs to be shown on the left.
                split = i;
            }

            const float a0 = sum * G3D_PI * 2.0 + shift;
            const float a1 = (sum + pct[i]) * G3D_PI * 2.0 + shift;
            sum += pct[i];

            // Number of subdivisions we need to make this slice look
            // like a smooth curve.
            const int M = iMax(1, (int)(pct[i] * totalSlices));
            for (int j = 0; j < M; ++j) {
                float f0 = G3D::lerp(a0, a1, j /(float)M);
                float f1 = G3D::lerp(a0, a1, (j + 1) /(float)M);
                rd->setColor(color[i]);
                rd->sendVertex(Vector2(cos(f0), sin(f0)) * innerRadius + pos);
                rd->sendVertex(Vector2(cos(f1), sin(f1)) * innerRadius + pos);

                rd->setColor(shade(f1, color[i]));
                rd->sendVertex(Vector2(cos(f1), sin(f1)) * outerRadius + pos);
                rd->setColor(shade(f0, color[i]));
                rd->sendVertex(Vector2(cos(f0), sin(f0)) * outerRadius + pos);
            }
        }
    }
    rd->endPrimitive();
    rd->setShadeMode(RenderDevice::SHADE_FLAT);

    // Draw outlines
    rd->setBlendFunc(RenderDevice::BLEND_SRC_ALPHA, RenderDevice::BLEND_ONE_MINUS_SRC_ALPHA);
    rd->beginPrimitive(RenderDevice::LINES);
    {
        rd->setColor(outlineColor);
        float sum = 0;
        for (int i = 0; i < N; ++i) {
            const float a0 = sum * G3D_PI * 2.0 + shift;
            const float a1 = (sum + pct[i]) * G3D_PI * 2.0 + shift;
            sum += pct[i];
            rd->sendVertex(Vector2(cos(a0), sin(a0)) * radius + pos);
            rd->sendVertex(pos);

            // Number of subdivisions we need to make this slice look
            // like a smooth curve.
            const int M = iMax(1, (int)(pct[i] * totalSlices));
            for (int j = 0; j < M; ++j) {
                float f0 = G3D::lerp(a0, a1, j /(float)M);
                float f1 = G3D::lerp(a0, a1, (j + 1) /(float)M);
                rd->sendVertex(Vector2(cos(f0), sin(f0)) * radius + pos);
                rd->sendVertex(Vector2(cos(f1), sin(f1)) * radius + pos);
            }
        }
    }
    rd->endPrimitive();

    // Text labels
    float fpsSize = radius * 0.8f;
    float labelSize = radius * 0.35f;

    labelFont->draw2D(rd, format("%d", (int)fps), pos - Vector2(0, labelSize / 2 + 4), fpsSize, 
        Color3::white(), Color3::black(), 
        GFont::XALIGN_CENTER, GFont::YALIGN_CENTER);

    smallFont->draw2D(rd, format("fps", (int)fps), pos + Vector2(0, fpsSize / 2 + 3), labelSize, 
        Color3::white(), Color3::black(), 
        GFont::XALIGN_CENTER, GFont::YALIGN_CENTER);

    // Draw some labels on the right and some on the left
    split = 3;
    int numLeft  = N - split;
    int numRight = split;

    float leftScale  = (radius * 2.0f - labelSize * 2) / (numLeft - 1);
    float rightScale = (radius * 2.0f - labelSize * 2) / (numRight - 1);

    for (int j = 0; j < numLeft; ++j) {
        int i = N - 1 - j;
        
        Vector2 textPos = pos + Vector2(-radius - 5, j * leftScale - radius + labelSize);

        Rect2D bounds = Rect2D::xywh(textPos - Vector2(-4, labelSize * 0.75), Vector2(-70, labelSize * 1.5));

        // Draw a button if the mouse is over the bounds
        if (bounds.contains(mouse)) {          
            G3UI::drawButton(rd, bounds, NULL, app->userInput->keyPressed(G3D::SDL_LEFT_MOUSE_KEY));
        }

        smallFont->draw2D(rd, format("%s% 3d%%", name[i], (int)(pct[i] * 100)),
            textPos,
            labelSize, color[i], Color4::clear(),
            GFont::XALIGN_RIGHT, GFont::YALIGN_CENTER);
    }

    for (int j = 0; j < numRight; ++j) {
        int i = j;
        
        Vector2 textPos = pos + Vector2(radius - 5, j * rightScale - radius + labelSize);

        Rect2D bounds = Rect2D::xywh(textPos - Vector2(-6, labelSize * 0.75), Vector2(70, labelSize * 1.5));

        float k = 1.0;
        // Draw a button if the mouse is over the bounds
        if (bounds.contains(mouse)) {          
            G3UI::drawButton(rd, bounds, NULL, app->userInput->keyPressed(G3D::SDL_LEFT_MOUSE_KEY));
            // Darken text
            k = 0.5;
        }

        smallFont->draw2D(rd,
            format("% 3d%% %s", (int)(pct[i] * 100), name[i]),
            textPos,
            labelSize, color[i] * k, Color4::clear(),
            GFont::XALIGN_LEFT, GFont::YALIGN_CENTER);
    }

    rd->popState();
}

}
