/****************************************************************************
* MeshLab                                                           o o     *
* A versatile mesh processing toolbox                             o     o   *
*                                                                _   O  _   *
* Copyright(C) 2005                                                \/)\/    *
* Visual Computing Lab                                            /\/|      *
* ISTI - Italian National Research Council                           |      *
*                                                                    \      *
* All rights reserved.                                                      *
*                                                                           *
* This program is free software; you can redistribute it and/or modify      *
* it under the terms of the GNU General Public License as published by      *
* the Free Software Foundation; either version 2 of the License, or         *
* (at your option) any later version.                                       *
*                                                                           *
* This program is distributed in the hope that it will be useful,           *
* but WITHOUT ANY WARRANTY; without even the implied warranty of            *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)          *
* for more details.                                                         *
*                                                                           *
****************************************************************************/

#include <math.h>
#include <limits>
#include <stdlib.h>
#include "decorate_background.h"
#include <wrap/gl/addons.h>

#include <meshlab/glarea.h>
#include <common/GLExtensionsManager.h>
#include <common/plugins/plugin_manager.h>


using namespace vcg;

QString DecorateBackgroundPlugin::decorationName(ActionIDType id) const
{
    switch (id)
    {
    case DP_SHOW_CUBEMAPPED_ENV:  return tr("Cube mapped background");
    case DP_SHOW_GRID:            return tr("Background Grid");
    }
    assert(0);
    return QString();
}

QString DecorateBackgroundPlugin::decorationInfo(ActionIDType id) const
{
    switch(id)
    {
    case DP_SHOW_CUBEMAPPED_ENV :  return tr("Draws a customizable cube mapped background that is sync with trackball rotation");
    case DP_SHOW_GRID :            return tr("Draws a gridded background that can be used as a reference.");
    }
    assert(0);
    return QString();
}

QString DecorateBackgroundPlugin::pluginName() const
{
	return "DecorateBackGround";
}

void DecorateBackgroundPlugin::initGlobalParameterList(const QAction* action, RichParameterList &parset)
{
    switch(ID(action))
    {
    case DP_SHOW_CUBEMAPPED_ENV :
        break;
    case DP_SHOW_GRID :
      parset.addParam(RichFloat(BoxRatioParam(),1.2f,"Box Ratio","The size of the grid around the object w.r.t. the bbox of the object"));
      parset.addParam(RichFloat(GridMajorParam(),10.0f,"Major Spacing",""));
      parset.addParam(RichFloat(GridMinorParam(),1.0f,"Minor Spacing",""));
      parset.addParam(RichBool(GridBackParam(),true,"Front grid culling",""));
      parset.addParam(RichBool(ShowShadowParam(),false,"Show silhouette",""));
      parset.addParam(RichColor(GridColorBackParam(), QColor(163,116,35,255), "Back Grid Color", ""));
      parset.addParam(RichColor(GridColorFrontParam(),QColor(22,139,119,255),"Front grid Color",""));
      parset.addParam(RichFloat(GridBaseLineWidthParam(),1.0f,"Line Width","The width of the lines of the grid"));
        break;
    }
}

bool DecorateBackgroundPlugin::startDecorate(const QAction * action, MeshDocument &/*m*/, const RichParameterList * parset, GLArea * gla)
{
    if (!GLExtensionsManager::initializeGLextensions_notThrowing()) {
        return false;
    }
    switch(ID(action))
    {
    case DP_SHOW_CUBEMAPPED_ENV :
		if(parset->hasParameter(CubeMapPathParam()) == false)
			qDebug("CubeMapPath was not set!!!");
        cubemapFileName = parset->getString(CubeMapPathParam());
        break;
    case DP_SHOW_GRID:
        /*QMetaObject::Connection aaa =*/ connect(gla, SIGNAL(transmitShot(QString, Shotm)), this, SLOT(setValue(QString, Shotm)));
        /*QMetaObject::Connection bbb =*/ connect(this, SIGNAL(askViewerShot(QString)), gla, SLOT(sendViewerShot(QString)));
        break;
    }
    return true;
}

void DecorateBackgroundPlugin::decorateDoc(const QAction* a, MeshDocument &m, const RichParameterList * parset, GLArea * gla, QPainter *, GLLogStream &)
{
    static QString lastname("uninitialized");
    switch(ID(a))
    {
    case DP_SHOW_CUBEMAPPED_ENV :
        {
            if(!cm.IsValid() || (lastname != cubemapFileName ) )
            {
                qDebug( "Current CubeMapPath Dir: %s ",qUtf8Printable(cubemapFileName));
                GLExtensionsManager::initializeGLextensions();
                bool ret = cm.Load(qUtf8Printable(cubemapFileName));
                lastname=cubemapFileName;
                if(! ret ) return;
                //QMessageBox::warning(gla,"Cubemapped background decoration","Warning unable to load cube map images: " + cubemapFileName );
                cm.radius=10;
            }
            if(!cm.IsValid()) return;

            Matrix44f tr;
            glGetv(GL_MODELVIEW_MATRIX,tr);
            // Remove the translation from the current matrix by simply padding the last column of the matrix
            tr.SetColumn(3,Point4f(0,0,0,1.0));
            //Remove the scaling from the the current matrix by adding an inverse scaling matrix
            float scale = 1.0/pow(tr.Determinant(),1.0f/3.0f);
            Matrix44f Scale;
            Scale.SetDiagonal(scale);
            tr=tr*Scale;

            glMatrixMode(GL_PROJECTION);
            glPushMatrix();
            glMatrixMode(GL_MODELVIEW);
            glPushMatrix();
            cm.DrawEnvCube(tr);
            glPopMatrix();
            glMatrixMode(GL_PROJECTION);
            glPopMatrix();
            glMatrixMode(GL_MODELVIEW);
        } 
        break;
    case DP_SHOW_GRID :
        {
            emit this->askViewerShot("backGrid");

            Scalarm scaleBB = parset->getFloat(BoxRatioParam());
            Scalarm majorTick = fabs(parset->getFloat(GridMajorParam()));
            Scalarm minorTick = fabs(parset->getFloat(GridMinorParam()));
            bool backFlag = parset->getBool(GridBackParam());
            bool shadowFlag = parset->getBool(ShowShadowParam());
            Color4b backColor = parset->getColor4b(GridColorBackParam());
            Color4b frontColor = parset->getColor4b(GridColorFrontParam());
            Scalarm baseLineWidth = parset->getFloat(GridBaseLineWidthParam());
            Box3m bb = m.bbox();
            Scalarm scalefactor = std::max(0.1, (scaleBB - 1.0));
            bb.Offset((bb.max - bb.min)*(scalefactor/2.0));

			// minortick should never be more than majortick
			if (minorTick > majorTick)
				minorTick = majorTick;

			// check if user asked for a grid that is too dense
			// if more than 100-200k ticks, the rendering will slow down too much and crash on some drivers
			int ticks = ((bb.Dim()[0] + bb.Dim()[1] + bb.Dim()[2]) / minorTick) + ((bb.Dim()[0] + bb.Dim()[1] + bb.Dim()[2]) / majorTick);
			if (ticks > 200000)
			{
				if (log10(bb.Diag()) > 0.0)
					majorTick = pow(10, floor(log10(bb.Diag()) -1));
				else
					majorTick = pow(10, floor(log10(bb.Diag()) + 1));
				minorTick = majorTick / 2.0;
			}

            MLSceneGLSharedDataContext* shared = NULL;
            QGLContext* cont = NULL;
            if ((gla != NULL) && (gla->mvc() != NULL))
            {
                cont = gla->context();
                shared = gla->mvc()->sharedDataContext();
            }

            DrawGriddedCube(shared,cont,*m.mm(), bb, majorTick, minorTick, backFlag, shadowFlag, backColor, frontColor,baseLineWidth);
        } 
        break;
    }
}


// Note minG/maxG is wider than minP/maxP.
void DrawGridPlane(int axis,
    int side, // 0 is min 1 is max
    Point3m minP, Point3m maxP,
    Point3m minG, Point3m maxG, // the box with vertex positions snapped to the grid (enlarging it).
    float majorTick, float minorTick,
    Color4b lineColor, float baseLineWidth=1.0f)
{
    int xAxis = (1+axis)%3;
    int yAxis = (2+axis)%3;
    int zAxis = (0+axis)%3; // the axis perpendicular to the plane we want to draw (e.g. if i want to draw plane xy I write '2')

    Color4b majorColor=lineColor;
    Color4b minorColor;
    Color4b axisColor;
    minorColor[0] = std::min(255.0, lineColor[0] * 2.0); // minorcolor is 2 times brighter and half solid w.r.t. majorcolor
    minorColor[1] = std::min(255.0, lineColor[1] * 2.0);
    minorColor[2] = std::min(255.0, lineColor[2] * 2.0);
    minorColor[3] = std::min(127.0, lineColor[3] / 2.0);
    axisColor[0] = lineColor[0] * 0.66; // axiscolor is 2/3 darker w.r.t. majorcolor
    axisColor[1] = lineColor[1] * 0.66;
    axisColor[2] = lineColor[2] * 0.66;
    axisColor[3] = 255;

    // We draw orizontal and vertical lines onto the XY plane snapped with the major ticks
    Point3m p1,p2,p3,p4;
    p1[zAxis] = p2[zAxis] = p3[zAxis] = p4[zAxis] = side ? maxG[zAxis] : minG[zAxis];

    float aMin,aMax,bMin,bMax;

    p1[yAxis] = minG[yAxis];
    p2[yAxis] = maxG[yAxis];
    p3[xAxis] = minG[xAxis];
    p4[xAxis] = maxG[xAxis];
    aMin = minG[xAxis];
    aMax = maxG[xAxis];
    bMin = minG[yAxis];
    bMax = maxG[yAxis];

    glLineWidth(baseLineWidth*0.5f);
    glColor(minorColor);
    glBegin(GL_LINES);
    for (float alpha = aMin; alpha <= aMax; alpha += minorTick)
    {
        p1[xAxis] = p2[xAxis] = alpha;
        glVertex(p1); glVertex(p2);
    }
    for (float alpha = bMin; alpha <= bMax; alpha += minorTick)
    {
        p3[yAxis] = p4[yAxis] = alpha;
        glVertex(p3); glVertex(p4);
    }
    glEnd();

    glLineWidth(baseLineWidth*1.0f);
    glColor(majorColor);
    glBegin(GL_LINES);
    for (float alpha = aMin; alpha <= aMax; alpha += majorTick)
    {
        p1[xAxis] = p2[xAxis] = alpha;
        glVertex(p1); glVertex(p2);
    }
    for (float alpha = bMin; alpha <= bMax; alpha += majorTick)
    {
        p3[yAxis] = p4[yAxis] = alpha;
        glVertex(p3); glVertex(p4);
    }
    glEnd();

    // Draw the axis
    glColor(axisColor);
    glLineWidth(baseLineWidth*1.5f);
    glBegin(GL_LINES);
    if(minP[xAxis]*maxP[xAxis] <0 )
    {
        p1[yAxis]=minG[yAxis];
        p2[yAxis]=maxG[yAxis];
        p1[xAxis]=p2[xAxis]=0;
        glVertex(p1);
        glVertex(p2);
    }
    if(minP[yAxis]*maxP[yAxis] <0 )
    {
        p1[xAxis]=minG[xAxis];
        p2[xAxis]=maxG[xAxis];
        p1[yAxis]=p2[yAxis]=0;
        glVertex(p1);
        glVertex(p2);
    }
    glEnd();
}

/* return true if the side of a box is front facing with respet of the give viewpoint.
side 0, axis i == min on than i-th axis
side 1, axis i == min on than i-th axis
questo capita se il prodotto scalare tra il vettore normale entro della faccia
*/
bool FrontFacing(Point3m viewPos,
    int axis,
    int side,
    Point3m minP, 
    Point3m maxP)
{
    assert (side==0 || side ==1);
    assert (axis>=0 && axis < 3);
    Point3m N(0,0,0);
    Point3m C = (minP+maxP)/2.0;

    if(side == 1) {
        C[axis] = maxP[axis];
        N[axis]=-1;
    }

    if(side == 0) {
        C[axis] = minP[axis];
        N[axis]=1;
    }
    Point3m vpc = viewPos-C;
    //  qDebug("FaceCenter %f %f %f - %f %f %f",C[0],C[1],C[2],N[0],N[1],N[2]);
    //  qDebug("VPC        %f %f %f",vpc[0],vpc[1],vpc[2]);
    return vpc*N > 0;
}

void DrawFlatMesh(MLSceneGLSharedDataContext* shared,QGLContext* cont,MeshModel &m, int axis, int side, Point3m minG, Point3m maxG)
{
    if ((shared == NULL) || (cont == NULL))
        return;
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    glDisable(GL_LIGHTING);
    glPushMatrix();
    Point3m trans = side?maxG:minG;
    Point3m scale(1.0f,1.0f,1.0);
    trans[(axis+1)%3]=0;
    trans[(axis+2)%3]=0;
    scale[axis]=0;
    glTranslate(trans);
    glScale(scale);
        shared->draw(m.id(),cont);
    //    m.render(GLW::DMFlat,GLW::CMNone,GLW::TMNone);
    glPopMatrix();
    glPopAttrib();
}

void DecorateBackgroundPlugin::DrawGriddedCube(MLSceneGLSharedDataContext* shared, QGLContext* cont, MeshModel &m, const Box3m &bb, Scalarm majorTick, Scalarm minorTick, 
                                               bool backCullFlag, bool shadowFlag, Color4b frontColor, Color4b backColor, float baseLineWidth)
{
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    Point3m minP,maxP, minG,maxG;
    minP=bb.min;maxP=bb.max;

    // Make the box well rounded wrt to major tick (only increase)
    for(int i=0;i<3;++i) // foreach axis
    {
        if (minP[i] == 0) 
            minG[i] = -majorTick;
        else
            minG[i] = minP[i] + fmod(fabs(minP[i]), majorTick) - majorTick;

        if (maxP[i] == 0) 
            maxG[i] =  majorTick;
        else
            maxG[i] = maxP[i] - fmod(fabs(maxP[i]), majorTick) + majorTick;
    }

    glDisable(GL_LIGHTING);
    glDisable(GL_COLOR_MATERIAL);
    glColor3f(0.8f,0.8f,0.8f);
    glEnable(GL_LINE_SMOOTH);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
    glDepthMask(GL_FALSE);
    Point3m viewPos = Point3m::Construct(this->curShot.GetViewPoint());
    //    qDebug("BG Grid MajorTick %4.2f MinorTick %4.2f  ## camera pos %7.3f %7.3f %7.3f", majorTick, minorTick, viewPos[0],viewPos[1],viewPos[2]);
    //    qDebug("BG Grid boxF %7.3f %7.3f %7.3f # %7.3f %7.3f %7.3f",minP[0],minP[1],minP[2],maxP[0],maxP[1],maxP[2]);
    //    qDebug("BG Grid boxG %7.3f %7.3f %7.3f # %7.3f %7.3f %7.3f",minG[0],minG[1],minG[2],maxG[0],maxG[1],maxG[2]);
    for (int ii=0;ii<3;++ii)
    {
        for(int jj=0;jj<2;++jj)
        {
            bool front = FrontFacing(viewPos,ii,jj,minP,maxP);
            if( front || !backCullFlag)
            {
                DrawGridPlane(ii,jj,minP,maxP,minG,maxG,majorTick,minorTick,front?frontColor:backColor,baseLineWidth);
                if(shadowFlag) 
                {
                    glPushAttrib(GL_COLOR_BUFFER_BIT);
                    glBlendColor(1.0f,1.0f,1.0f,0.4f);
                    glBlendFunc(GL_CONSTANT_COLOR, GL_ONE);
                    DrawFlatMesh(shared,cont,m,ii,jj,minG,maxG);
                    glPopAttrib();
                }
            }
        }
    }
    glDisable(GL_BLEND);
    glPopAttrib();
}

void  DecorateBackgroundPlugin::setValue(QString, Shotm val)
{
    curShot=val;
}

MESHLAB_PLUGIN_NAME_EXPORTER(DecorateBackgroundPlugin)
