/*******************************************************************************
 * Copyright (c) 2016 - 2018 Johannes Messmer (admin@jomess.com), fortiss GmbH
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Johannes Messmer - initial API and implementation and/or initial documentation
 *   Jose Cabral - Cleaning of namespaces
 *******************************************************************************/

#ifndef SRC_MODULES_EMBRICK_BUSCONTROLLER_H_
#define SRC_MODULES_EMBRICK_BUSCONTROLLER_H_

#include <extevhan.h>
#include <devlog.h>
#include <sys/time.h>
#include <stdio.h>
#include <math.h>
#include <timerha.h>
#include <string>
#include <pthread.h>
#include <fortelist.h>
#include "spi.h"
#include "pin.h"
#include <slave/slave.h>
#include <forte_sync.h>
#include <forte_sem.h>
#include <forte_thread.h>

#include <io/device/io_controller_multi.h>

const unsigned int TransferBufferLength = 150;
const unsigned int SyncGapMultiplicator = 15;
const unsigned int SyncGapDuration = (SyncGapMultiplicator - 1) * 32 + 10;

class EmbrickBusHandler : public forte::core::io::IODeviceMultiController {
    friend class EmbrickSlaveHandler;

  public:
    explicit EmbrickBusHandler(CDeviceExecution& paDeviceExecution);

    enum Command {
      /**
       * Initializes the slave and assigns an individual address to it.
       * The slave provides hardware specifications and length information for the data exchange.
       * The master sends configuration parameter to synchronize transfer timings.
       */
      Init = 2,

      /**
       * It enables the slave select signal of the addressed slave.
       * The subsequent slave waits for initialization.
       */
      SelectNextSlave = 3,

      /**
       * It sets and gets the current state, called process image, of the slave.
       * The amount of exchanged data bytes depends on the functionality of the slave.
       * If a slave, for example, has 8 digital outputs, the master sends 1 Byte (1 Bit for each digital output) to set the slave state.
       * Likewise if a slave reads 8 analog values, the slave sends 16 Bytes (2 Bytes for each value) to the master.
       * The structure of the process image is specified in the emBRICK products manual.
       * The Data command should be performed at least 20 times per second.
       */
      Data = 10,
    };

    struct Config : forte::core::io::IODeviceController::Config {
        unsigned int mBusInterface; //!< Selects the SPI interface for the brickBUS. The default value is 1 (selects SPI1).
        unsigned int mBusSelectPin; //!< Sets the pin, which is connect to the slave select pin of the brickBUS.
        unsigned long mBusInitSpeed; //!< Sets the SPI speed for the brickBUS during the initialization of the slaves. The default value is 300000 Hz.
        unsigned long mBusLoopSpeed; //!< Sets the maximal SPI speed for the brickBUS during the runtime updates of the slaves. The default value is 700000 Hz.
    };

    enum HandleType {
      Bit,
      Analog,
      Analog10
    };

    class HandleDescriptor : public forte::core::io::IODeviceMultiController::HandleDescriptor {
      public:
        HandleType mType;
        uint8_t mOffset;
        uint8_t mPosition;

        HandleDescriptor(CIEC_WSTRING const &paId, forte::core::io::IOMapper::Direction paDirection, int paSlaveIndex, HandleType paType, uint8_t paOffset,
            uint8_t paPosition) :
            forte::core::io::IODeviceMultiController::HandleDescriptor(paId, paDirection, paSlaveIndex), mType(paType), mOffset(paOffset), mPosition(paPosition) {

        }
    };

    void setConfig(struct forte::core::io::IODeviceController::Config* paConfig);

    EmbrickSlaveHandler* getSlave(int paIndex);
    void forceUpdate(int paIndex);

    void addSlaveHandle(int paIndex, forte::core::io::IOHandle* paHandle);
    void dropSlaveHandles(int paIndex);
  protected:
    const char* init();
    void deInit();

    forte::core::io::IOHandle* initHandle(forte::core::io::IODeviceController::HandleDescriptor *paHandleDescriptor);

    void prepareLoop();
    virtual void runLoop();
    void cleanLoop();

    bool transfer(unsigned int paTarget, Command paCmd, unsigned char* paDataSend =
    NULL, int paDataSendLength = 0, unsigned char* paDataReceive = NULL, int paDataReceiveLength = 0, EmbrickSlaveHandler::SlaveStatus* paStatus = NULL,
        CSyncObject *paSyncMutex = NULL);
    bool broadcast(Command paCmd, unsigned char* paDataSend =
    NULL, int paDataSendLength = 0, unsigned char* paDataReceive = NULL, int paDataReceiveLength = 0) {
      return transfer(0, paCmd, paDataSend, paDataSendLength, paDataReceive, paDataReceiveLength);
    }

    // Config
    struct Config mConfig;

    // Timing variables
    struct timespec mLastLoop;
    struct timespec mNextLoop;
    uint64_t mLastTransfer;

    // Handlers
    EmbrickSPIHandler *mSpi;
    EmbrickPinHandler *mSlaveSelect;

    // Slaves
    typedef CSinglyLinkedList<EmbrickSlaveHandler *> TSlaveList;
    TSlaveList *mSlaves;
    int mSlaveCount;

    // Sync
    bool mLoopActive;
    CSemaphore mForceLoop;
    CSyncObject mSyncObject;

    // Error
    bool checkHandlerError();

    // Scheduling
    struct SEntry {
        EmbrickSlaveHandler* mSlave;
        struct timespec mNextDeadline;
        uint16_t mLastDuration;
        bool mForced;
        bool mDelayed;
    };
    struct SEntry **mSList;
    SEntry *mSNext;

  private:
    bool isSlaveAvailable(int paIndex);
    bool checkSlaveType(int paIndex, int paType);

    uint64_t micros();
    unsigned long millis();
    time_t mInitTime;
    void microsleep(uint64_t paMicroseconds);
    void addTime(struct timespec& paTs, unsigned long paMicroseconds);

    unsigned char calcChecksum(unsigned char * paData, int paDataLen);

    unsigned char mSendBuffer[TransferBufferLength];
    unsigned char mRecvBuffer[TransferBufferLength];

    static const unsigned char scmChecksumConstant = 0x55;

    static const char * const scmSlaveUpdateFailed;
    static const char * const scmNoSlavesFound;
};

#endif /* SRC_MODULES_EMBRICK_BUSCONTROLLER_H_ */
