/*
 * Author: Andrew Mann
 *
 * Copyright (C) 2004 PlaneShift Team (info@planeshift.it,
 * http://www.planeshift.it)
 *
 * 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 "cssysdef.h"
#include "ivideo/txtmgr.h"
#include "ivideo/material.h"
#include "ivaria/reporter.h"
#include "ivaria/stdrep.h"
#include "iutil/document.h"

#include "pv_helpers.h"


// Static member initialization
iObjectRegistry *DirectionArrow::object_reg=NULL;
iEngine *DirectionArrow::engine=NULL;
iMaterialWrapper *DirectionArrow::arrow_material_wrapper=NULL;
iShader *DirectionArrow::arrow_shader=NULL;


const char *DirectionArrow::shader_string = 
"<shader type=\"xmlshader\" name=\"directionarrow_color_shader\"><technique priority=\"100\"><pass>"
"<buffer name=\"vertices\" destination=\"position\" /><buffer name=\"colors\" destination=\"primary color\" />"
"<fp plugin=\"glfixed\"><fixedfp><layer>"
"<colorsource num=\"0\" source=\"primary color\" modifier=\"color\"/><coloroperation operation=\"replace\" />"
"</layer></fixedfp></fp>"
"</pass></technique></shader>";




DirectionArrow::DirectionArrow(iObjectRegistry* o_reg,iSector *sector, csVector3 position,csVector3 direction,float length)
: loc_position(position), loc_direction(direction)
{
    loc_sector=sector;
    loc_length=length;

    if (object_reg == NULL)
        object_reg=o_reg;

    if (!engine)
    {
        csRef<iEngine> temp_engine;
        temp_engine = CS_QUERY_REGISTRY (object_reg, iEngine);
        engine=temp_engine;
        engine->IncRef();
    }

    arrow_factory_wrapper=NULL;
    arrow_mesh=NULL;
    shown=false;
}


DirectionArrow::~DirectionArrow()
{
    if (engine)
    {
        engine->RemoveObject(arrow_mesh_wrapper);
        engine->RemoveObject(arrow_factory_wrapper);
    }
}

bool DirectionArrow::Initialize()
{
    iMovable *arrow_movable;

    if (!object_reg || !engine)
        return false;
    if (!InitializeArrowFactory())
        return false;
    
    if (arrow_mesh)
        return true;

    arrow_mesh=arrow_factory_wrapper->GetMeshObjectFactory()->NewInstance();
    arrow_mesh_wrapper=engine->CreateMeshWrapper(arrow_mesh,NULL,loc_sector,loc_position);

    arrow_movable=arrow_mesh_wrapper->GetMovable();

    csVector3 other_vector;
    if (loc_direction.x  > -0.00001f && loc_direction.x  < 0.00001f)
        other_vector.Set(1.0f,0.0f,0.0f);
    else
        other_vector.Set(0.0f,1.0f,0.0f);

    csVector3 x_axis,z_axis;
    x_axis=loc_direction % other_vector;
    z_axis=loc_direction % x_axis;

    loc_direction.Normalize();
    x_axis.Normalize();
    z_axis.Normalize();

/*
    csMatrix3 rot_matrix(
        x_axis.x,x_axis.y,x_axis.z,
        loc_direction.x,loc_direction.y,loc_direction.z,
        z_axis.x,z_axis.y,z_axis.z);
*/
    csMatrix3 rot_matrix(
        x_axis.x,loc_direction.x,z_axis.x,
        x_axis.y,loc_direction.y,z_axis.y,
        x_axis.z,loc_direction.z,z_axis.z
        );


    arrow_movable->Transform(rot_matrix);

    arrow_movable->UpdateMove();
    engine->PrepareMeshes();

    shown=true;

    return true;
}

void DirectionArrow::MoveTo(csVector3 position)
{
    iMovable *arrow_movable;

    if (!object_reg || !engine)
        return;
   
    if (!arrow_mesh)
        return;

    arrow_movable=arrow_mesh_wrapper->GetMovable();
    if (!arrow_movable)
        return;

    loc_position=position;

    arrow_movable->SetPosition(position);
    arrow_movable->UpdateMove();
}

void DirectionArrow::PointTo(csVector3 direction)
{
    iMovable *arrow_movable;


    if (!object_reg || !engine)
        return;
   
    if (!arrow_mesh)
        return;

    arrow_movable=arrow_mesh_wrapper->GetMovable();
    if (!arrow_movable)
        return;

    loc_direction=direction;

    csVector3 other_vector;
    if (loc_direction.x  > -0.00001f && loc_direction.x  < 0.00001f)
        other_vector.Set(1.0f,0.0f,0.0f);
    else
        other_vector.Set(0.0f,1.0f,0.0f);

    csVector3 x_axis,z_axis;
    x_axis=loc_direction % other_vector;
    z_axis=loc_direction % x_axis;

    loc_direction.Normalize();
    x_axis.Normalize();
    z_axis.Normalize();


    csMatrix3 rot_matrix(
        x_axis.x,loc_direction.x,z_axis.x,
        x_axis.y,loc_direction.y,z_axis.y,
        x_axis.z,loc_direction.z,z_axis.z
        );


    arrow_movable->SetTransform(rot_matrix);

    arrow_movable->UpdateMove();
}


void DirectionArrow::Hide()
{
    if (!shown)
        return;
    arrow_mesh_wrapper->GetMovable()->ClearSectors();
    arrow_mesh_wrapper->PlaceMesh();
    shown=false;
}

void DirectionArrow::Show()
{
    if (shown)
        return;
    arrow_mesh_wrapper->GetMovable()->SetSector(loc_sector);
    arrow_mesh_wrapper->PlaceMesh();

    shown=true;
}

/*
       D


 9A  56  78 BC






     12  34


 verts =
0   -0.1, 0 , 0.1
1   -0.1, 0 , -0.1
2   0.1, 0 , -0.1
3   0.1, 0 , 0.1
4   -0.1, 0.8 , 0.1
5   -0.1, 0.8 , -0.1
6   0.1, 0.8 , -0.1
7   0.1, 0.8 , 0.1
8   -0.2, 0.8, 0.2
9   -0.2, 0.8, -0.2
10  0.2, 0.8, -0.2
11  0.2, 0.8, 0.2
12  0.0, 1.0, 0.0

(top view)
1      2


0      3

(top view)
5      6


4      7

(top view)
9      10

   12

8      11



tri indices = 
// bottom
0  3,1,0
1  2,1,3
// front
2  0,4,3
3  4,7,3
// left
4  1,5,0
5  0,5,4
// rear
6  2,6,1
7  1,6,5
// right
8  3,7,2
9  2,7,6
// bottom of arrowhead
10 9,8,11
11 10,9,11
// front face
12 8,12,11
// left face
13 9,12,8
// rear face
14 10,12,9
// right face
15 11,12,10



*/



bool DirectionArrow::InitializeArrowFactory()
{
    int i;
    csVector3 *vert_array;
    csTriangle *tri_array;
    csColor *color_array;

    // Only needs to be setup once
    if (arrow_factory_wrapper)
        return true;

    if (!InitializeArrowMaterial())
        return false;

    csRef<iMeshFactoryWrapper> temp_factory = engine->CreateMeshFactory("crystalspace.mesh.object.genmesh","DirectionArrowFactory");
    arrow_factory_wrapper=temp_factory;
    if (!arrow_factory_wrapper)
        return false;
    arrow_factory_wrapper->IncRef();

    iMeshObjectFactory *arrow_factory=arrow_factory_wrapper->GetMeshObjectFactory();
    
    csRef<iGeneralFactoryState> factory_state = SCF_QUERY_INTERFACE(arrow_factory,iGeneralFactoryState);

    factory_state->SetManualColors(true);
    factory_state->SetLighting(false);
    factory_state->SetVertexCount(13);
    factory_state->SetTriangleCount(16);

    vert_array=factory_state->GetVertices();

    vert_array[0].Set(-0.005f,0.0f,0.005f);
    vert_array[1].Set(-0.005f,0.0f,-0.005f);
    vert_array[2].Set(0.005f,0.0f,-0.005f);
    vert_array[3].Set(0.005f,0.0f,0.005f);
    // Vertices below this point should have their y value scaled by length
    vert_array[4].Set(-0.005f,0.8f * loc_length,0.005f);
    vert_array[5].Set(-0.005f,0.8f * loc_length,-0.005f);
    vert_array[6].Set(0.005f,0.8f * loc_length,-0.005f);
    vert_array[7].Set(0.005f,0.8f * loc_length,0.005f);
    vert_array[8].Set(-0.02f,0.8f * loc_length,0.02f);
    vert_array[9].Set(-0.02f,0.8f * loc_length,-0.02f);
    vert_array[10].Set(0.02f,0.8f * loc_length,-0.02f);
    vert_array[11].Set(0.02f,0.8f * loc_length,0.02f);
    vert_array[12].Set(0.0f,1.0f * loc_length,0.0f);
    
    tri_array=factory_state->GetTriangles();
    tri_array[0].Set(3,1,0);
    tri_array[1].Set(2,1,3);
    tri_array[2].Set(0,4,3);
    tri_array[3].Set(4,7,3);
    tri_array[4].Set(1,5,0);
    tri_array[5].Set(0,5,4);
    tri_array[6].Set(2,6,1);
    tri_array[7].Set(1,6,5);
    tri_array[8].Set(3,7,2);
    tri_array[9].Set(2,7,6);
    tri_array[10].Set(9,8,11);
    tri_array[11].Set(10,9,11);
    tri_array[12].Set(8,12,11);
    tri_array[13].Set(9,12,8);
    tri_array[14].Set(10,12,9);
    tri_array[15].Set(11,12,10);

    color_array=factory_state->GetColors();
    for (i=0;i<13;i++)
        color_array[i].Set(0.1f,0.8f,0.1f);

    factory_state->SetMaterialWrapper(arrow_material_wrapper);
    factory_state->CalculateNormals();
    factory_state->Invalidate();


    return true;
}

bool DirectionArrow::InitializeArrowMaterial()
{
    csColor transp(0,0,0);
    if (arrow_material_wrapper)
        return true;

    if (!InitializeArrowShader())
        return false;

    // We need an interface to the stringset object to retrieve common global string IDs
    csRef<iStringSet> strings = CS_QUERY_REGISTRY_TAG_INTERFACE (object_reg, "crystalspace.shared.stringset", iStringSet);
    if (!strings)
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "DirectionArrow::InitializeArrowMaterial()","Could not find an iStringSet interface in the available plugins!");
        return false;
    }

    iTextureWrapper *arrow_texture=engine->CreateBlackTexture("DirectionArrowPsuedoTexture",1,1,&transp,CS_TEXTURE_3D);
    csRef<iMaterial> arrow_material=engine->CreateBaseMaterial(arrow_texture);
    arrow_material->SetShader(strings->Request("OR compatibility"),arrow_shader);

    arrow_material_wrapper=engine->GetMaterialList()->NewMaterial(arrow_material, 0);

    engine->PrepareTextures();

    return (arrow_material_wrapper!=NULL);
}

bool DirectionArrow::InitializeArrowShader()
{
    if (arrow_shader!=NULL)
        return true;

    // We need a document system to parse the shader definition
    csRef<iDocumentSystem> docsys (CS_QUERY_REGISTRY(object_reg, iDocumentSystem));
    if (!docsys)
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "DirectionArrow::InitializeArrowShader()","Could not find an iDocumentSystem interface in the available plugins!");
        return false;
    }

    csRef<iDocument> shaderDoc=docsys->CreateDocument();
    shaderDoc->Parse(shader_string);

    // Now we need to get an interface to the shader manager which will allow us to compile and register this shader
    csRef<iShaderManager> shmgr (CS_QUERY_REGISTRY(object_reg, iShaderManager));
    if (!shmgr)
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "DirectionArrow::InitializeArrowShader()","Could not find an iShaderManager interface in the available plugins!");
        return false;
    }

    // And get the XMLShader compiler, which is used for all the shader top level definitions
    csRef<iShaderCompiler> shcom (shmgr->GetCompiler ("XMLShader"));
    
    // Compile the shader XML into a shader object which can later be applied to the material
    csRef<iShader> temp_arrow_shader = shcom->CompileShader (shaderDoc->GetRoot ()->GetNode ("shader"));
    arrow_shader = temp_arrow_shader;
    arrow_shader->IncRef();
    
    return (arrow_shader!=NULL);
}

void DirectionArrow::SetShaftColor(csColor c)
{
    int i;
    csColor *color_array;
    if (!arrow_factory_wrapper)
        return;
    iMeshObjectFactory *arrow_factory=arrow_factory_wrapper->GetMeshObjectFactory();
    csRef<iGeneralFactoryState> factory_state = SCF_QUERY_INTERFACE(arrow_factory,iGeneralFactoryState);

    color_array=factory_state->GetColors();

    for (i=0;i<8;i++)
        color_array[i]=c;

    factory_state->Invalidate();
}

void DirectionArrow::SetHeadColor(csColor c)
{
    int i;
    csColor *color_array;
    if (!arrow_factory_wrapper)
        return;
    iMeshObjectFactory *arrow_factory=arrow_factory_wrapper->GetMeshObjectFactory();
    csRef<iGeneralFactoryState> factory_state = SCF_QUERY_INTERFACE(arrow_factory,iGeneralFactoryState);

    color_array=factory_state->GetColors();

    for (i=8;i<13;i++)
        color_array[i]=c;

    factory_state->Invalidate();

}




SCF_IMPLEMENT_IBASE (CoordinateArrows)
  SCF_IMPLEMENTS_INTERFACE (iMovableListener)
SCF_IMPLEMENT_IBASE_END




CoordinateArrows::CoordinateArrows(iObjectRegistry* o_reg,iSector *sector, csVector3 position,csVector3 vec_y,csVector3 vec_x ,float arrow_offset, float arrow_length)
{
    csVector3 vec_z = vec_x % vec_y;

    vec_x.Normalize();
    vec_y.Normalize();
    vec_z.Normalize();

    offset=arrow_offset;
    length=arrow_length;

    arrow_x=new DirectionArrow(o_reg,sector,position+vec_x * arrow_offset,vec_x,arrow_length);
    arrow_y=new DirectionArrow(o_reg,sector,position+vec_y * arrow_offset,vec_y,arrow_length);
    arrow_z=new DirectionArrow(o_reg,sector,position+vec_z * arrow_offset,vec_z,arrow_length);
}

CoordinateArrows::~CoordinateArrows()
{
    delete arrow_x;
    arrow_x=NULL;
    delete arrow_y;
    arrow_y=NULL;
    delete arrow_z;
    arrow_z=NULL;
}

bool CoordinateArrows::Initialize()
{
  bool success=true;
  if (!arrow_x || !arrow_y || !arrow_z)
      return false;

  success&=arrow_x->Initialize();
  success&=arrow_y->Initialize();
  success&=arrow_z->Initialize();

  arrow_x->SetHeadColor(csColor(1.0,0,0));
  arrow_y->SetHeadColor(csColor(0,0,1.0));
  arrow_z->SetHeadColor(csColor(1.0,0,1.0));

  return success;
}


void CoordinateArrows::Show()
{
    if (arrow_x)
        arrow_x->Show();
    if (arrow_y)
        arrow_y->Show();
    if (arrow_z)
        arrow_z->Show();
}

void CoordinateArrows::Hide()
{
    if (arrow_x)
        arrow_x->Hide();
    if (arrow_y)
        arrow_y->Hide();
    if (arrow_z)
        arrow_z->Hide();

}

void CoordinateArrows::MovableChanged(iMovable* movable)
{
    csVector3 position,vec_x,vec_y,vec_z;
    position=movable->GetFullPosition();
    csReversibleTransform trans=movable->GetFullTransform();
    csMatrix3 trans_mat=trans.GetO2T();

    vec_x=trans_mat.Row1();
    vec_y=trans_mat.Row2();
    vec_z=trans_mat.Row3();

    if (arrow_x)
    {
        arrow_x->MoveTo(position + (vec_x * offset));
        arrow_x->PointTo(vec_x);
    }
    if (arrow_y)
    {
        arrow_y->MoveTo(position + (vec_y * offset));
        arrow_y->PointTo(vec_y);
    }
    if (arrow_z)
    {
        arrow_z->MoveTo(position + (vec_z * offset));
        arrow_z->PointTo(vec_z);
    }


}

/// The movable is about to be destroyed.
void CoordinateArrows::MovableDestroyed(iMovable* movable)
{


}







