/***************************************************************************
 *   Copyright (C) 2012 by Santiago González                               *
 *                                                                         *
 ***( see copyright.txt file at root folder )*******************************/

#pragma once

//#define DEBUG_EVENTS

#include "e-node.h"
#include "e-element.h"
#include "analogclock.h"

enum simState_t{
    SIM_STOPPED=0,
    SIM_ERROR,
    SIM_STARTING,
    SIM_PAUSED,
    SIM_WAITING,
    SIM_RUNNING,
    SIM_DEBUG,
}; 

#include <QElapsedTimer>
#include <QFuture>

class BaseProcessor; 
class Updatable; 
class eElement; 
class Socket; 
class eNode; 
class CircMatrix; 
class QemuDevice; 

class Simulator : public QObject
{
        friend class eNode; 

    Q_OBJECT
    public:
        Simulator( QObject* parent=0 ); 
        ~Simulator(); 

 static Simulator* self() { return m_pSelf;  }

        void addEventAt( uint64_t time, eElement* el ); 
        inline void addEvent( uint64_t time, eElement* el ){ addEventAt( time+m_circTime, el );  }
        void cancelEvents( eElement* el ); 

        void startSim( bool paused=false ); 
        void pauseSim(); 
        void resumeSim(); 
        void stopSim(); 

        void setWarning( int warning ) { m_warning = warning;  }
        
        uint64_t fps() { return m_fps;  }
        void setFps( uint64_t fps ); 
        uint64_t psPerFrame() { return m_psPF;  }
        uint64_t simPsPF() { return m_simPsPF;  }

        uint64_t psPerSec() { return m_psPerSec;  } // Speed picosecond/second
        void setPsPerSec( uint64_t psPs ); 

        uint64_t stepSize() { return m_stepSize;  }
        void setStepSize( uint64_t stepSize ) { m_stepSize = stepSize;  }

        uint64_t stepsPerSec() { return m_stepsPS;  }
        void setStepsPerSec( uint64_t sps ); 

        void  setSlopeSteps( int steps ) { m_slopeSteps = steps;  }
        int slopeSteps( ) { return m_slopeSteps;  }

        void  setMaxNlSteps( uint32_t steps ) { m_maxNlstp = steps;  }
        uint32_t maxNlSteps( ) { return m_maxNlstp;  }
        
        bool isRunning() { return (m_state >= SIM_STARTING);  }
        bool isPaused()  { return (m_state == SIM_PAUSED);  }
        bool isPauseDebug() { return (m_state == SIM_PAUSED && m_debug == true);  }

        uint64_t circTime() { return m_circTime;  }

        void timerEvent( QTimerEvent* e ); 

        double realSpeed() { return m_realSpeed;  } // 0 to 10000 => 0 to 100%

        simState_t simState() { return m_state;  }

        inline void notCorverged() { m_converged = false;  }

        void addToEnodeList( eNode* nod ); 

        void addToElementList( eElement* el ); 
        void remFromElementList( eElement* el ); 
        
        void addToUpdateList( Updatable* el ); 
        void remFromUpdateList( Updatable* el ); 

        void addToSocketList( Socket* el ); 
        void remFromSocketList( Socket* el ); 

        /// FIXME: make a list?
        /// QemuDevice* m_qemuDevice; 

    private:
 static Simulator* m_pSelf; 

        // Accelerate calls from eNode:
        inline void addToChangedNodes( eNode* nod ) { nod->nextCH = m_changedNode;  m_changedNode = nod;  }
        inline void addToChangedList( eElement* el ) { el->nextChanged = m_voltChanged;  m_voltChanged = el;  }
        inline void addToNoLinList( eElement* el ) { el->nextChanged = m_nonLinear;   m_nonLinear = el;  }

        void createNodes(); 
        void resetSim(); 
        void runCircuit(); 
        inline void solveCircuit(); 
        inline void solveMatrix(); 

        inline void clearEventList(); 

        //inline void stopTimer(); 
        //inline void initTimer(); 
        eElement* m_firstEvent; 

#ifdef DEBUG_EVENTS
        uint64_t m_events; 
        uint64_t m_maxEvents; 
        uint64_t m_totEvents; 
        uint64_t m_findEvents; 
        uint64_t m_maxfinds; 
#endif

        QFuture<void> m_CircuitFuture; 

        CircMatrix* m_matrix; 

        QMap<int, QString> m_errors; 
        QMap<int, QString> m_warnings; 

        QList<eNode*> m_eNodeList; 

        eNode*    m_changedNode; 
        eElement* m_voltChanged; 
        eElement* m_nonLinear; 

        QList<eElement*> m_elementList; 
        QList<Updatable*> m_updateList; 
        QList<Socket*> m_socketList; 

        volatile simState_t m_state; 
        simState_t m_oldState; 

        bool m_debug; 
        bool m_converged; 
        bool m_pauseCirc; 

        int m_error; 
        int m_warning; 
        int m_timerId; 
        int m_timerTick_ms; 
        int m_slopeSteps; 

        double m_realFPS; 
        uint64_t m_fps; 
        uint32_t m_NLstep; 
        uint32_t m_maxNlstp; 

        uint64_t m_psPerSec; 
        uint64_t m_stepSize;   ///
        uint64_t m_stepsPS;    ///
        uint64_t m_psPF; 
        uint64_t m_simPsPF; 
        double   m_realSpeed; 

        uint64_t m_timerTime; 
        uint64_t m_circTime; 
        uint64_t m_tStep; 
        uint64_t m_lastStep; 
        uint64_t m_refTime; 
        uint64_t m_lastRefT; 
        uint64_t m_loopTime; 
        uint64_t m_guiTime; 
        uint64_t m_updtTime; 
        double   m_simLoad; 

        QElapsedTimer m_RefTimer; 
        AnalogClock m_analogClock; 
}; 
