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

#ifndef LSYS2_INSTANCE_HPP
#define LSYS2_INSTANCE_HPP

#include "../../eshared.hpp"

#define LSYS2_MAX_PARAMS 6

class eLsys2_SymbolInstance {
public:
	eLsys2_SymbolInstance(eU32 symbolIndex, eLsys2_Evaluator* state, eLsys2_Symbol* symbol, eU32 localIndex, eU32 parent) : m_localIndex(localIndex), m_parent(parent), m_derivation(-1), m_symbolIndex(symbolIndex) {
        for(eU32 i = 0; i < LSYS2_MAX_PARAMS; i++)
            this->m_params[i] = 1.0f;
        this->m_childGrowTime = (symbol) ? symbol->getInstanceGrowTime() : 1.0f;
        m_handle = (symbol) ? symbol->init(state) : -1;
	}

	void evaluate(eF32 time, eLsys2_Evaluator* state, eLSys2_Context& context) {
		if(time < eALMOST_ZERO)
			return;
        eF32 localGrowTime = this->m_parent == -1 ? 1.0f : context.getInstance(m_parent).m_childGrowTime;
        eF32 energy = eLn(1.0f + (eEXPONE - 1.0f) * time / localGrowTime);
		// a single node
		if(time < localGrowTime) {
			// execute local Symbol
            state->execute(context, this->m_symbolIndex, m_handle, energy, energy, (eF32*)&this->m_params);
		} else {
			// test if we have a child
			if(m_derivation == -1) {
				// nope, so just execute ourselves
                state->execute(context, this->m_symbolIndex, m_handle, 1.0f, energy, (eF32*)&this->m_params);
			} else {
				// we have a child
				if(time < localGrowTime + m_childGrowTime) {
                    eF32 leftLocalEnergy = 1.0f - eLn(1.0f + (eEXPONE - 1.0f) * (time - localGrowTime) / m_childGrowTime);
					
					// draw own symbol
                    state->execute(context, this->m_symbolIndex, m_handle, 1.0f, leftLocalEnergy, (eF32*)&this->m_params);
				}
				// draw child process
				eU32 cnt = m_derivation_length;
				for(eU32 i = 0; i < m_derivation_length; i++)
					context.getInstance(this->m_derivation + i).evaluate(time - localGrowTime, state, context);
			}
		}
	}

	static void prepare(eU32 idx, eF32 time, eLsys2_Evaluator* state, eLSys2_Context& context) {
		if(time < eALMOST_ZERO)
			return;
        eLsys2_SymbolInstance* inst = &context.getInstance(idx);
        eF32 localGrowTime = inst->m_parent == -1 ? 1.0f : context.getInstance(inst->m_parent).m_childGrowTime;
        eF32 energy = eLn(1.0f + (eEXPONE - 1.0f) * time / localGrowTime);
		// a single node
		if(time < localGrowTime) {
            state->prepare(context, inst->m_symbolIndex, inst->m_handle, energy, energy, (eF32*)&inst->m_params);
        } else {
			// create child if neccessary
			if(inst->m_derivation == -1)  {
                eLsys2_Symbol* symbol = context.getSymbol(inst->m_symbolIndex);
                if(symbol && symbol->applyRandomRule(state, idx, context)) 
                    // derivation has been created, our local instance may have been invalidated, so re-call ourself
                    inst = &context.getInstance(idx);
            }

			// test if we have a child
			if(inst->m_derivation == -1) {
                // nope, only sample ourself
                state->prepare(context, inst->m_symbolIndex, inst->m_handle, 1.0f, energy, (eF32*)&inst->m_params);
            } else {
                // sample children
                eU32 devCnt = inst->m_derivation_length;
                eU32 devPos = inst->m_derivation + devCnt - 1;
                while(devCnt--) {
                    context.getInstance(devPos).prepare(devPos, time - localGrowTime, state, context);
                    devPos--;
                }
                inst = &context.getInstance(idx);
				
                if(time < localGrowTime + inst->m_childGrowTime) {
                    eF32 leftLocalEnergy = 1.0f - eLn(1.0f + (eEXPONE - 1.0f) * (time - localGrowTime) / inst->m_childGrowTime);
                    // sample ourself too
                    state->prepare(context, inst->m_symbolIndex, inst->m_handle, 1.0f, leftLocalEnergy, (eF32*)&inst->m_params);
                }
            }
		}
	}

    void setDerivation(eU32 start, eU32 len) {
        this->m_derivation = start;
        this->m_derivation_length = len;
    }

    eF32& param(eU32 nr) {
        return this->m_params[nr];
    }
private:
    eU32                            m_symbolIndex;
    eU32                            m_localIndex;
	eU32							m_parent;
	eU32							m_derivation;
	eU32							m_derivation_length;
    eF32                            m_params[LSYS2_MAX_PARAMS];
    eF32                            m_childGrowTime;
    eU32                            m_handle;
};

#endif // LSYS2_SYMBOL_HPP
