/*
 * ChainBasedNode.cpp
 *
 * Copyright (C) 2003 Th. Rothermel, 2007 J. "MUFTI" Scheurich
 * 
 * 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see the file "COPYING" for details); if 
 * not, write to the Free Software Foundation, Inc., 675 Mass Ave, 
 * Cambridge, MA 02139, USA.
 */

#include "stdafx.h"
#include "Scene.h"
#include "Util.h"
#include "MoveCommand.h"
#include "MFFloat.h"
#include "MFVec3f.h"
#include "ChainBasedNode.h"
#include "NodeCoordinate.h"
#include "NodeIndexedLineSet.h"
#include "NodePositionInterpolator.h"
#include "NodeOrientationInterpolator.h"
#include "LdrawDefines.h"

ChainBasedNode::ChainBasedNode(Scene *scene, Proto *proto)
  : GeometryNode(scene, proto)
{
    _chain.resize(0);
    _chainDirty = true;
}

ChainBasedNode::~ChainBasedNode()
{
    _chain.resize(0);
}

void
ChainBasedNode::setField(int index, FieldValue *value) 
{
    _chainDirty = true;
    Node::setField(index, value);
}

void
ChainBasedNode::draw()
{
    if(_chainDirty) {
        createChain();
        _chainDirty = false;
    }
    
    if (_chain.size() == 0) 
        createChain();

    int i;
    
    glPushAttrib(GL_ENABLE_BIT | GL_LIGHTING);
    glDisable(GL_LIGHTING);
    glDisable(GL_TEXTURE_2D);
    glEnable(GL_LINE_SMOOTH);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
    glLineWidth(1);
    
    //taken from IndexedLineSet if colors == NULL

    float c[4];
    glGetMaterialfv(GL_FRONT, GL_EMISSION, c);
    Util::myGlColor4fv(c);

    glBegin(GL_LINE_STRIP);
    for (i=0; i<_chain.size(); i++)
        glVertex3f(_chain[i].x, _chain[i].y, _chain[i].z);
    glEnd();
    glEnable(GL_LIGHTING);
    glPopAttrib();
}

Node * 
ChainBasedNode::toIndexedLineSet(void)
{
    int i;

    if(_chainDirty) {
       createChain();
       _chainDirty = false;
    }
  
    if (_chain.size() == 0) 
        createChain();

    NodeCoordinate *ncoord = (NodeCoordinate *)_scene->createNode("Coordinate");

    int chainLength = _chain.size();
    float *chainValues = new float[chainLength * 3];
    for (i = 0; i < chainLength; i++) {
        chainValues[i * 3    ] = _chain[i].x;
        chainValues[i * 3 + 1] = _chain[i].y;
        chainValues[i * 3 + 2] = _chain[i].z;
    }
    ncoord->point(new MFVec3f(chainValues, chainLength * 3));
    NodeIndexedLineSet *node = (NodeIndexedLineSet *)
                               _scene->createNode("IndexedLineSet");
    node->coord(new SFNode(ncoord));
    
    int *chainIndex = new int[chainLength];
    for (i = 0; i < chainLength; i++)
        chainIndex[i] = i;
    node->coordIndex(new MFInt32(chainIndex, chainLength));
    _chain.resize(0);
    _chainDirty = true;
    return node;
}

int             
ChainBasedNode::write(int f, int indent) 
{
    int flags = _scene->getWriteFlags();
    if (flags & WONDERLAND) {
        NodeIndexedLineSet *chainNode = (NodeIndexedLineSet *)
                                        toIndexedLineSet();
        return chainNode->write(f, indent);
    }
    return Node::write(f, indent);
}

Vec3f               
ChainBasedNode::getMinBoundingBox(void)
{
    if (_chainDirty) {
        createChain();
        _chainDirty = false;
    }
  
    if (_chain.size() == 0) 
        createChain();

    int chainLength = _chain.size();
    float *chainValues = new float[chainLength * 3];
    for (int i = 0; i < chainLength; i++) {
        chainValues[i * 3    ] = _chain[i].x;
        chainValues[i * 3 + 1] = _chain[i].y;
        chainValues[i * 3 + 2] = _chain[i].z;
    }
    MFVec3f chain(chainValues, chainLength * 3);
    return chain.getMinBoundingBox();
}

Vec3f               
ChainBasedNode::getMaxBoundingBox(void)
{
    if (_chainDirty) {
        createChain();
        _chainDirty = false;
    }
  
    if (_chain.size() == 0) 
        createChain();

    int chainLength = _chain.size();
    float *chainValues = new float[chainLength * 3];
    for (int i = 0; i < chainLength; i++) {
        chainValues[i * 3    ] = _chain[i].x;
        chainValues[i * 3 + 1] = _chain[i].y;
        chainValues[i * 3 + 2] = _chain[i].z;
    }
    MFVec3f chain(chainValues, chainLength * 3);
    return chain.getMaxBoundingBox();
}

void
ChainBasedNode::flip(int index)
{
    Node *lineSet = _scene->convertToIndexedLineSet(this);
    if (lineSet != NULL)
        lineSet->flip(index);
}

void
ChainBasedNode::swap(int fromTo)
{
    Node *lineSet = _scene->convertToIndexedLineSet(this);
    if (lineSet != NULL)
        lineSet->swap(fromTo);
}

Node * 
ChainBasedNode::toPositionInterpolator(void)
{
    int i;

    if(_chainDirty) {
       createChain();
       _chainDirty = false;
    }
  
    if (_chain.size() == 0) 
        createChain();

    NodePositionInterpolator *npos = (NodePositionInterpolator *)
                                     _scene->createNode("PositionInterpolator");

    int chainLength = _chain.size();
    float *chainValues = new float[chainLength * 3];
    for (i = 0; i < chainLength; i++) {
        chainValues[i * 3    ] = _chain[i].x;
        chainValues[i * 3 + 1] = _chain[i].y;
        chainValues[i * 3 + 2] = _chain[i].z;
    }
    npos->keyValue(new MFVec3f(chainValues, chainLength * 3));
    float *chainKeyes = new float[chainLength];
    for (i = 0; i < chainLength; i++)
        chainKeyes[i] = 1.0 / (chainLength - 1) * i;
    npos->key(new MFFloat(chainKeyes, chainLength));
    _chain.resize(0);
    _chainDirty = true;
    return npos;
}

Node * 
ChainBasedNode::toOrientationInterpolator(Direction direction)
{
    int i;

    if(_chainDirty) {
       createChain();
       _chainDirty = false;
    }
  
    if (_chain.size() == 0) 
        createChain();

    NodeOrientationInterpolator *npos = (NodeOrientationInterpolator *)
                                 _scene->createNode("OrientationInterpolator");

    int chainLength = _chain.size();
    if (chainLength < 3)
        return npos;
    float *chainValues = new float[(chainLength - 1) * 4];
    Quaternion oldQuat(0, 0 , 0, 1);
    for (i = 1; i < (chainLength - 1); i++) {
        Vec3f point1(_chain[i - 1].x,  _chain[i - 1].y, _chain[i - 1].z);
        Vec3f point2(_chain[i    ].x,  _chain[i    ].y, _chain[i    ].z);
        Vec3f point3(_chain[i + 1].x,  _chain[i + 1].y, _chain[i + 1].z);
        Vec3f vec1(point2 - point1);
        Vec3f vec2(point3 - point2);
        switch (direction) {
          case XZ_DIRECTION:
            vec1.y = 0;
            vec2.y = 0;
            break;
          case YZ_DIRECTION:
            vec1.x = 0;
            vec2.x = 0;
            break;
          case XY_DIRECTION:
            vec1.z = 0;
            vec2.z = 0;
            break;
        }
        Vec3f rotationAxis = vec1.cross(vec2);
        float sinangle = rotationAxis.length() / vec1.length() / vec2.length();
        float cosangle = vec1.dot(vec2) / vec1.length() / vec2.length();
        float angle = 0;
        if (sinangle != 0)
            angle = atan2(sinangle, cosangle);
        Vec3f axis(-rotationAxis.x, -rotationAxis.y, -rotationAxis.z);
        axis.normalize();
        Quaternion quat(axis, -angle);
        quat.normalize();
        quat = quat * oldQuat;
        SFRotation rot(quat);
        chainValues[i * 4    ] =  rot.getValue()[0];
        chainValues[i * 4 + 1] =  rot.getValue()[1];
        chainValues[i * 4 + 2] =  rot.getValue()[2];
        chainValues[i * 4 + 3] =  rot.getValue()[3];
        oldQuat = quat;
    }

    chainValues[0] = 0;
    chainValues[1] = 0;
    chainValues[2] = 1;
    chainValues[3] = 0;
    npos->keyValue(new MFRotation(chainValues, (chainLength - 1) * 4));

    float *chainKeyes = new float[chainLength - 1];
    for (i = 0; i < (chainLength - 1); i++)
        chainKeyes[i] = 1.0 / (chainLength - 2) * i;
    npos->key(new MFFloat(chainKeyes, chainLength - 1));
    _chain.resize(0);
    _chainDirty = true;
    return npos;
}

const Vec3f *
ChainBasedNode::getChain()
{
    if(_chainDirty) {
       createChain();
       _chainDirty = false;
    }
    return _chain.getData();
}

int
ChainBasedNode::getChainLength()
{
    if(_chainDirty) {
       createChain();
       _chainDirty = false;
    }
    return _chain.size();
}

int 
ChainBasedNode::writeLdrawDat(int f, int indent)
{
    if(_chainDirty) {
       createChain();
       _chainDirty = false;
    }
  
    if (_chain.size() == 0) 
        createChain();

    Matrix matrix;
    glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat *) matrix);
    RET_ONERROR( mywritestr(f, "0 //") )
    if (hasName())
        RET_ONERROR( mywritef(f, " %s", (const char *)getName()))
    RET_ONERROR( mywritef(f, " %s\n", (const char *)getProto()->getName(false)))

    if (hasName())
        RET_ONERROR( mywritef(f, "name \"%s\"\n", getName().getData()) )
        
    for (int i = 0; i < _chain.size(); i++) {
        if (i > 0) {
            RET_ONERROR( mywritef(f, "2 24 ") )
            for (int j = 0; j < 2; j++)  {
                Vec3f v(_chain[i - j].x, _chain[i - j].y, _chain[i - j].z);
                v = matrix * v;
                RET_ONERROR( mywritef(f, " %f %f %f",  v.z * LDRAW_SCALE, 
                                                      -v.y * LDRAW_SCALE, 
                                                       v.x * LDRAW_SCALE) )
            }
            RET_ONERROR( mywritestr(f, "\n") )
        }
    }
    return 0;
}


