#include <math.h>
#include "qwt3d_color.h"
#include "qwt3d_plot.h"
#include "qwt3d_enrichment_std.h"

using namespace Qwt3D;

/////////////////////////////////////////////////////////////////
//
//   CrossHair
//
/////////////////////////////////////////////////////////////////

CrossHair::CrossHair()
{
    configure(0, 1, false, false);
}

CrossHair::CrossHair(double rad, double linewidth, bool smooth, bool boxed)
{
    configure(rad, linewidth, smooth, boxed);
}

void CrossHair::configure(double rad, double linewidth, bool smooth, bool boxed)
{
    plot = 0;
    radius_ = rad;
    linewidth_ = linewidth;
    smooth_ = smooth;
    boxed_ = boxed;
}

void CrossHair::drawBegin()
{
    setDeviceLineWidth(linewidth_);
    oldstate_ = glIsEnabled(GL_LINE_SMOOTH);
    if (smooth_)
        glEnable(GL_LINE_SMOOTH);
    else
        glDisable(GL_LINE_SMOOTH);
    glBegin(GL_LINES);
}

void CrossHair::drawEnd()
{
    glEnd();

    if (oldstate_)
        glEnable(GL_LINE_SMOOTH);
    else
        glDisable(GL_LINE_SMOOTH);
}

void CrossHair::draw(Qwt3D::Triple const &pos)
{
    RGBA rgba = (*plot->dataColor())(pos);
    glColor4d(rgba.r, rgba.g, rgba.b, rgba.a);

    double diag = (plot->hull().maxVertex - plot->hull().minVertex).length() * radius_;

    glVertex3d(pos.x - diag, pos.y, pos.z);
    glVertex3d(pos.x + diag, pos.y, pos.z);

    glVertex3d(pos.x, pos.y - diag, pos.z);
    glVertex3d(pos.x, pos.y + diag, pos.z);

    glVertex3d(pos.x, pos.y, pos.z - diag);
    glVertex3d(pos.x, pos.y, pos.z + diag);

    // hull

    if (!boxed_)
        return;

    glVertex3d(pos.x - diag, pos.y - diag, pos.z + diag);
    glVertex3d(pos.x + diag, pos.y - diag, pos.z + diag);
    glVertex3d(pos.x - diag, pos.y - diag, pos.z - diag);
    glVertex3d(pos.x + diag, pos.y - diag, pos.z - diag);

    glVertex3d(pos.x - diag, pos.y + diag, pos.z + diag);
    glVertex3d(pos.x + diag, pos.y + diag, pos.z + diag);
    glVertex3d(pos.x - diag, pos.y + diag, pos.z - diag);
    glVertex3d(pos.x + diag, pos.y + diag, pos.z - diag);

    glVertex3d(pos.x - diag, pos.y - diag, pos.z + diag);
    glVertex3d(pos.x - diag, pos.y + diag, pos.z + diag);
    glVertex3d(pos.x - diag, pos.y - diag, pos.z - diag);
    glVertex3d(pos.x - diag, pos.y + diag, pos.z - diag);

    glVertex3d(pos.x + diag, pos.y - diag, pos.z + diag);
    glVertex3d(pos.x + diag, pos.y + diag, pos.z + diag);
    glVertex3d(pos.x + diag, pos.y - diag, pos.z - diag);
    glVertex3d(pos.x + diag, pos.y + diag, pos.z - diag);

    glVertex3d(pos.x - diag, pos.y - diag, pos.z - diag);
    glVertex3d(pos.x - diag, pos.y - diag, pos.z + diag);
    glVertex3d(pos.x + diag, pos.y - diag, pos.z - diag);
    glVertex3d(pos.x + diag, pos.y - diag, pos.z + diag);

    glVertex3d(pos.x - diag, pos.y + diag, pos.z - diag);
    glVertex3d(pos.x - diag, pos.y + diag, pos.z + diag);
    glVertex3d(pos.x + diag, pos.y + diag, pos.z - diag);
    glVertex3d(pos.x + diag, pos.y + diag, pos.z + diag);
}

/////////////////////////////////////////////////////////////////
//
//   Dot
//
/////////////////////////////////////////////////////////////////

Dot::Dot()
{
    configure(1, false);
}

Dot::Dot(double pointsize, bool smooth)
{
    configure(pointsize, smooth);
}

void Dot::configure(double pointsize, bool smooth)
{
    plot = 0;
    pointsize_ = pointsize;
    smooth_ = smooth;
}

void Dot::drawBegin()
{
    setDevicePointSize(pointsize_);
    oldstate_ = glIsEnabled(GL_POINT_SMOOTH);
    if (smooth_)
        glEnable(GL_POINT_SMOOTH);
    else
        glDisable(GL_POINT_SMOOTH);

    // glPointSize(10);
    glBegin(GL_POINTS);
}

void Dot::drawEnd()
{
    glEnd();

    if (oldstate_)
        glEnable(GL_POINT_SMOOTH);
    else
        glDisable(GL_POINT_SMOOTH);
}

void Dot::draw(Qwt3D::Triple const &pos)
{
    RGBA rgba = (*plot->dataColor())(pos);
    glColor4d(rgba.r, rgba.g, rgba.b, rgba.a);
    glVertex3d(pos.x, pos.y, pos.z);
}

/////////////////////////////////////////////////////////////////
//
//   Cone
//
/////////////////////////////////////////////////////////////////

Cone::Cone()
{
    hat = gluNewQuadric();
    disk = gluNewQuadric();

    configure(0, 3);
}

Cone::Cone(double rad, unsigned quality)
{
    hat = gluNewQuadric();
    disk = gluNewQuadric();

    configure(rad, quality);
}

Cone::~Cone()
{
    gluDeleteQuadric(hat);
    gluDeleteQuadric(disk);
}

void Cone::configure(double rad, unsigned quality)
{
    plot = 0;
    radius_ = rad;
    quality_ = quality;
    oldstate_ = GL_FALSE;

    gluQuadricDrawStyle(hat, GLU_FILL);
    gluQuadricNormals(hat, GLU_SMOOTH);
    gluQuadricOrientation(hat, GLU_OUTSIDE);
    gluQuadricDrawStyle(disk, GLU_FILL);
    gluQuadricNormals(disk, GLU_SMOOTH);
    gluQuadricOrientation(disk, GLU_OUTSIDE);
}

void Cone::draw(Qwt3D::Triple const &pos)
{
    RGBA rgba = (*plot->dataColor())(pos);
    glColor4d(rgba.r, rgba.g, rgba.b, rgba.a);

    GLint mode;
    glGetIntegerv(GL_MATRIX_MODE, &mode);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    glTranslatef(pos.x, pos.y, pos.z);

    gluCylinder(hat, 0.0, radius_, radius_ * 2, quality_, 1);
    glTranslatef(0, 0, radius_ * 2);
    gluDisk(disk, 0.0, radius_, quality_, 1);

    glPopMatrix();
    glMatrixMode(mode);
}

/////////////////////////////////////////////////////////////////
//
//   Arrow
//
/////////////////////////////////////////////////////////////////

Arrow::Arrow()
{
    hat = gluNewQuadric();
    disk = gluNewQuadric();
    base = gluNewQuadric();
    bottom = gluNewQuadric();

    gluQuadricDrawStyle(hat, GLU_FILL);
    gluQuadricNormals(hat, GLU_SMOOTH);
    gluQuadricOrientation(hat, GLU_OUTSIDE);
    gluQuadricDrawStyle(disk, GLU_FILL);
    gluQuadricNormals(disk, GLU_SMOOTH);
    gluQuadricOrientation(disk, GLU_OUTSIDE);
    gluQuadricDrawStyle(base, GLU_FILL);
    gluQuadricNormals(base, GLU_SMOOTH);
    gluQuadricOrientation(base, GLU_OUTSIDE);
    gluQuadricDrawStyle(bottom, GLU_FILL);
    gluQuadricNormals(bottom, GLU_SMOOTH);
    gluQuadricOrientation(bottom, GLU_OUTSIDE);

    configure(3, 0.4, 0.06, 0.02);
}

Arrow::~Arrow()
{
    gluDeleteQuadric(hat);
    gluDeleteQuadric(disk);
    gluDeleteQuadric(base);
    gluDeleteQuadric(bottom);
}

/**
\param segs number of faces for the fields arrows (see the gallery for examples)
\param relconelength see picture
\param relconerad see picture
\param relstemrad see picture
\image html arrowanatomy.png
*/
void Arrow::configure(int segs, double relconelength, double relconerad, double relstemrad)
{
    plot = 0;
    segments_ = segs;
    oldstate_ = GL_FALSE;
    rel_cone_length = relconelength;
    rel_cone_radius = relconerad;
    rel_stem_radius = relstemrad;
}

void Arrow::draw(Qwt3D::Triple const &pos)
{
    Triple end = top_;
    Triple beg = pos;
    Triple vdiff = end - beg;
    double length = vdiff.length();
    glColor4d(rgba_.r, rgba_.g, rgba_.b, rgba_.a);

    double radius[2];
    radius[0] = rel_cone_radius * length;
    radius[1] = rel_stem_radius * length;

    GLint mode;
    glGetIntegerv(GL_MATRIX_MODE, &mode);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    Triple axis;
    double phi = calcRotation(axis, FreeVector(beg, end));

    glTranslatef(beg.x, beg.y, beg.z);
    glRotatef(phi, axis.x, axis.y, axis.z);

    double baseheight = (1 - rel_cone_length) * length;

    glTranslatef(0, 0, baseheight);

    gluCylinder(hat, radius[0], 0.0, rel_cone_length * length, segments_, 1);
    gluDisk(disk, radius[1], radius[0], segments_, 1);

    glTranslatef(0, 0, -baseheight);

    gluCylinder(base, radius[1], radius[1], baseheight, segments_, 1);
    gluDisk(disk, 0, radius[1], segments_, 1);

    glPopMatrix();
    glMatrixMode(mode);
}

//! transform a vector on the z axis with length |beg-end|, to get them in coincidence with the vector(beg,end)
/**
        \return Angle in degree to rotate
        \param axis   The axis to rotate around
        \param beg    result vector base point
        \param end    result vector top point
*/
double Arrow::calcRotation(Triple &axis, FreeVector const &vec)
{

    Triple end = vec.top;
    Triple beg = vec.base;

    Triple firstbeg(0.0, 0.0, 0.0);
    Triple firstend(0.0, 0.0, (end - beg).length());

    Triple first = firstend - firstbeg;
    first.normalize();

    Triple second = end - beg;
    second.normalize();

    axis = normalizedcross(first, second);
    double cosphi = dotProduct(first, second);

    return 180 * acos(cosphi) / Qwt3D::PI;
}
