#include "tiptilt_ctrl/bcu/Interface/Sharknir/Bcu/ServiceWorkerSimulator.h"

// #include "tiptilt_ctrl/bcu/Interface/Sharknir/Bcu/AdSecDiagnosticRecord.h"
#include "tiptilt_ctrl/bcu/Interface/Sharknir/Bcu/SharknirBcuSlopeDiagnosticRecord.h"
#include "tiptilt_ctrl/bcu/Interface/Sharknir/Bcu/SharknirBcuCcdDiagnosticRecord.h"
#include "tiptilt_ctrl/bcu/Interface/Sharknir/Bcu/Device.h"
#include "tiptilt_ctrl/bcu/Interface/Sharknir/Bcu/OverallDiagnosticRecordBuffer.h"
#include "tiptilt_ctrl/bcu/Interface/Sharknir/Bcu/OverallDiagnosticRecord.h"
#include "tiptilt_ctrl/bcu/Interface/Sharknir/Bcu/FakeDevice.h"

#include "tiptilt_ctrl/bcu/util/C++11.h"
#include "tiptilt_ctrl/bcu/util/NiceClock.h"

#include <boost/make_shared.hpp>

using boost::make_shared;

namespace Sharknir { namespace Bcu {

const uint32_t ServiceWorkerSimulator::APD_COUNT_0= 3;
const uint32_t ServiceWorkerSimulator::APD_COUNT_1= 5;
const uint32_t ServiceWorkerSimulator::APD_COUNT_2= 7;
const uint32_t ServiceWorkerSimulator::APD_COUNT_3= 11;
const uint32_t ServiceWorkerSimulator::FLAO_SLOPES_POSITION= 1200;

namespace simulation
{



class OverallDiagnosticRecordBufferFake : public OverallDiagnosticRecordBuffer
{
private:
	long int apdFrameCounter_;
	long int asmWfsFrameCounter_;
public:
	OverallDiagnosticRecordBufferFake(const Nice::JointPtr& config)
	{
		config_= config;
		apdFrameCounter_= 0;
		asmWfsFrameCounter_= 0;
	}

	void throwExceptionIfRequested()
    {
        if (config_->getAtNode<bool>(
                PropertyName::Simulator::externalOperationFailureRequested()))
        {
            E_THROW(Sharknir::BcuServiceException,
            		"this diagnostics failure was requested");
        }
    }


    /** @note overridden */
    virtual size_t getCapacity()
    {
        return 10*1000;
    }

    /** @note overridden */
    virtual size_t getSize()
    {
        return 10*1000;
    }

    /** @note overridden */
    virtual size_t getNumOfDuplicatedSlopeRecords()
    {
        return 0;
    }

    /** @note overridden */
    virtual size_t getNumOfDuplicatedCcdRecords()
    {
        return 0;
    }

    /** @note overridden */
    virtual size_t getNumOfDuplicatedAsmRecords()
    {
        return 0;
    }

    /** @note overridden */
    virtual size_t getNumOfInsertedSlopeRecords()
    {
        return 0;
    }

    /** @note overridden */
    virtual size_t getNumOfInsertedCcdRecords()
    {
        return 0;
    }

    /** @note overridden */
    virtual size_t getNumOfInsertedAsmRecords()
    {
        return 0;
    }

    /** @note overridden */
    virtual FrameCounter getHighestReceivedFrameCounter()
    {
        return 0;
    }

    /** @note overridden */
    virtual size_t getNumOfLateSlopeRecords()
    {
        return 0;
    }

    /** @note overridden */
    virtual size_t getNumOfLateCcdRecords()
    {
        return 0;
    }

    /** @note overridden */
    virtual size_t getNumOfLateAsmRecords()
    {
        return 0;
    }

    /** @note overridden */
    virtual void clear()
    {
    }

    /** @note overridden */
    virtual void insertSlopeRecord(const FrameCounter,
            const Basda::Microgate::ByteBufferPtr&)
    {
    }

    /** @note overridden */
    virtual void insertCcdRecord(const FrameCounter,
            const Basda::Microgate::ByteBufferPtr&)
    {
    }

    /** @note overridden */
    virtual void insertAsmRecord(const FrameCounter,
            const Basda::Microgate::ByteBufferPtr&)
    {
    }

    /** @note overridden */
    virtual SeqOverallDiagnosticRecordPtr getPastOverallDiagnosticRecords(
            size_t nMaxRecords)
    {
	    throwExceptionIfRequested();

        OverallDiagnosticRecord draft;
        draft.slopeRecord= SeqByte(sizeof(SharknirBcuSlopeDiagnosticRecord));
        SharknirBcuSlopeDiagnosticRecord *rawSlopeRecord=
        	reinterpret_cast<SharknirBcuSlopeDiagnosticRecord*>(
        			&draft.slopeRecord.front());
        rawSlopeRecord->sc_APDCounters[0]= ServiceWorkerSimulator::APD_COUNT_0;
        rawSlopeRecord->sc_APDCounters[1]= ServiceWorkerSimulator::APD_COUNT_1;
        rawSlopeRecord->sc_APDCounters[2]= ServiceWorkerSimulator::APD_COUNT_2;
        rawSlopeRecord->sc_APDCounters[3]= ServiceWorkerSimulator::APD_COUNT_3;
        rawSlopeRecord->sc_FCVector.mpifr_FrameNumber= 13;

        insertFakeSlopes(&draft.slopeRecord);

        draft.ccdRecord= SeqByte(sizeof(SharknirBcuCcdDiagnosticRecord));
        insertFakeCcdFrame(&draft.ccdRecord);

        // draft.asmRecord= SeqByte(sizeof(MirrorOutputDiagn));

        SeqOverallDiagnosticRecordPtr records=
                make_shared<SeqOverallDiagnosticRecord>(
                        static_cast<size_t>(nMaxRecords), draft);
        for (int32_t i= 0; i < static_cast<int32_t>(nMaxRecords); ++i)
        {
            records->at(i).frameCounter= i;
            SharknirBcuSlopeDiagnosticRecord *rawSlopeRecord=
            	reinterpret_cast<SharknirBcuSlopeDiagnosticRecord*>(
            			&records->at(i).slopeRecord.front());
            rawSlopeRecord->sc_FCVector.mpifr_FrameNumber= apdFrameCounter_++;

            for (unsigned int flaoSlope=ServiceWorkerSimulator::FLAO_SLOPES_POSITION;
            	flaoSlope <= ServiceWorkerSimulator::FLAO_SLOPES_POSITION + 50;
            	flaoSlope++)
            	rawSlopeRecord->sc_TotalSlopeVector[flaoSlope]=
            	(apdFrameCounter_ % 10) * 0.1;

            // MirrorOutputDiagn *rawAsmRecord=
            // 	reinterpret_cast<MirrorOutputDiagn*>(
            // 			&records->at(i).asmRecord.front());
            // rawAsmRecord->header.WFSFrameCounter= asmWfsFrameCounter_;
            // rawAsmRecord->footer.WFSFrameCounter= asmWfsFrameCounter_++;
        }

        return records;
    }

    /** @note overridden */
    virtual SeqOverallDiagnosticRecordPtr getFutureOverallDiagnosticRecords(
            size_t nRecords, const Nice::Time&)
    {
        return this->getPastOverallDiagnosticRecords(nRecords);
    }

    /** @note overridden */
    virtual SeqOverallDiagnosticRecordPtr getPastCcdOverallDiagnosticRecords(
            const size_t nMaxRecords)
    {
        return this->getPastOverallDiagnosticRecords(nMaxRecords);
    }

    /** @note overridden */
    virtual SeqOverallDiagnosticRecordPtr
    getPastSlopeOverallDiagnosticRecords(const size_t nMaxRecords)
    {
        return this->getPastOverallDiagnosticRecords(nMaxRecords);
    }

    /** @note overridden */
    virtual SeqOverallDiagnosticRecordPtr getFutureCcdOverallDiagnosticRecords(
            size_t nMaxRecords, const Nice::Time& timeout)
    {
        return this->getFutureOverallDiagnosticRecords(nMaxRecords, timeout);
    }

    /** @note overridden */
    virtual SeqOverallDiagnosticRecordPtr
    getFutureSlopeOverallDiagnosticRecords(
            const size_t nMaxRecords, const Nice::Time& timeout)
    {
        return this->getFutureOverallDiagnosticRecords(nMaxRecords, timeout);
    }


private:

    void insertFakeSlopes(SeqByte* const slopeRecord)
        {
            const size_t nSlopes= SharknirBcuSlopeDiagnosticRecord::N_TOTAL_SLOPES;
            static const SharknirBcuSlopeDiagnosticRecord::Slope slopes[nSlopes] = {
    #include "SlopeVector.inc"
            };
            SharknirBcuSlopeDiagnosticRecord* rec=
                 reinterpret_cast<SharknirBcuSlopeDiagnosticRecord*>(
                         &slopeRecord->front());
            memcpy(rec->sc_TotalSlopeVector, slopes, nSlopes*sizeof(slopes[0]));
        }


    void insertFakeCcdFrame(SeqByte* const ccdRecord)
    {
        const size_t nPixels= SharknirBcuCcdDiagnosticRecord::N_PIXELS;
        static const uint16_t pnccdFrameInBcuOrder[nPixels] = {
#include "PnCCDFrameInBcuMem.inc"
        };
        static const uint16_t darkFrame[nPixels]= {
#include "PnCCDDarkFrame.inc"
        };
        SharknirBcuCcdDiagnosticRecord* rec=
                reinterpret_cast<SharknirBcuCcdDiagnosticRecord*>(
                        &ccdRecord->front());
        if (config_->getAtNode<bool>(
            PropertyName::Simulator::zeroBeamsOnCcdRequested()))
        {
        	memcpy(rec->pixels, darkFrame,
                   nPixels*sizeof(SharknirBcuCcdDiagnosticRecord::Pixel));
        	//memset(rec->pixels, 0, nPixels);
        }
        else
        {
            memcpy(rec->pixels,
                   pnccdFrameInBcuOrder,
                   nPixels*sizeof(SharknirBcuCcdDiagnosticRecord::Pixel));
        }
    }

    Nice::JointPtr config_;
};

SHARKNIR_DECLARE_BOOST_SP(OverallDiagnosticRecordBufferFake)
}

class FakeDeviceFactory : public DeviceFactory
{
public:
    FakeDeviceFactory(const Nice::JointPtr& config) : config_(config)
    {
        assert(config);
    }

    virtual DevicePtr buildDevice()
    {
        return new Sharknir::Bcu::FakeDevice(config_);
    }

private:
    Nice::JointPtr config_;
};


ServiceWorkerSimulator::ServiceWorkerSimulator(const Nice::JointPtr& config) :
        Sharknir::Bcu::ServiceWorker(config,
                make_shared<simulation::OverallDiagnosticRecordBufferFake>(
                    config),
                make_shared<Sharknir::util::NiceClock>())
{
    SHARKNIR_PRECONDITION(0 != config, "config must be valid");
    Sharknir::Bcu::DeviceFactoryPtr deviceFactory=
        boost::make_shared<FakeDeviceFactory>(config);
    ServiceWorker::setDeviceFactory(deviceFactory);
    deviceFactory->buildDevice();

    config->setAtNode<bool>(PropertyName::reachable(), true);
    config->setAtNode<bool>(
        PropertyName::Simulator::externalOperationFailureRequested(),
        false);
    config->setAtNode<bool>(
        PropertyName::Simulator::zeroBeamsOnCcdRequested(), false);
}

}
}
