/*************************************************************************
 * Copyright (c) 2011 AT&T Intellectual Property 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
 *
 * Contributors: Details at https://graphviz.org
 *************************************************************************/

#include "config.h"

#include <glcomp/glutils.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <glcomp/glcompdefs.h>

/*transforms 2d windows location to 3d gl coords but depth is calculated unlike the previous function*/
void GetOGLPosRef(int x, int y, float *X, float *Y) {
    double wwinZ;
    double posX, posY;

    int32_t viewport[4];
    double modelview[16];
    double projection[16];
    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);

    // draw a point to an unimportant location to get window coordinates
    glColor4f(0.0f, 0.0f, 0.0f, 0.001f);

    glBegin(GL_POINTS);
    glVertex3f(-100.0f, -100.0f, 0.0f);
    glEnd();
    gluProject(-100.0, -100.0, 0.00, modelview, projection, viewport,
               &(double){0}, &(double){0}, &wwinZ);
    const double winX = x;
    const double winY = (double)viewport[3] - y;
    gluUnProject(winX, winY, wwinZ, modelview, projection, viewport, &posX,
                 &posY, &(double){0}); // ignore Z that the caller does not need

    *X = (float) posX;
    *Y = (float) posY;
}

double GetOGLDistance(double l) {
    double wwinZ;
    double posX;
    double posXX;

    int32_t viewport[4];
    double modelview[16];
    double projection[16];

    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);

    // draw a point to an unimportant location to get window coordinates
    glColor4f(0.0f, 0.0f, 0.0f, 0.001f);

    glBegin(GL_POINTS);
    glVertex3f(10.0f, 10.0f, 1.0f);
    glEnd();
    gluProject(10.0, 10.0, 1.0, modelview, projection, viewport, &(double){0},
               &(double){0}, &wwinZ);
    double x = 50.0;
    const double y = 50.0;
    double winX = x;
    double winY = viewport[3] - y;
    gluUnProject(winX, winY, wwinZ, modelview, projection, viewport, &posX,
                 &(double){0}, &(double){0});
    x += l;
    winX = x;
    winY = viewport[3] - y;
    gluUnProject(winX, winY, wwinZ, modelview, projection, viewport,
                 &posXX, &(double){0}, &(double){0});
    return posXX - posX;
}

/*
	functions def: returns opengl coordinates of first hit object by using screen coordinates
	x,y; 2D screen coordinates (usually received from mouse events
	X,Y,Z; pointers to coordinates values to be calculated
	return value: no return value


*/

void to3D(int x, int y, float *X, float *Y, float *Z) {
    int const WIDTH = 20;

    int32_t viewport[4];
    double modelview[16];
    double projection[16];
    float winZ[400];
    double posX, posY, posZ;
    int idx;

    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);

    const double winX = x;
    const double winY = (double)viewport[3] - y;

    glReadPixels(x - WIDTH / 2, (int) winY - WIDTH / 2, WIDTH, WIDTH,
		 GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);
    float comp = -9999999;
    for (idx = 0; idx < WIDTH * WIDTH; idx++) {
	if (winZ[idx] > comp && winZ[idx] < 1)
	    comp = winZ[idx];
    }

    gluUnProject(winX, winY, comp, modelview, projection, viewport, &posX,
		 &posY, &posZ);

    *X = (float)posX;
    *Y = (float)posY;
    *Z = (float)posZ;
}

#include <math.h>

static glCompPoint sub(glCompPoint p, glCompPoint q)
{
    return (glCompPoint){.x = p.x - q.x, .y = p.y - q.y, .z = p.z - q.z};
}

static double dot(glCompPoint p, glCompPoint q)
{
    return p.x * q.x + p.y * q.y + p.z * q.z;
}

static double len(glCompPoint p)
{
    return sqrt(dot(p, p));
}

static glCompPoint blend(glCompPoint p, glCompPoint q, float m)
{
    glCompPoint r;

    r.x = p.x + m * (q.x - p.x);
    r.y = p.y + m * (q.y - p.y);
    r.z = p.z + m * (q.z - p.z);
    return r;
}

static double dist(glCompPoint p, glCompPoint q)
{
    return len(sub(p, q));
}

/*
 * Given a line segment determined by two points a and b, and a 3rd point p,
 * return the distance between the point and the segment.
 * If the perpendicular from p to the line a-b is outside of the segment,
 * return the distance to the closer of a or b.
 */
double point_to_lineseg_dist(glCompPoint p, glCompPoint a, glCompPoint b)
{
    float U;
    glCompPoint q;
    glCompPoint ba = sub(b, a);
    glCompPoint pa = sub(p, a);

    U = (float) (dot(pa, ba) / dot(ba, ba));

    if (U > 1)
	q = b;
    else if (U < 0)
	q = a;
    else
	q = blend(a, b, U);

    return dist(p, q);
}

void glCompCalcWidget(glCompCommon * parent, glCompCommon * child,
		      glCompCommon * ref)
{
    /*check alignments first , alignments overrides anchors */
    float borderWidth;
    ref->height = child->height;
    ref->width = child->width;
    if(!parent)
    {
	child->refPos.x = child->pos.x;
	child->refPos.y = child->pos.y;
	return;
    }
    borderWidth = parent->borderWidth;
    if (child->align != glAlignNone)	//if alignment, make sure width and height is no greater than parent
    {
	if (child->width > parent->width)
	    ref->width = parent->width - 2.0f *borderWidth;
	if (child->height > parent->height)
	    ref->height = parent->height - 2.0f *borderWidth;;

    }

    ref->pos.x = parent->refPos.x + ref->pos.x + borderWidth;
    ref->pos.y = parent->refPos.y + ref->pos.y + borderWidth;


    switch (child->align) {
    case glAlignLeft:
	ref->pos.x = parent->refPos.x + borderWidth;
	ref->pos.y = parent->refPos.y + borderWidth;
	ref->height = parent->height - 2.0f * borderWidth;
	break;
    case glAlignRight:
	ref->pos.x =
	    parent->refPos.x + parent->width - child->width - borderWidth;
	ref->pos.y = parent->refPos.y + borderWidth;
	ref->height = parent->height - 2.0f * borderWidth;
	break;

    case glAlignTop:
	ref->pos.y =
	    parent->refPos.y + parent->height - child->height -
	    borderWidth;
	ref->pos.x = parent->refPos.x;
	ref->width = parent->width - 2.0f * borderWidth;
	break;

    case glAlignBottom:
	ref->pos.y = parent->refPos.y + borderWidth;
	ref->pos.x = parent->refPos.x + borderWidth;
	ref->width = parent->width - 2.0f * borderWidth;
	break;
    case glAlignParent:
	ref->pos.y = parent->refPos.y + borderWidth;
	ref->pos.x = parent->refPos.x + borderWidth;;
	ref->width = parent->width - 2.0f * borderWidth;;
	ref->height = parent->height - 2.0f * borderWidth;
	break;
    case glAlignCenter:
    case glAlignNone:
	break;
    }
    if (child->align == glAlignNone)	// No alignment, check anchors
    {
	ref->pos.x = parent->refPos.x + child->pos.x + borderWidth;
	ref->pos.y = parent->refPos.y + child->pos.y + borderWidth;

	if (child->anchor.leftAnchor)
	    ref->pos.x =
		parent->refPos.x + child->anchor.left + borderWidth;
	if (child->anchor.bottomAnchor)
	    ref->pos.y =
		parent->refPos.y + child->anchor.bottom + borderWidth;

	if (child->anchor.topAnchor)
	    ref->height =
		parent->refPos.y + parent->height - ref->pos.y -
		child->anchor.top - borderWidth;
	if (child->anchor.rightAnchor)
	    ref->width =
		parent->refPos.x + parent->width - ref->pos.x -
		child->anchor.right - borderWidth;
    }
    child->refPos.x = ref->pos.x;
    child->refPos.y = ref->pos.y;
    child->width = ref->width;
    child->height = ref->height;
}

static void glCompQuadVertex(glCompPoint p0, glCompPoint p1, glCompPoint p2,
                             glCompPoint p3) {
    glVertex3f(p0.x, p0.y, p0.z);
    glVertex3f(p1.x, p1.y, p1.z);
    glVertex3f(p2.x, p2.y, p2.z);
    glVertex3f(p3.x, p3.y, p3.z);
}

void glCompSetColor(glCompColor c) { glColor4d(c.R, c.G, c.B, c.A); }

void glCompDrawRectangle(glCompRect * r)
{
    glBegin(GL_QUADS);
    glVertex3f(r->pos.x, r->pos.y, r->pos.z);
    glVertex3f(r->pos.x + r->w, r->pos.y, r->pos.z);
    glVertex3f(r->pos.x + r->w, r->pos.y + r->h, r->pos.z);
    glVertex3f(r->pos.x, r->pos.y + r->h, r->pos.z);
    glEnd();
}

void glCompDrawRectPrism(glCompPoint p, float w, float h, float b,
                         glCompColor c, bool bumped) {
    const float d = 0.01f;
    float color_fac = 1.0f / 1.3f;
    float dim = 1;
    if (!bumped) {
	color_fac = 1.3f;
	b -= 2;
	dim = 0.5;
    }

    const glCompPoint A = {.x = p.x,         .y = p.y,         .z = p.z    };
    const glCompPoint B = {.x = p.x + w,     .y = p.y,         .z = p.z    };
    const glCompPoint C = {.x = p.x + w,     .y = p.y + h,     .z = p.z    };
    const glCompPoint D = {.x = p.x,         .y = p.y + h,     .z = p.z    };
    const glCompPoint G = {.x = p.x + b,     .y = p.y + b,     .z = p.z + d};
    const glCompPoint H = {.x = p.x + w - b, .y = p.y + b,     .z = p.z + d};
    const glCompPoint E = {.x = p.x + b,     .y = p.y + h - b, .z = p.z + d};
    const glCompPoint F = {.x = p.x + w - b, .y = p.y + h - b, .z = p.z + d};
    glBegin(GL_QUADS);
    glColor4d(c.R * dim, c.G * dim, c.B * dim, c.A);
    glCompQuadVertex(G, H, F, E);

    glColor4d(c.R * color_fac * dim, c.G * color_fac * dim,
              c.B * color_fac * dim, c.A);
    glCompQuadVertex(A, B, H, G);
    glCompQuadVertex(B, H, F, C);

    glColor4d(c.R / color_fac * dim, c.G / color_fac * dim,
              c.B / color_fac * dim, c.A);
    glCompQuadVertex(A, G, E, D);
    glCompQuadVertex(E, F, C, D);
    glEnd();
}

double distBetweenPts(glCompPoint A, glCompPoint B, double R) {
    double rv = (A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y) + (A.z - B.z) * (A.z - B.z);
    rv = sqrt(rv);
    if (rv <=R)
	return 0;
    return rv;
}

int is_point_in_rectangle(float X, float Y, float RX, float RY, float RW,float RH)
{
    if (X >= RX && X <= RX + RW && Y >= RY && Y <= RY + RH)
	return 1;
    else
	return 0;
}

/**
 * @dir lib/glcomp
 * @brief OpenGL GUI for cmd/smyrna
 */
