/*************************************************************************\
* Copyright (c) 2019 European Spallation Source ERIC
* ecmc is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution. 
*
*  ecmcSocketCANWriteBuffer.cpp
*
*  Created on: Mar 22, 2020
*      Author: anderssandstrom
*
\*************************************************************************/

// Needed to get headers in ecmc right...
#define ECMC_IS_PLUGIN

#include <sstream>
#include "ecmcSocketCANWriteBuffer.h"
#include "epicsThread.h"

#define ECMC_PLUGIN_ASYN_PREFIX      "plugin.can"

// Start worker for socket read()
void f_worker_write(void *obj) {
  if(!obj) {
    printf("%s/%s:%d: Error: Worker write thread ecmcSocketCANWriteBuffer object NULL..\n",
            __FILE__, __FUNCTION__, __LINE__);
    return;
  }
  ecmcSocketCANWriteBuffer * canObj = (ecmcSocketCANWriteBuffer*)obj;
  canObj->doWriteWorker();
}

/** ecmc ecmcSocketCANWriteBuffer class
 * Implements writing of can messages to a socket. 
 * Two buffers are used. While a thread writes to socket from one of teh buffers,
 * data can still be added to the other buffer. Then teh buffers are switched.
*/
ecmcSocketCANWriteBuffer::ecmcSocketCANWriteBuffer(int socketId, int cfgDbgMode) {
  memset(&buffer1_.frames,0,sizeof(struct can_frame)*ECMC_CAN_MAX_WRITE_CMDS);
  memset(&buffer2_.frames,0,sizeof(struct can_frame)*ECMC_CAN_MAX_WRITE_CMDS);
  bufferIdAddFrames_ = 1;  // start to add frames to buffer 1
  writeBusy_         = 0;
  socketId_          = socketId;
  cfgDbgMode_        = cfgDbgMode;
  destructs_         = 0;
  bufferSwitchMutex_ = epicsMutexCreate();
  lastWriteSumError_ = 0;
  writePauseTime_.tv_sec  = 0;
  writePauseTime_.tv_nsec = 2e6;  // 2ms
  buffer1_.frameCounter = 0;
  buffer2_.frameCounter = 0;

  bufferAdd_ = &buffer1_;
  bufferWrite_ = &buffer2_; 

  // Create worker thread for writing socket
  std::string threadname = "ecmc." ECMC_PLUGIN_ASYN_PREFIX".write";
  if(epicsThreadCreate(threadname.c_str(), 0, 32768, f_worker_write, this) == NULL) {
    throw std::runtime_error("Error: Failed create worker thread for write().");
  }
}

ecmcSocketCANWriteBuffer::~ecmcSocketCANWriteBuffer() {
  // kill worker
  destructs_ = 1;  // maybe need todo in other way..
}

// Write socket worker thread (switch between two buffers)
void ecmcSocketCANWriteBuffer::doWriteWorker() {
  while(true) {    
    if(destructs_) {
      return;
    }    

    nanosleep(&writePauseTime_,NULL);    

    if(writeBusy_) {      
      continue;
    }

    if(destructs_) {
      return;
    }

    writeBusy_ = 1;
    // Check if anything to write..
    if(bufferAdd_->frameCounter == 0) {
      writeBusy_ = 0;
      continue;
    }

    // Switch buffers and write!
    switchBuffer();
    
    writeBuffer();
    writeBusy_ = 0;
  }
}

int ecmcSocketCANWriteBuffer::addWriteCAN(can_frame *frame) {  
  // Cannot switch if busy..
  int errorCode = 0;
  epicsMutexLock(bufferSwitchMutex_);
  errorCode = addToBuffer(frame);
  epicsMutexUnlock(bufferSwitchMutex_);
  return errorCode;
}

// Test can write function (simple if for plc func)
int ecmcSocketCANWriteBuffer::addWriteCAN( uint32_t canId,
                                           uint8_t len,
                                           uint8_t data0,
                                           uint8_t data1,
                                           uint8_t data2,
                                           uint8_t data3,
                                           uint8_t data4,
                                           uint8_t data5,
                                           uint8_t data6,
                                           uint8_t data7) {
  can_frame frame;
  frame.can_id  = canId;
  frame.can_dlc = len;     // data length
  frame.data[0] = data0;  // request read cmd
  frame.data[1] = data1;
  frame.data[2] = data2;
  frame.data[3] = data3;
  frame.data[4] = data4;
  frame.data[5] = data5;
  frame.data[6] = data6;
  frame.data[7] = data7;
  return addWriteCAN(&frame);
}

int ecmcSocketCANWriteBuffer::getlastWritesErrorAndReset() {
  int tempError = lastWriteSumError_;
  lastWriteSumError_ = 0;
  return tempError;
}

int ecmcSocketCANWriteBuffer::addToBuffer(can_frame *frame) {

  if(bufferAdd_->frameCounter >= ECMC_CAN_MAX_WRITE_CMDS) {
    return ECMC_CAN_ERROR_WRITE_FULL;
  }

  bufferAdd_->frames[bufferAdd_->frameCounter] = *frame;
  bufferAdd_->frameCounter++; 
  return 0;
}

int ecmcSocketCANWriteBuffer::writeBuffer() {

  int errorCode = 0;
  if(bufferWrite_->frameCounter==0) {
    return 0;
  }

  for(int i=0; i<bufferWrite_->frameCounter;i++) {
    errorCode = writeCAN(&bufferWrite_->frames[i]);
    if(errorCode) {
      lastWriteSumError_ = errorCode;
    }
  }
  bufferWrite_->frameCounter = 0;
  return lastWriteSumError_;
}

int ecmcSocketCANWriteBuffer::switchBuffer() {

  // ensure safe buffer switch
  epicsMutexLock(bufferSwitchMutex_);
  canWriteBuffer *temp = bufferWrite_;
  bufferWrite_ = bufferAdd_;
  bufferAdd_   = temp;
  epicsMutexUnlock(bufferSwitchMutex_);
  return 0;
}

// Write to socket
int ecmcSocketCANWriteBuffer::writeCAN(can_frame *frame){

  if(!frame) {
    return ECMC_CAN_ERROR_WRITE_NO_DATA;
  }

  // Maybe need to add the size to write here.. if struct is not full, hmm?!
  int nbytes = write(socketId_, frame, sizeof(struct can_frame));
  if(nbytes == -1) {    
    printf("ecmcSocketCAN: write() fail with error %s.\n", strerror(errno));      
    return ECMC_CAN_ERROR_WRITE_INCOMPLETE;    
  }
  
  if (nbytes!= sizeof(struct can_frame)) {
    return ECMC_CAN_ERROR_WRITE_INCOMPLETE;
  }

  if(cfgDbgMode_) {	  
    // Simulate candump printout
    printf("w 0x%03X", frame->can_id);
    printf(" [%d]", frame->can_dlc);
    for(int i=0; i<frame->can_dlc; i++ ) {
      printf(" 0x%02X", frame->data[i]);
    }
    printf("\n");
  }
  return 0;
}

// Avoid issues with std:to_string()
std::string ecmcSocketCANWriteBuffer::to_string(int value) {
  std::ostringstream os;
  os << value;
  return os.str();
}
