#include <sstream>

#include "actor.h"
#include "control.h"
#include "input.h"
#include "renderer.h"

Actor::Actor(){

location=Vector3f(0,0,0);
rotation=Vector3f(0,0,0);
scale=Vector3f(1,1,1);

color=Vector4f(1,1,1,1);

//Shader related
shaderProgram=0;
vertexShader=0;
fragmentShader=0;

shaderProgramName="color";
vertexShaderFileName="shaders/color.vert";                   //holds the file name
fragmentShaderFileName="shaders/color.frag";                 //holds the file name
vertexShaderFile="";               //actually holds the whole file
fragmentShaderFile="";             //actually holds the whole file
soundFileName="";
meshFileName="";

elapsedTime=0.0;
birth=0.0;

name="Actor";

bPickable=true;
bHidden=false;

drawType=DRAW_TEA;                     //teapot
mesh="";
texture="";

controller=NULL;
mover=NULL;

nextKeyFrame=0;

registerProperties();

menuType.push_back("10MoveButton");
menuType.push_back("12RotateButton");
menuType.push_back("14GetActorButton");

menuProp.push_back("none");
menuProp.push_back("none");
menuProp.push_back("NEXTKEYFRAME");

menuIcon.push_back("move");
menuIcon.push_back("rotate");
menuIcon.push_back("scale");

bTextured=false;
bShadow=false;
bAlphaBlend=false;
bWireFrame=false;
bDepthTest=true;
bUseShader=false;
bFullBright=false;

texFactorX=1.0f;
texFactorY=1.0f;
}

Actor::~Actor(){
std::cout << "destroying..." << name << endl;
}

void Actor::registerProperties(){
//register all properties for saving
createMemberID("NAME",&name,this);

createMemberID("LOCATION", &location, this);
createMemberID("ROTATION", &rotation, this);
createMemberID("SCALE", &scale, this);

createMemberID("COLOR", &color, this);

createMemberID("MESH", &mesh, this);

createMemberID("TEXTURE", &texture, this);
createMemberID("TEXFACTORX", &texFactorX, this);
createMemberID("TEXFACTORY", &texFactorY, this);

createMemberID("DRAWTYPE",&drawType,this);

createMemberID("SHADERPROGRAMNAME",&shaderProgramName,this);
createMemberID("VERTEXSHADERFILENAME",&vertexShaderFileName,this);
createMemberID("FRAGMENTSHADERFILENAME",&fragmentShaderFileName,this);

createMemberID("MESHFILENAME",&meshFileName,this);


createMemberID("SOUNDFILENAME",&soundFileName,this);
createMemberID("NEXTKEYFRAME",&nextKeyFrame,this);

createMemberID("BSHADOW",&bShadow,this);
createMemberID("BFULLBRIGHT",&bFullBright,this);

createMemberID("BTEXTURED",&bTextured,this);
createMemberID("BALPHABLEND",&bAlphaBlend,this);
createMemberID("BWIREFRAME",&bWireFrame,this);
createMemberID("BDEPTHTEST",&bDepthTest,this);
createMemberID("BUSESHADER",&bUseShader,this);


}

void Actor::setup(){

if (vertexShaderFileName!="")
  setupShaders();
else
  cout << "no shader defined for " << name << endl;
}

void Actor::setupShaders(){
//check if there is already a shader program available

if (Renderer::shaderList[shaderProgramName]!=0)
    {
    shaderProgram=Renderer::shaderList[shaderProgramName];
    cout << "found program!" << shaderProgram << "\n";
    //Link shader variables and attributes
    }
else
    {
    GLuint fragmentShader;
    GLuint fragmentProgram;

    //setup shader
    vertexShader=glCreateShader(GL_VERTEX_SHADER);
    fragmentShader=glCreateShader(GL_FRAGMENT_SHADER);

    cout << fragmentShaderFileName << "\n";
    vertexShaderFile=textFileRead((char*)vertexShaderFileName.c_str());
    fragmentShaderFile=textFileRead((char*)fragmentShaderFileName.c_str());

    const char* ptrV = vertexShaderFile;
    const char* ptrF = fragmentShaderFile;

    glShaderSource(vertexShader, 1, &ptrV,NULL);
    glShaderSource(fragmentShader, 1, &ptrF,NULL);

    glCompileShader(vertexShader);
    glCompileShader(fragmentShader);

    printShaderInfoLog(vertexShader);
    printShaderInfoLog(fragmentShader);


    //Link shaders
    shaderProgram=glCreateProgram();
    glAttachShader(shaderProgram,vertexShader);
    glAttachShader(shaderProgram,fragmentShader);

    glLinkProgram(shaderProgram);
    printProgramInfoLog(shaderProgram);


    //cleanUp
    free(vertexShaderFile);
    free(fragmentShaderFile);

    Renderer::shaderList[shaderProgramName]=shaderProgram;
    cout << "registered program!" << shaderProgram << "\n";
    }
linkShaderVariables();
}

void Actor::linkShaderVariables(){
}

void Actor::printShaderInfoLog(GLuint obj){
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

	glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
		printf("%s\n",infoLog);
        free(infoLog);
    }
}

void Actor::printProgramInfoLog(GLuint obj){
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

	glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
		printf("%s\n",infoLog);
        free(infoLog);
    }
}

void Actor::trigger(Actor * other){
}

void Actor::setLocation(Vector3f loc){
location=loc;
}

void Actor::setRotation(Vector3f rot){
rotation=rot;
}


/* update and draw */


void Actor::update(double deltaTime){

elapsedTime+=deltaTime;
}

void Actor::updateShaders(){
}

void Actor::draw(){


    if (bTextured)
      {
      glEnable(GL_TEXTURE_2D);
      if (texture!="") glBindTexture(GL_TEXTURE_2D, Renderer::textureList[texture]);
      if (bShadow) glBindTexture(GL_TEXTURE_2D,Renderer::frameBufferTexture);
      }

    if (bAlphaBlend)
      {
      glEnable(GL_BLEND);
      glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
      }

    if (bWireFrame)
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    if (!bDepthTest)
      glDepthMask(GL_FALSE);

    if (bUseShader)
      {
      glUseProgram(shaderProgram);
      updateShaders();
      }

    if (bFullBright)
      glDisable(GL_LIGHTING);

    glPushMatrix();
    glTranslatef(location.x,location.y,location.z);
    glRotatef(rotation.x,1,0,0);
    glRotatef(rotation.y,0,1,0);
    glRotatef(rotation.z,0,0,1);


    glColor4f(color.r,color.g,color.b,color.a);
    if (drawType==DRAW_PLANE) drawPlane();
    else if (drawType==DRAW_PARTICLES) drawParticles();                 //Mesh
    else if (drawType==DRAW_MESH) drawMesh();                 //Mesh
    else if (drawType==DRAW_SPRITE) drawSprite();
    else if (drawType==DRAW_CUBE) drawCube();                 //Mesh
    else if (drawType==DRAW_TEA) drawTeapot();

    if (bTextured) glDisable(GL_TEXTURE_2D);
    if (bAlphaBlend) glDisable(GL_BLEND);
    if (bWireFrame) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    if (!bDepthTest) glDepthMask(GL_TRUE);
    if (bUseShader) glUseProgram(0);
    if (bFullBright) glEnable(GL_LIGHTING);

    glPopMatrix();
}

void Actor::drawCollision(){

    glPushMatrix();
    glTranslatef(location.x,location.y,location.z);
    glRotatef(rotation.x,1,0,0);
    glRotatef(rotation.y,0,1,0);
    glRotatef(rotation.z,0,0,1);


    glColor4f(1.0f,1.0f,0.0f,1.0f);                         //collisionColor
    if (drawType==DRAW_PLANE) drawPlane();
    else if (drawType==DRAW_PARTICLES) {                        //ATTENTION! Particles are drawn as Cubes!

        glColor4f(color.r,color.g,color.b,color.a);
        glutSolidCube(scale.x/5);
        }

    else if (drawType==DRAW_MESH) drawMesh();                 //Mesh
    else if (drawType==DRAW_SPRITE) drawSprite();
    else if (drawType==DRAW_CUBE) drawCube();                 //Mesh
    else if (drawType==DRAW_TEA) drawTeapot();
    glPopMatrix();
}


void Actor::drawMesh(){

}

void Actor::drawSprite(){

//implement point sprite drawing:
  glTexEnvf( GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE );
  glEnable( GL_VERTEX_PROGRAM_POINT_SIZE );
  glEnable( GL_POINT_SPRITE_ARB );
  glBegin(GL_POINTS);
    glVertex4f(0,0,0,1);
  glEnd();
  glDisable(GL_POINT_SPRITE_ARB);
  glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
}

void Actor::drawPlane(){

    glBegin(GL_QUADS);
		glTexCoord2f(0.0,0.0);
		glVertex3f(0,0,0.0);
        glTexCoord2f(texFactorX*1.0,0.0);
        glVertex3f(scale.x,0,0.0);
        glTexCoord2f(texFactorX*1.0,texFactorY*1.0);
		glVertex3f(scale.x,scale.y,0.0);
        glTexCoord2f(0.0,texFactorY*1.0);
		glVertex3f(0,scale.y,0.0);
	glEnd();

}

void Actor::drawParticles(){

}

void Actor::drawTeapot(){

glColor4f(color.r,color.g,color.b,color.a);
glutSolidTeapot(scale.x);
}

void Actor::drawCube(){

glColor4f(color.r,color.g,color.b,color.a);
glutSolidCube(scale.x);
}


/* saving/loading stuff */

void Actor::create(){
addActor(this);
std::cout << "creating an actor!";
}

TiXmlElement* Actor::save(TiXmlElement *root){

TiXmlElement * element = new TiXmlElement( "Actor" );
element->SetAttribute("name", name);
TiXmlText * text = new TiXmlText( typeid(*this).name() );
element->LinkEndChild( text );

std::map <std::string, memberID>::iterator it;

for ( it=property.begin() ; it != property.end(); it++ )
  {
  TiXmlElement * property=new TiXmlElement(it->first);
  string value = memberToString(&it->second);
  property->LinkEndChild ( new TiXmlText( value));
  element->LinkEndChild( property );
  }

return element;
}

void Actor::load(TiXmlElement *myInfo){

    std::map <std::string, memberID>::iterator it;

    for ( it=property.begin() ; it != property.end(); it++ )
      {
      TiXmlElement * property=NULL;                     //always initialise if you want to check against NULL!
      property=myInfo->FirstChildElement(it->first);
      if (property && property->GetText()!=NULL)        //check if the saved file has this element
        memberFromString(&(it->second),property->GetText());
      else
        cout << "old file version!!!" << endl;
      }
}

string Actor::memberToString(memberID *mID){

char value[64] ;
Vector3f vec3f;
Vector4f vec4f;
bool b;
GLuint glui;
int i;
float f;
double d;
string s="";

const std::type_info* mType;
mType=&(typeid(vec3f));
if (mID->memberType->name()==mType->name())
   {
   Vector3f * p;
   p=(Vector3f*)mID->memberReference;
   vec3f=*p;
   sprintf(value,"vec3f %f %f %f",vec3f.x, vec3f.y, vec3f.z);
   }

mType=&(typeid(vec4f));
if (mID->memberType->name()==mType->name())
   {
   Vector4f * p;
   p=(Vector4f*)mID->memberReference;
   vec4f=*p;
   sprintf(value,"vec4f %f %f %f %f",vec4f.r, vec4f.g, vec4f.b, vec4f.a);
   }

mType=&(typeid(b));
if (mID->memberType->name()==mType->name())
   {
   bool * p;
   p=(bool*)mID->memberReference;
   b=*p;
   sprintf(value,"bool %i",b);
   }

mType=&(typeid(glui));
if (mID->memberType->name()==mType->name())
   {
   GLuint * p;
   p=(GLuint*)mID->memberReference;
   glui=*p;
   sprintf(value,"GLuint %i",glui);
   }

mType=&(typeid(i));
if (mID->memberType->name()==mType->name())
   {
   int * p;
   p=(int*)mID->memberReference;
   i=*p;
   sprintf(value,"int %i",i);
   }

mType=&(typeid(f));
if (mID->memberType->name()==mType->name())
   {
   float * p;
   p=(float*)mID->memberReference;
   f=*p;
   sprintf(value,"float %f",f);
   }

mType=&(typeid(d));
if (mID->memberType->name()==mType->name())
   {
   double * p;
   p=(double*)mID->memberReference;
   d=*p;
   sprintf(value,"double %f",d);
   }

mType=&(typeid(s));
if (mID->memberType->name()==mType->name())
   {
   string* me=(string*) mID->memberReference;
   s="string ";
   s+= (*me);
   return s;
   }

return value;
}

void Actor::memberFromString(memberID *mID,string value){

char * cValue;
cValue = new char [value.size()+1];
strcpy (cValue, value.c_str());

//supported types:
Vector3f vec3f= readVector3f(cValue);
Vector4f vec4f= readVector4f(cValue);
GLuint glui=    readGLuint(cValue);
int i=          readInt(cValue);
float f=        readFloat(cValue);
double d=       readDouble(cValue);
string s=       readString(cValue);
bool b=         readBool(cValue);


if (setPropertyTo (mID, b))
  return;
if (setPropertyTo (mID, vec3f))
  return;
if (setPropertyTo (mID, vec4f))
  return;
if (setPropertyTo (mID, glui))
  return;
if (setPropertyTo (mID, i))
  return;
if (setPropertyTo (mID, f))
  return;
if (setStringPropertyTo(mID, s))
  return;
}

bool Actor::setStringPropertyTo(memberID * mID,string s){

const std::type_info* mType;
mType=&(typeid(s));

if (mID->memberType->name()==mType->name())
  {
  string * theString=(string*)mID->memberReference;
  *theString=s;
  return true;
  }
return false;
}

void Actor::checkReferences(int i){

if (nextKeyFrame==i) {
     nextKeyFrame=0;
    }

if (nextKeyFrame>i) {
    nextKeyFrame--;
    }

if (attachedTo==i) {
    attachedTo=0;
    }
if (attachedTo>i) {
    attachedTo--;
    }
}


/* xml parsing */

Vector3f Actor::readVector3f(char* cValue){
Vector3f vec3f;
if( strncmp("vec3f ",cValue,6) == 0 )
    sscanf((cValue+6),"%f%f%f",&vec3f.x,&vec3f.y,&vec3f.z);
return vec3f;
}

Vector4f Actor::readVector4f(char* cValue){
Vector4f vec4f;
if( strncmp("vec4f ",cValue,6) == 0 )
    sscanf((cValue+6),"%f%f%f%f",&vec4f.r,&vec4f.g,&vec4f.b,&vec4f.a);
return vec4f;
}

bool Actor::readBool(char* cValue){
bool b=false;
int convBuffer;
if( strncmp("bool ",cValue,5) == 0 )
    sscanf((cValue+5),"%i",&convBuffer);
if (convBuffer>0)
 b=true;
return (bool)b;
}

GLuint Actor::readGLuint(char* cValue){
GLuint glui;
if( strncmp("GLuint ",cValue,7) == 0 )
    sscanf((cValue+7),"%u",&glui);
return glui;
}

int Actor::readInt(char* cValue){
int i;
if( strncmp("int ",cValue,4) == 0 )
    sscanf((cValue+4),"%i",&i);
return i;
}

float Actor::readFloat(char* cValue){
float f;
if( strncmp("float ",cValue,6) == 0 )
    sscanf((cValue+6),"%f",&f);
return f;
}

double Actor::readDouble(char* cValue){
double d;
if( strncmp("double ",cValue,7) == 0 )
    sscanf((cValue+7),"%f",&d);
return d;
}

string Actor::readString(char* cValue){
string s;
if( strncmp("string ",cValue,7) == 0 )
    {
    char * myString = new char[64];
    sscanf((cValue+7),"%s",myString);
    s= myString;
    }
return s;
}

bool Actor::createNewActor(string actorIDName){

if (Renderer::actorInfo[actorIDName].actorReference)
  {
  Renderer::actorInfo[actorIDName].actorReference->create();
  return true;
  }
else
  return false;
}

bool Actor::isA(string className){

//create internal class name
int nameCount=className.length();
std::string s;
std::stringstream out;
out << nameCount;
s = out.str();
className=s.append(className);

//transform to upper case
std::transform(className.begin(), className.end(),className.begin(), ::toupper);

std::string myName=typeid(*this).name();

//transform to upper case
std::transform(myName.begin(), myName.end(),myName.begin(), ::toupper);

cout << myName << " vs " << className << endl;
if (myName==className)
  return true;
else
  return false;
}
