/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *   This file is part of
 *       _______   ______________  ______     _____
 *      / ____/ | / /  _/ ____/  |/  /   |   |__  /
 *     / __/ /  |/ // // / __/ /|_/ / /| |    /_ <
 *    / /___/ /|  // // /_/ / /  / / ___ |  ___/ /
 *   /_____/_/ |_/___/\____/_/  /_/_/  |_| /____/.
 *
 *   Copyright © 2003-2010 Brain Control, all rights reserved.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#ifndef LSYS2_DRAW_STATE_HPP
#define LSYS2_DRAW_STATE_HPP

#include "../../eshared.hpp"
#include "../physics/ephysics.hpp"
#include "evaluator.hpp"
#include "symbol.hpp"
#include "hermitecurve.hpp"

class Hinge {
public:
    Hinge(const eVector3& axis, eF32 amount, eF32 period) : m_axis(axis), m_amount(amount), m_period(period) {
        this->m_offset = eRandomF() * period;
        this->m_sd.setStatic(eFALSE);
    }

    eVector3 m_axis;
    eF32     m_amount;
    eF32     m_offset;
    eF32     m_period;

    void*    m_collideHandle;
    eSceneData  m_sd;
};


class DrawState_X : public eLsys2_Evaluator {
public:

    class State {
    public:
        eLsys2_Turtle	        m_current;
	    eLsys2_Turtle		    m_last;
	    eLsys2_Turtle		    m_base;
        eSceneData*             m_sd;
        eU32                    m_parentHinge;
    };

    DrawState_X() : m_physics(eNULL), m_width(0.2f) {};

    ~DrawState_X() {
        this->clearCollides(eNULL);
    }


    void reset(eF32 scale, eVector3& position, eQuat& rotQuat, ePhysics* physics) {
        this->m_mainSd = eSceneData();
        this->m_state.m_sd = &m_mainSd;
        this->m_accMass = 0.0f;
        this->clearCollides(physics);
        m_physics = physics;
        this->m_state.m_parentHinge = -1;


        m_state.m_current.reset(position, rotQuat, scale);
        m_state.m_last = m_state.m_current;
        m_state.m_base.m_rotation = eQuat();
        m_state.m_base.m_position = eVector3(0,0,0);
        m_state.m_base.m_scale = 1.0f;
    }

    void clearCollides(ePhysics* newPhysics) {
        for(eU32 i = 0; i < m_enabledHinges.size(); i++) {
            Hinge& h = this->m_hinges[this->m_enabledHinges[i]];
            if(m_physics && (m_physics == newPhysics)) 
                this->m_physics->remove(h.m_collideHandle);
        }
        this->m_enabledHinges.clear();
    }

    void finish() {
        this->m_state.m_sd = &this->m_mainSd;
        if(this->m_physics) {
            for(eU32 i = 0; i < this->m_enabledHinges.size() - 1; i++)
                for(eU32 j = i + 1; j < this->m_enabledHinges.size(); j++) {
                    this->m_physics->disableCollisions(this->m_hinges[this->m_enabledHinges[i]].m_collideHandle, this->m_hinges[this->m_enabledHinges[j]].m_collideHandle);
                }
        }
    }

    eLsys2_Turtle&      getTurtle() {
        return m_state.m_current;
    }
    eLsys2_Turtle&      getLastTurtle() {
        return m_state.m_last;
    }

    eSceneData& getSceneData() {
        return *this->m_state.m_sd;
    }

    eF32&   accMass() {
        return m_accMass;
    }

    virtual eU32 initInstance(eLSys2_Context& context, eU32 symbolIndex) {
        switch(symbolIndex) {
        default:
            eLsys2_Symbol* symbol = context.getSymbol(symbolIndex);
            if(symbol)
                return symbol->init(this);
            else
                return -1;
        }
    }
    virtual void prepare(eLSys2_Context& context, eU32 symbolIndex, eU32 instanceHandle, eF32 blendWeight, eF32 energy, const eF32* parameters) {
        switch(symbolIndex) {
        case '[':            
            this->m_accMassQueue.append(m_accMass);
            m_accMass += this->m_accMassStack.pop();
        break;
        case ']': 
            this->m_accMassStack.append(m_accMass);
            this->m_accMassQueue.append(m_accMass);
            m_accMass = 0.0f;
        break;
        default:
            eLsys2_Symbol* symbol = context.getSymbol(symbolIndex);
            if(symbol)
                symbol->prepare(instanceHandle, this, blendWeight, energy);
        }
    }
    virtual void execute(eLSys2_Context& context, eU32 symbolIndex, eU32 instanceHandle, eF32 blendWeight, eF32 energy, const eF32* parameters) {
        switch(symbolIndex) {
        case '-':  getTurtle().rotate(0, -blendWeight * context.param(CTX_ROTATION_UNIT_X) * parameters[0]); break;
        case '+':  getTurtle().rotate(0,  blendWeight * context.param(CTX_ROTATION_UNIT_X) * parameters[0]); break;
        case '&':  getTurtle().rotate(1, -blendWeight * context.param(CTX_ROTATION_UNIT_Y) * parameters[0]); break;
        case '^':  getTurtle().rotate(1,  blendWeight * context.param(CTX_ROTATION_UNIT_Y) * parameters[0]); break;
        case '\\':  getTurtle().rotate(2, -blendWeight * context.param(CTX_ROTATION_UNIT_Z) * parameters[0]); break;
        case '/':  getTurtle().rotate(2,  blendWeight * context.param(CTX_ROTATION_UNIT_Z) * parameters[0]); break;
        case '[':           
            m_stack.append(m_state);
            m_accMass = this->m_accMassQueue.lastElement();
            this->m_accMassQueue.removeLastElement();
        break;
        case ']':           
                m_state = m_stack.pop();
                m_accMass = this->m_accMassQueue.pop();
            break;
        default:
            eLsys2_Symbol* symbol = context.getSymbol(symbolIndex);
            if(symbol) 
                symbol->execute(instanceHandle, this, blendWeight, energy, parameters, context);
        }
    }

    void drawModel(eU32 instanceHandle, eSceneData* model, eU32 numSegments) {
        if(instanceHandle != -1) {
            this->enableHinge(instanceHandle, model);
//            return;
        }


        eF32 size = m_state.m_current.m_scale * m_width;
        eF32 len = (m_state.m_current.m_position - m_state.m_last.m_position).length() * 0.8f;
        eVector3 boxSize = eVector3(size, size, len);

        eQuat con = m_state.m_current.m_rotation;
        con.conjugate();
		eMatrix4x4 mtx;
        mtx.scale(boxSize);
        mtx = mtx * eMatrix4x4(con);
//        mtx = m_state.m_baseMatrix.inverse() * mtx;
        mtx.translate(m_state.m_current.m_position - m_state.m_base.m_position);
//        mtx.translate(m_state.m_current.m_position);

        this->m_state.m_sd->merge(*model, mtx * eMatrix4x4(m_state.m_base.m_rotation));

/*        
        eF32 tanLen = 1.2f * (turtle1.m_position - turtle0.m_position).length();
        eVector3 tan0 = (turtle0.m_rotation.getVector(2)).normalized() * tanLen;
        eVector3 tan1 = (turtle1.m_rotation.getVector(2)).normalized() * tanLen;
        eVector3 pos = turtle0.m_position;
        eVector3 normal = turtle0.m_rotation.getVector(2);
        for(eU32 i = 0; i < numSegments; i++) {
            eF32 t = (eF32)i / (eF32)(numSegments - 1);
            HermiteCurve::calculate(t, turtle0.m_position, tan0, turtle1.m_position, tan1, pos, normal);
            eF32 scale = turtle0.m_scale * (1.0f - t) + turtle1.m_scale * t;

			eMatrix4x4 mtx;
			mtx.scale(scale);
            eQuat con = turtle0.m_rotation.slerp(t, turtle1.m_rotation);
			con.conjugate();
            eQuat rq = turtle0.m_rotation.inverse();
			mtx = mtx * eMatrix4x4(con * rq);
			mtx.translate(pos);
            this->m_state.m_sd->merge(*model, mtx);
        }
*/
    }

    void turtleUpdate() {
        this->m_state.m_last = this->m_state.m_current;
    }

    eU32 newHinge(const Hinge& o) {
        this->m_hinges.append(o);
        return this->m_hinges.size() - 1;
    }

    void enableHinge(eU32 idx, eSceneData* model) {
        m_enabledHinges.append(idx);
        Hinge& o = m_hinges[idx];
        o.m_sd.clear();
        m_state.m_sd = &o.m_sd;

        this->createHingeObject(idx);
    }

    void createHingeObject(eU32 idx) {
        Hinge& o = m_hinges[idx];
        if(m_physics) {
            eF32 size = m_state.m_current.m_scale * m_width;
            eF32 len = (m_state.m_current.m_position - m_state.m_last.m_position).length() * 0.8f;
            eVector3 boxSize = eVector3(size, size, len);
            eVector3 pos = m_state.m_current.m_position;
            eMatrix4x4 mtxR(m_state.m_current.m_rotation);
            eBool isStatic = eFALSE; 
//            eBool isStatic = this->m_parentHinge == -1;
            o.m_collideHandle = m_physics->addCollide(&o.m_sd, ePhysics::TYPE::BOX, 0.5f, 0.3f, 0.7f, pos, boxSize, mtxR, isStatic, size * size * len * 0.5f);

            if(this->m_state.m_parentHinge != -1) {
                Hinge& ph = m_hinges[this->m_state.m_parentHinge];
                eVector3 hingePos = (m_state.m_current.m_position + m_state.m_last.m_position) * 0.5f;
                m_physics->createJoint(o.m_collideHandle, ph.m_collideHandle, m_state.m_current.m_rotation.getVector(1), hingePos, eMatrix4x4(m_state.m_current.m_rotation).inverse(), eMatrix4x4(m_state.m_base.m_rotation).inverse(), 
                                      m_state.m_current.m_scale, 0.1f, 0.0f, 0.0f, 100.0f); 
            } 

            m_state.m_base = m_state.m_current;
            this->m_state.m_parentHinge = idx;
        }
    }

//private:
    State                   m_state;
    eArray<State>   m_stack;
    eSceneData              m_mainSd;

    eF32                    m_accMass;
    eArray<eF32>            m_accMassStack;
    eArray<eF32>            m_accMassQueue;

    eArray<Hinge>           m_hinges;
    eArray<eU32>            m_enabledHinges;
    ePhysics*               m_physics;
    eF32                    m_width;

};

#endif // LSYS2_STACK_HPP
