/*
* Author: Andrew Robberts
*
* Copyright (C) 2003 Atomic Blue (info@planeshift.it, http://www.atomicblue.org)
*
*
* 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 (version 2 of the License)
* 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 for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*
*/

#include <psconfig.h>

#include <csutil/xmltiny.h>
#include <iengine/engine.h>
#include <iengine/material.h>
#include <iengine/mesh.h>
#include <iengine/movable.h>
#include <iengine/camera.h>
#include <cstool/csview.h>
#include <iengine/region.h>
#include <imesh/objmodel.h>
#include <csutil/flags.h>
#include <csgeom/tri.h>

#include "effects/pseffectobjquad.h"
#include "effects/pseffectanchor.h"

#include "util/log.h"
#include "util/pscssetup.h"


psEffectObjQuad::psEffectObjQuad(iView * parentView)
               : psEffectObj(parentView)
{
    meshControl.AttachNew(new MeshAnimControl(this));        
}

psEffectObjQuad::~psEffectObjQuad()
{

}

bool psEffectObjQuad::Load(iDocumentNode *node)
{
    // get the attributes
    name = "";
    materialName = "";
    xCells = 1;
    yCells = 1;
    csRef<iDocumentAttributeIterator> attribIter = node->GetAttributes();
    while (attribIter->HasNext())
    {
        csRef<iDocumentAttribute> attr = attribIter->Next();
        csString attrName = attr->GetName();
        attrName.Downcase();
        if (attrName == "name")
            name = attr->GetValue();
        else if (attrName == "material")
            materialName = attr->GetValue();
        else if (attrName == "xcells")
            xCells = attr->GetValueAsInt();
        else if (attrName == "ycells")
            yCells = attr->GetValueAsInt();
    }
    if (name == "")
    {
        csReport(psCSSetup::object_reg, CS_REPORTER_SEVERITY_ERROR, "planeshift_effects", "Attempting to create an effect obj with no name.\n");
        return false;
    }
    if (materialName == "")
    {
        csReport(psCSSetup::object_reg, CS_REPORTER_SEVERITY_ERROR, "planeshift_effects", "Attempting to create an effect obj without a material.\n");
        return false;
    }
    
    if (!psEffectObj::Load(node))
        return false;
    
    return PostSetup();
}

bool psEffectObjQuad::Render(const csVector3 &up)
{
    static unsigned long nextUniqueID = 0;
    csString effectID = "effect_quad_";
    effectID += nextUniqueID++;

    // create a mesh wrapper from the factory we just created
    mesh = engine->CreateMeshWrapper(meshFact, effectID.GetData());

    // do the up vector
    objUp = up;
    csReversibleTransform rt;
    rt.LookAt(csVector3(up.x, up.z, up.y), csVector3(0,2,1));
    matUp = rt.GetT2O();
    matBase = matUp;

    // common flags
    mesh->GetFlags().Set(CS_ENTITY_NOHITBEAM);
    mesh->SetZBufMode(zFunc);
    mesh->SetRenderPriority(priority);

    // disable culling
    mesh->GetMeshObject()->GetObjectModel()->SetPolygonMeshViscull(0);

    // obj specific
    genState = SCF_QUERY_INTERFACE(mesh->GetMeshObject(), iGeneralMeshState);
    genState->SetLighting(false);

    if (mixmode != CS_FX_ALPHA)
      mesh->GetMeshObject()->SetMixMode(mixmode);  // to check
      //genState->SetMixMode(mixmode); was not working with latest CS

    genState->SetAnimationControl((iGenMeshAnimationControl *)meshControl);

    // initialize the data we'll pass to the genmesh animation control
    vert[0].Set( 0.5f, 0,  0.5f);
    vert[1].Set(-0.5f, 0,  0.5f);
    vert[2].Set(-0.5f, 0, -0.5f);
    vert[3].Set( 0.5f, 0, -0.5f);

    for (int a=0; a<4; ++a)
        colour[a].Set(1, 1, 1);

    return true;
}

bool psEffectObjQuad::Update(csTicks elapsed)
{
    if (!anchor || !anchor->IsReady()) // wait for anchor to be ready
        return true;

    if (!psEffectObj::Update(elapsed))
        return false;

    int cell = 0;
    if (keyFrames->Length() > 0)
    {
        // COLOUR
        csVector3 lerpColour = LERP_VEC_KEY(KA_COLOUR);
        float lerpAlpha = LERP_KEY(KA_ALPHA);
        for (int a=0; a<4; ++a)
            colour[a].Set(lerpColour.x, lerpColour.y, lerpColour.z, lerpAlpha);

        // CELL
        cell = (int)keyFrames->Get(currKeyFrame)->actions[psEffectObjKeyFrame::KA_CELL];
    }    

    float u = (float)(cell % xCells) / (float)xCells;
    float v = (float)((int)(cell / yCells)) / (float)yCells;
    float width = 1.0f / (float)xCells;
    float height = 1.0f / (float)yCells;

    texel[0].Set(u+width, v);
    texel[1].Set(u,       v);
    texel[2].Set(u,       v+height);
    texel[3].Set(u+width, v+height);

    return true;
}

psEffectObj * psEffectObjQuad::Clone() const
{
    psEffectObjQuad * newObj = new psEffectObjQuad(view);
    CloneBase(newObj);

    // quad specific
    newObj->xCells = xCells;
    newObj->yCells = yCells;
    
    return newObj;
}

bool psEffectObjQuad::PostSetup()
{

    static unsigned int uniqueID = 0;
    csString facName = "effect_quad_fac_";
    facName += uniqueID++;
    
    meshFact = engine->CreateMeshFactory ("crystalspace.mesh.object.genmesh", facName.GetData());
    region->Add(meshFact->QueryObject());
    
    // create the actual sprite3d data
    iMeshObjectFactory * fact = meshFact->GetMeshObjectFactory();
    csRef<iGeneralFactoryState> facState = SCF_QUERY_INTERFACE(fact, iGeneralFactoryState);
    if (!facState)
    {
        csReport(psCSSetup::object_reg, CS_REPORTER_SEVERITY_ERROR, "planeshift_effects", "Couldn't create genmesh: %s\n", name.GetData());
        return false;
    }

    // setup the material
    csRef<iMaterialWrapper> mat = region->FindMaterial(materialName);
    if (!mat)
    {
        csReport(psCSSetup::object_reg, CS_REPORTER_SEVERITY_ERROR, "planeshift_effects", "Couldn't find effect material: %s\n", materialName.GetData());
        return false;
    }
    fact->SetMaterialWrapper(mat);

    facState->SetVertexCount(4);

    // we have to set the vertices so that the quad actually gets rendered
    facState->GetVertices()[0].Set( 0.5f, 0,  0.5f);
    facState->GetVertices()[1].Set(-0.5f, 0,  0.5f);
    facState->GetVertices()[2].Set(-0.5f, 0, -0.5f);
    facState->GetVertices()[3].Set( 0.5f, 0, -0.5f);

    facState->GetNormals()[0].Set(0,1,0);
    facState->GetNormals()[1].Set(0,1,0);
    facState->GetNormals()[2].Set(0,1,0);
    facState->GetNormals()[3].Set(0,1,0);

    facState->SetTriangleCount(4);

    facState->GetTriangles()[0].Set(0, 2, 1);
    facState->GetTriangles()[1].Set(0, 2, 3);
    facState->GetTriangles()[2].Set(2, 0, 1);
    facState->GetTriangles()[3].Set(2, 0, 3);

    animLength = 10;
    if (keyFrames->Length() > 0)
        animLength += keyFrames->Get(keyFrames->Length()-1)->time;

    return true;
}

const csVector3 * psEffectObjQuad::MeshAnimControl::UpdateVertices(csTicks current, const csVector3 * verts, int num_verts, uint32 version_id)
{
    return parent->vert;
}

const csVector2 * psEffectObjQuad::MeshAnimControl::UpdateTexels(csTicks current, const csVector2 * texels, int num_texels, uint32 version_id)
{
    return parent->texel;
}

const csColor4 * psEffectObjQuad::MeshAnimControl::UpdateColors(csTicks current, const csColor4 * colors, int num_colors, uint32 version_id)
{
    return parent->colour;
}
