/*************************************************************************
 * 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 v1.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors: Details at https://graphviz.org
 *************************************************************************/

#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*/
int GetOGLPosRef(int x, int y, float *X, float *Y) {
    double wwinX;
    double wwinY;
    double wwinZ;
    double posX, posY, posZ;

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

    //draw a point  to a not important 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,
	       &wwinX, &wwinY, &wwinZ);
    winX = (float) x;
    winY = (float) viewport[3] - (float) y;
    gluUnProject(winX, winY, wwinZ, modelview, projection, viewport, &posX,
		 &posY, &posZ);

    *X = (float) posX;
    *Y = (float) posY;
    (void)posZ; // ignore Z that the caller does not need

    return 1;
}

float GetOGLDistance(float l) {
    double wwinX;
    double wwinY;
    double wwinZ;
    double posX, posY, posZ;
    double posXX, posYY, posZZ;

    int32_t viewport[4];
    double modelview[16];
    double projection[16];
    float winX, winY;

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

    //draw a point  to a not important 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.00, modelview, projection, viewport, &wwinX,
	       &wwinY, &wwinZ);
    float x = 50.0f;
    const float y = 50.0f;
    winX = x;
    winY = (float) viewport[3] - y;
    gluUnProject(winX, winY, wwinZ, modelview, projection, viewport, &posX,
		 &posY, &posZ);
    x += l;
    winX = x;
    winY = (float) viewport[3] - y;
    gluUnProject(winX, winY, wwinZ, modelview, projection, viewport,
		 &posXX, &posYY, &posZZ);
    return ((float) (posXX - posX));
}

/*
	functions def: returns opengl coordinates of firt hit object by using screen coordinates
	x,y; 2D screen coordiantes (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 winX, winY;
    float winZ[400];
    double posX, posY, posZ;
    int idx;

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

    winX = (float) x;
    winY = (float) viewport[3] - (float) 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;
    return;
}

#include <math.h>

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

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 , chekc 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) { glColor4f(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, float d,
                         glCompColor *c, bool bumped) {
    float color_fac;
    glCompPoint A, B, C, D, E, F, G, H;
    float dim = 1;
    if (!bumped) {
	color_fac = 1.3f;
	b = b - 2;
	dim = 0.5;
    } else
	color_fac = 1.0f / 1.3f;


    A.x = p->x;
    A.y = p->y;
    A.z = p->z;
    B.x = p->x + w;
    B.y = p->y;
    B.z = p->z;
    C.x = p->x + w;
    C.y = p->y + h;
    C.z = p->z;
    D.x = p->x;
    D.y = p->y + h;
    D.z = p->z;
    G.x = p->x + b;
    G.y = p->y + b;
    G.z = p->z + d;
    H.x = p->x + w - b;
    H.y = p->y + b;
    H.z = p->z + d;
    E.x = p->x + b;
    E.y = p->y + h - b;
    E.z = p->z + d;
    F.x = p->x + w - b;
    F.y = p->y + h - b;
    F.z = p->z + d;
    glBegin(GL_QUADS);
    glColor4f(c->R * dim, c->G * dim, c->B * dim, c->A);
    glCompQuadVertex(&G, &H, &F, &E);

    glColor4f(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);

    glColor4f(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();

}

float distBetweenPts(glCompPoint A, glCompPoint B, float R) {
    float 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=sqrtf(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
 */
