/*******************************************************************************
 * Copyright (c) 2016 - 2018 Johannes Messmer (admin@jomess.com), fortiss GmbH
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * 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 BusInterface; //!< Selects the SPI interface for the brickBUS. The default value is 1 (selects SPI1).
    unsigned int BusSelectPin; //!< Sets the pin, which is connect to the slave select pin of the brickBUS.
    unsigned long BusInitSpeed; //!< Sets the SPI speed for the brickBUS during the initialization of the slaves. The default value is 300000 Hz.
    unsigned long BusLoopSpeed; //!< 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
  };

  struct HandleDescriptor: forte::core::IO::IODeviceMultiController::HandleDescriptor {
    HandleType type;
    uint8_t offset;
    uint8_t position;

    HandleDescriptor(CIEC_WSTRING const &id, forte::core::IO::IOMapper::Direction direction,
        int slaveIndex, HandleType type, uint8_t offset,
        uint8_t position) :
          forte::core::IO::IODeviceMultiController::HandleDescriptor(id, direction,
            slaveIndex), type(type), offset(offset), position(position) {

    }
  };

  void setConfig(struct forte::core::IO::IODeviceController::Config* config);

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

  void addSlaveHandle(int index, forte::core::IO::IOHandle* handle);
  void dropSlaveHandles(int index);
protected:
  const char* init();
  void deInit();

  forte::core::IO::IOHandle* initHandle(
      forte::core::IO::IODeviceMultiController::HandleDescriptor *handleDescriptor);

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

  bool transfer(unsigned int target, Command cmd, unsigned char* dataSend =
                  NULL, int dataSendLength = 0, unsigned char* dataReceive = NULL,
                int dataReceiveLength = 0, EmbrickSlaveHandler::SlaveStatus* status = NULL,
                CSyncObject *syncMutex = NULL);
  bool broadcast(Command cmd, unsigned char* dataSend =
                   NULL, int dataSendLength = 0, unsigned char* dataReceive = NULL,
                 int dataReceiveLength = 0) {
    return transfer(0, cmd, dataSend, dataSendLength, dataReceive,
                    dataReceiveLength);
  }

  // Config
  struct Config config;

  // Timing variables
  struct timespec lastLoop;
  struct timespec nextLoop;
  uint64_t lastTransfer;

  // Handlers
  EmbrickSPIHandler *spi;
  EmbrickPinHandler *slaveSelect;

  // Slaves
  typedef CSinglyLinkedList<EmbrickSlaveHandler *> TSlaveList;
  TSlaveList *slaves;
  int slaveCount;

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

  // Error
  bool checkHandlerError();

  // Scheduling
  struct SEntry {
    EmbrickSlaveHandler* slave;
    struct timespec nextDeadline;
    uint16_t lastDuration;
    bool forced;
    bool delayed;
  };
  struct SEntry **sList;
  SEntry *sNext;

private:
  bool isSlaveAvailable(int index);
  bool checkSlaveType(int index, int type);

  uint64_t micros();
  unsigned long millis();
  time_t initTime;
  void microsleep(uint64_t microseconds);
  void addTime(struct timespec& t, unsigned long microseconds);

  unsigned char calcChecksum(unsigned char * data, int dataLen);

  unsigned char sendBuffer[TransferBufferLength];
  unsigned char recvBuffer[TransferBufferLength];

  static const unsigned char ChecksumConstant = 0x55;

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

#endif /* SRC_MODULES_EMBRICK_BUSCONTROLLER_H_ */
