
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+

#include "GLPanel.h"
#include "cubic_math.h"
#include <algorithm>

using namespace CubicVR;

GLPanel::GLPanel() : fillType(GLPANEL_FILL_SOLID), contentsVisible(true), visible(true), transform(mat4::identity()) {
    pos[0] = 0.0f;
    pos[1] = 0.0f;
    rot[0] = 0.0f;
    rot[1] = 0.0f;
    rot[2] = 0.0f;
    size[0] = 1.0f;
    size[1] = 1.0f;
    fill[0] = RGBA4f(0.5f,0.5f,0.5f);
    fill[1] = RGBA4f(0.1f,0.1f,0.1f);
    borderColor = RGBA4f(0.8f, 0.8f, 0.8f);
    setCoordinateSystem(GLPANEL_Y_UP);
    setMarginPx(0);
    setBorderPx(0);
    srcBlend = GL_SRC_ALPHA;
    dstBlend = GL_ONE_MINUS_SRC_ALPHA;
}

void GLPanel::genArrays() {
    float gmin = -1.0, gmid = 0, gmax = 1.0;
    
    if (fillType == GLPANEL_FILL_SOLID || fillType == GLPANEL_FILL_GRAD_X || fillType == GLPANEL_FILL_GRAD_Y) {
        glPoints.reserve(2 * 4);
        glPoints.resize(2 * 4);
        glColors.reserve(4 * 4);
        glColors.resize(4 * 4);
        
        float pts[2 * 4] = {
                gmin, gmin,
                gmin, gmax,
                gmax, gmax,
                gmax, gmin
        };
        
        RGBA4f c[4];
        
        if (fillType == GLPANEL_FILL_SOLID) {
            c[0] = c[1] = c[2] = c[3] = fill[0];
        } else if (fillType == GLPANEL_FILL_GRAD_X) {
            c[0] = c[1] = fill[0];
            c[2] = c[3] = fill[1];
        } else if (fillType == GLPANEL_FILL_GRAD_Y) {
            c[0] = c[3] = fill[0];
            c[1] = c[2] = fill[1];
        }
        
        float clr[4 * 4] = {
             c[0].r, c[0].g, c[0].b, c[0].a,
             c[1].r, c[1].g, c[1].b, c[1].a,
             c[2].r, c[2].g, c[2].b, c[2].a,
             c[3].r, c[3].g, c[3].b, c[3].a
         };
        
        glPoints.assign(pts, pts + (2 * 4));
        glColors.assign(clr, clr + (4 * 4));
    } else {
        glPoints.reserve(2 * 8);
        glPoints.resize(2 * 8);
        glColors.reserve(4 * 8);
        glColors.resize(4 * 8);
        
        RGBA4f c[8];
        
        if (fillType == GLPANEL_FILL_GRAD_BAR_X) {
            float pts[2 * 8] = {
                    gmin, gmin,
                    gmin, gmax,
                    gmid, gmax,
                    gmid, gmin,

                    gmid, gmin,
                    gmid, gmax,
                    gmax, gmax,
                    gmax, gmin
            };
            glPoints.assign(pts, pts + (2 * 8));

            c[0] = c[1] = fill[0];
            c[2] = c[3] = fill[1];

            c[4] = c[5] = fill[1];
            c[6] = c[7] = fill[0];

        } else if (fillType == GLPANEL_FILL_GRAD_BAR_Y) {
            float pts[2 * 8] = {
                    gmin, gmin,
                    gmin, gmid,
                    gmax, gmid,
                    gmax, gmin,

                    gmin, gmid,
                    gmin, gmax,
                    gmax, gmax,
                    gmax, gmid
            };
            glPoints.assign(pts, pts + (2 * 8));

            c[0] = c[3] = fill[0];
            c[1] = c[2] = fill[1];

            c[4] = c[7] = fill[1];
            c[5] = c[6] = fill[0];
        }
        
        float clr[4 * 8] = {
            c[0].r, c[0].g, c[0].b, c[0].a,
            c[1].r, c[1].g, c[1].b, c[1].a,
            c[2].r, c[2].g, c[2].b, c[2].a,
            c[3].r, c[3].g, c[3].b, c[3].a,
            c[4].r, c[4].g, c[4].b, c[4].a,
            c[5].r, c[5].g, c[5].b, c[5].a,
            c[6].r, c[6].g, c[6].b, c[6].a,
            c[7].r, c[7].g, c[7].b, c[7].a
        };
        
        glColors.assign(clr, clr + (4 * 8));
    }
}


void GLPanel::setViewport() {
    GLint vp[4];
    glGetIntegerv(GL_VIEWPORT, vp);
    
    view[0] = vp[2];
    view[1] = vp[3];
}

void GLPanel::setPosition(float x, float y) {
    pos[0] = x;
    pos[1] = y;
}

void GLPanel::setSize(float w, float h) {
    size[0] = w;
    size[1] = h;
}

float GLPanel::getWidth() {
    return size[0];
}

float GLPanel::getHeight() {
    return size[1];
}

float GLPanel::getWidthPx() const {
    return pdim.x;
}

float GLPanel::getHeightPx() const {
    return pdim.y;
}


void GLPanel::setCoordinateSystem(GLPanelCoordinateSystem coord_in) {
    coord = coord_in;
    
    if (coord == GLPANEL_Y_DOWN || coord == GLPANEL_Y_UP) {
        min = -1;
        mid = 0;
        max = 1;
    } else {
        min = 0;
        mid = 0.5;
        max = 1;
    }
    
    genArrays();
}

bool GLPanel::hitTest(CubicVR::vec2 pos_in, CubicVR::vec2 &result) const {
    CubicVR::vec4 hitPos = CubicVR::mat4::vec4_multiply(CubicVR::vec4(pos_in.x, pos_in.y, 0.0, 1.0), transformInverse);
    
    if (hitPos.x >= -1.0 && hitPos.x <= 1.0 && hitPos.y >= -1.0 && hitPos.y <= 1.0) {
        result.x = hitPos.x;
        result.y = hitPos.y;
        return true;
    }
    
    return false;
}


void GLPanel::setFill(GLPanelFillType fill_mode) {
    fillType = fill_mode;
    genArrays();
}

void GLPanel::setFillColor(const RGBA4f& color1) {
    fill[0] = color1;
    genArrays();
}

void GLPanel::setFillColor(const RGBA4f& color1, const RGBA4f& color2) {
    fill[0] = color1;
    fill[1] = color2;
    genArrays();
}

void GLPanel::setMarginPx(float marg) {
    marginPx = marg;
}


void GLPanel::setBorderColor(const RGBA4f& clr) {
    borderColor = clr;
}

void GLPanel::setBorderPx(float bord) {
    borderPx.left = borderPx.right = borderPx.top = borderPx.bottom = bord;
}

void GLPanel::setBorderPx(float bordl, float bordr, float bordt, float bordb) {
    borderPx.left = bordl;
    borderPx.right = bordr;
    borderPx.top = bordt;
    borderPx.bottom = bordb;
}

void GLPanel::setBlend(GLuint src, GLuint dst) {
    srcBlend = src;
    dstBlend = dst;
}

void GLPanel::addChild(GLPanel *childPanel) {
    auto i = std::find(children.begin(), children.end(), childPanel);

    if (i == children.end()) {
        children.push_back(childPanel);
    }
}

void GLPanel::removeChild(GLPanel *childPanel) {
    auto i = std::find(children.begin(), children.end(), childPanel);

    if (i != children.end()) {
        children.erase(i);
    }
}

void GLPanel::drawChildren() {
    if (!children.empty()) {
        std::vector<GLPanel *>::iterator panel_i;
        
        for (panel_i = children.begin(); panel_i != children.end(); panel_i++) {
            (*panel_i)->calcTransform(transform);
            (*panel_i)->draw();
        }
    }
}

void GLPanel::drawPanelContents() {
    drawChildren();
}

void GLPanel::calcTransform(mat4 transform_in) {
    // compute local transform
    localTransform = mat4::translate(pos[0], pos[1], 0) * mat4::scale(size[0], size[1], 1);
    
    if (rot[0] || rot[1] || rot[2]) {
        localTransform *= mat4::rotate(rot[0], rot[1], rot[2]);
    }
    
    // compute global transform
    transform = transform_in * localTransform;
    
    // init view[]
    setViewport();
    
    // get min/max transform
    vec4 vmin_t = mat4::vec4_multiply(vec4(min, min, 0, 1), transform);
    vec4 vmax_t = mat4::vec4_multiply(vec4(max, max, 0, 1), transform);
    
    // screen dimensions
    vmin = vec2((vmin_t.x > vmax_t.x)?vmax_t.x:vmin_t.x, (vmin_t.y > vmax_t.y)?vmax_t.y:vmin_t.y);
    vmax = vec2((vmin_t.x > vmax_t.x)?vmin_t.x:vmax_t.x, (vmin_t.y > vmax_t.y)?vmin_t.y:vmax_t.y);
    
    // unit dimensions
    umin = (vmin * 0.5) + vec2(1,1);
    umax = (vmax * 0.5) + vec2(1,1);
    
    ucenter = vec2((umin + umax) * 0.5);
    
    // pixel dimensions
    pdim = vec2((vmax.x - vmin.x) / 2.0 * view[0], (vmax.y  - vmin.y) / 2.0 * view[1]);
    pvec = vec2(((vmax.x - vmin.x) / 2.0) / pdim.x, ((vmax.y - vmin.y) / 2.0) / pdim.y);
    
//    std::cout << umin << " :: " << ucenter << " :: " << pdim << " :: " << pvec << std::endl;
    
    if (marginPx) {
        transform *= mat4::scale(1.0 - marginPx * 2.0 * pvec.x / size[0], 1.0 - marginPx * 2.0 * pvec.y / size[1], 1);
    }
    
    transformInverse = CubicVR::mat4::inverse(transform);
}

void GLPanel::draw() {
    float gmin = -1.0, gmax = 1.0;

    glLoadMatrixf(transform.to_ptr());
    
    if (fillType != GLPANEL_FILL_NONE && visible) {
        glEnable(GL_BLEND);
        glBlendFunc(srcBlend, dstBlend);
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_COLOR_ARRAY);
        glVertexPointer(2, GL_FLOAT, 0, &glPoints[0]);
        glColorPointer(4, GL_FLOAT, 0, &glColors[0]);
        
        glDrawArrays(GL_QUADS, 0, glPoints.size() / 2);
        
        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_COLOR_ARRAY);
        
        if (borderPx.left || borderPx.right || borderPx.top || borderPx.bottom) {
            glEnable(GL_LINE_SMOOTH);
            glColor4f(borderColor.r, borderColor.g, borderColor.b, borderColor.a);
            
            if (borderPx.left) {
                glLineWidth(borderPx.left);
                glBegin(GL_LINES);
                glVertex2f(gmin, gmin);
                glVertex2f(gmin, gmax);
                glEnd();
            }
        
            if (borderPx.right) {
                glLineWidth(borderPx.right);
                glBegin(GL_LINES);
                glVertex2f(gmax, gmin);
                glVertex2f(gmax, gmax);
                glEnd();
            }

            if (borderPx.top) {
                glLineWidth(borderPx.top);
                glBegin(GL_LINES);
                glVertex2f(gmin, gmin);
                glVertex2f(gmax, gmin);
                glEnd();
            }

            if (borderPx.bottom) {
                glLineWidth(borderPx.bottom);
                glBegin(GL_LINES);
                glVertex2f(gmin, gmax);
                glVertex2f(gmax, gmax);
                glEnd();
            }

            glDisable(GL_LINE_SMOOTH);
        }
        glDisable(GL_BLEND);
    }
    
    if (contentsVisible) {
        mat4 mCoord = mat4::identity();

        if (coord == GLPANEL_Y_DOWN_ZERO_ONE) {
            mCoord *= mat4::translate(-1.0f, 1.0f, 0.0f) * mat4::scale(2.0f, -2.0f, 2.0f);
        }
        if (coord == GLPANEL_Y_UP_ZERO_ONE) {
            mCoord = mat4::translate(-1.0f, -1.0f, 0.0f) * mat4::scale(2.0f, 2.0f, 2.0f);
        }
        if (coord == GLPANEL_Y_DOWN) {
            mCoord = mat4::scale(1.0f, -1.0f, 1.0f);
        }
        //        if (coord == GLPANEL_Y_UP) {
        //        }
        glLoadMatrixf((transform * mCoord).to_ptr());
        drawPanelContents();
    }
}


GLTextPanel::GLTextPanel() : GLPanel() {
    coord = GLPANEL_Y_UP;
    horizAlign = GLFont::GLFONT_ALIGN_CENTER;
    vertAlign = GLFont::GLFONT_ALIGN_CENTER;
    useNativeFont = true;
}

void GLTextPanel::drawPanelContents() {
    glColor4f(1, 1, 1, 1.0);
   
    
    
    float pdimy = pdim.y;

    double appliedScaleFactor = GLFont::getScaleFactor();
        
    if (useNativeFont) {
        appliedScaleFactor = 1.0;
    }
    
    //pdimy is considered un-scaled 
    pdimy = round(pdimy / appliedScaleFactor);
 
    int size = 12;

    if (pdimy <= 16) {
    
        size = 12;
    } else if (pdimy <= 18) {
      
        size = 16;
    } else if(pdimy <= 24) {
      
        size = 18;
    } else if(pdimy <= 32) {
      
        size = 24;
    } else if(pdimy <= 48) {
      
        size = 32;
    } else {
     
        size = 48;
    }

    GLFont::getFont(size, appliedScaleFactor).drawString(textVal, mid, mid, horizAlign, vertAlign, (int)pdim.x, (int)pdim.y);
}

void GLTextPanel::setText(std::string text, GLFont::Align hAlign, GLFont::Align vAlign, bool useNative) {
    textVal = text;
    horizAlign = hAlign;
    vertAlign = vAlign;
    useNativeFont = useNative;
}

std::string GLTextPanel::getText() {
    return textVal;
}



void GLTestPanel::drawPanelContents() {
    glColor3f(1.0,1.0,1.0);
    glBegin(GL_LINES);
    glVertex2f(min, mid);
    glVertex2f(max, mid);
    glVertex2f(mid, min);
    glVertex2f(mid, max);
    
    glVertex2f(mid, max);
    glVertex2f(mid - 0.02, max - 0.2);
    glVertex2f(mid, 1);
    glVertex2f(mid + 0.02, max - 0.2);
    
    glVertex2f(max, mid);
    glVertex2f(max - 0.1, mid + max * 0.25);
    glVertex2f(max, mid);
    glVertex2f(max - 0.1, mid - max * 0.25);
    
    glEnd();
}
