/****************************************************************************
 * FreeModbus Library: A portable Modbus implementation for Modbus ASCII/RTU.
 *
 *   Copyright (C) 2013 Armink <armink.ztl@gmail.com>
 *   All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#include <stdlib.h>
#include <string.h>

#include "port.h"

#include "modbus/mb.h"
#include "modbus/mb_m.h"
#include "modbus/mbframe.h"
#include "modbus/mbproto.h"
#include "modbus/mbfunc.h"

#include "modbus/mbport.h"

#ifdef CONFIG_MB_RTU_MASTER
#  include "mbrtu_m.h"
#endif

#ifdef CONFIG_MB_ASCII_MASTER
#  include "mbascii.h"
#endif

#ifdef CONFIG_MB_TCP_MASTER
#  include "mbtcp.h"
#endif

#if defined(CONFIG_MB_RTU_MASTER) || defined(CONFIG_MB_ASCII_MASTER)

#ifndef CONFIG_MB_PORT_HAS_CLOSE
#  define MB_PORT_HAS_CLOSE 0
#else
#  define MB_PORT_HAS_CLOSE 1
#endif

/****************************************************************************
 * Private Data
 ****************************************************************************/

static uint8_t ucMBMasterDestAddress;
static bool xMBRunInMasterMode = false;
static eMBMasterErrorEventType eMBMasterCurErrorType;

static enum
{
  STATE_ENABLED,
  STATE_DISABLED,
  STATE_NOT_INITIALIZED
} eMBState = STATE_NOT_INITIALIZED;

/* Functions pointer which are initialized in eMBInit( ). Depending on the
 * mode (RTU or ASCII) the are set to the correct implementations.
 * Using for Modbus Master,Add by Armink 20130813
 */

static peMBFrameSend peMBMasterFrameSendCur;
static pvMBFrameStart pvMBMasterFrameStartCur;
static pvMBFrameStop pvMBMasterFrameStopCur;
static peMBFrameReceive peMBMasterFrameReceiveCur;
static pvMBFrameClose pvMBMasterFrameCloseCur;

/* Callback functions required by the porting layer. They are called when
 * an external event has happened which includes a timeout or the reception
 * or transmission of a character.
 * Using for Modbus Master,Add by Armink 20130813
 */

bool(*pxMBMasterFrameCBByteReceived) (void);
bool(*pxMBMasterFrameCBTransmitterEmpty) (void);
bool(*pxMBMasterPortCBTimerExpired) (void);

bool(*pxMBMasterFrameCBReceiveFSMCur) (void);
bool(*pxMBMasterFrameCBTransmitFSMCur) (void);

/* An array of Modbus functions handlers which associates Modbus function
 * codes with implementing functions.
 */

static xMBFunctionHandler xMasterFuncHandlers[CONFIG_MB_FUNC_HANDLERS_MAX] = {
#ifdef CONFIG_MB_FUNC_OTHER_REP_SLAVEID_ENABLED

  /* TODO Add Master function define */

  {MB_FUNC_OTHER_REPORT_SLAVEID, eMBFuncReportSlaveID},
#endif
#ifdef CONFIG_MB_MASTER_FUNC_READ_INPUT_ENABLED
  {MB_FUNC_READ_INPUT_REGISTER, eMBMasterFuncReadInputRegister},
#endif
#ifdef CONFIG_MB_MASTER_FUNC_READ_HOLDING_ENABLED
  {MB_FUNC_READ_HOLDING_REGISTER, eMBMasterFuncReadHoldingRegister},
#endif
#ifdef CONFIG_MB_MASTER_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
  {MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBMasterFuncWriteMultipleHoldingRegister},
#endif
#ifdef CONFIG_MB_MASTER_FUNC_WRITE_HOLDING_ENABLED
  {MB_FUNC_WRITE_REGISTER, eMBMasterFuncWriteHoldingRegister},
#endif
#ifdef CONFIG_MB_MASTER_FUNC_READWRITE_HOLDING_ENABLED
  {MB_FUNC_READWRITE_MULTIPLE_REGISTERS,
   eMBMasterFuncReadWriteMultipleHoldingRegister},
#endif
#ifdef CONFIG_MB_MASTER_FUNC_READ_COILS_ENABLED
  {MB_FUNC_READ_COILS, eMBMasterFuncReadCoils},
#endif
#ifdef CONFIG_MB_MASTER_FUNC_WRITE_COIL_ENABLED
  {MB_FUNC_WRITE_SINGLE_COIL, eMBMasterFuncWriteCoil},
#endif
#ifdef CONFIG_MB_MASTER_FUNC_WRITE_MULTIPLE_COILS_ENABLED
  {MB_FUNC_WRITE_MULTIPLE_COILS, eMBMasterFuncWriteMultipleCoils},
#endif
#ifdef CONFIG_MB_MASTER_FUNC_READ_DISCRETE_INPUTS_ENABLED
  {MB_FUNC_READ_DISCRETE_INPUTS, eMBMasterFuncReadDiscreteInputs},
#endif
};

/****************************************************************************
 * Public Functions
 ****************************************************************************/

eMBErrorCode eMBMasterInit(eMBMode eMode, uint8_t ucPort,
                           speed_t ulBaudRate, eMBParity eParity)
{
  eMBErrorCode eStatus = MB_ENOERR;

  switch (eMode)
    {
#ifdef CONFIG_MB_RTU_MASTER
    case MB_RTU:
      pvMBMasterFrameStartCur = eMBMasterRTUStart;
      pvMBMasterFrameStopCur = eMBMasterRTUStop;
      peMBMasterFrameSendCur = eMBMasterRTUSend;
      peMBMasterFrameReceiveCur = eMBMasterRTUReceive;
      pvMBMasterFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBMasterPortClose : NULL;
      pxMBMasterFrameCBByteReceived = xMBMasterRTUReceiveFSM;
      pxMBMasterFrameCBTransmitterEmpty = xMBMasterRTUTransmitFSM;
      pxMBMasterPortCBTimerExpired = xMBMasterRTUTimerExpired;

      eStatus = eMBMasterRTUInit(ucPort, ulBaudRate, eParity);
      break;
#endif
#ifdef CONFIG_MB_ASCII_MASTER
    case MB_ASCII:
      pvMBMasterFrameStartCur = eMBMasterASCIIStart;
      pvMBMasterFrameStopCur = eMBMasterASCIIStop;
      peMBMasterFrameSendCur = eMBMasterASCIISend;
      peMBMasterFrameReceiveCur = eMBMasterASCIIReceive;
      pvMBMasterFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBMasterPortClose : NULL;
      pxMBMasterFrameCBByteReceived = xMBMasterASCIIReceiveFSM;
      pxMBMasterFrameCBTransmitterEmpty = xMBMasterASCIITransmitFSM;
      pxMBMasterPortCBTimerExpired = xMBMasterASCIITimerT1SExpired;

      eStatus = eMBMasterASCIIInit(ucPort, ulBaudRate, eParity);
      break;
#endif
    default:
      eStatus = MB_EINVAL;
      break;
    }

  if (eStatus == MB_ENOERR)
    {
      if (!xMBMasterPortEventInit())
        {
          /* Port dependent event module initialization failed. */

          eStatus = MB_EPORTERR;
        }
      else
        {
          eMBState = STATE_DISABLED;
        }

      /* Initialize the OS resource for modbus master. */

      vMBMasterOsResInit();
    }

  return eStatus;
}

eMBErrorCode eMBMasterClose(void)
{
  eMBErrorCode eStatus = MB_ENOERR;

  if (eMBState == STATE_DISABLED)
    {
      if (pvMBMasterFrameCloseCur != NULL)
        {
          pvMBMasterFrameCloseCur();
        }
    }
  else
    {
      eStatus = MB_EILLSTATE;
    }

  return eStatus;
}

eMBErrorCode eMBMasterEnable(void)
{
  eMBErrorCode eStatus = MB_ENOERR;

  if (eMBState == STATE_DISABLED)
    {
      /* Activate the protocol stack. */

      pvMBMasterFrameStartCur();
      eMBState = STATE_ENABLED;
    }
  else
    {
      eStatus = MB_EILLSTATE;
    }

  return eStatus;
}

eMBErrorCode eMBMasterDisable(void)
{
  eMBErrorCode eStatus;

  if (eMBState == STATE_ENABLED)
    {
      pvMBMasterFrameStopCur();
      eMBState = STATE_DISABLED;
      eStatus = MB_ENOERR;
    }
  else if (eMBState == STATE_DISABLED)
    {
      eStatus = MB_ENOERR;
    }
  else
    {
      eStatus = MB_EILLSTATE;
    }

  return eStatus;
}

eMBErrorCode eMBMasterPoll(void)
{
  static uint8_t *ucMBFrame;
  static uint8_t ucRcvAddress;
  static uint8_t ucFunctionCode;
  static uint16_t usLength;
  static eMBException eException;
  int i;
  int j;

  eMBErrorCode eStatus = MB_ENOERR;
  eMBMasterEventType eEvent;
  eMBMasterErrorEventType errorType;

  /* Check if the protocol stack is ready. */

  if (eMBState != STATE_ENABLED)
    {
      return MB_EILLSTATE;
    }

  /* Check if there is a event available. If not return control to caller.
   * Otherwise we will handle the event.
   */

  if (xMBMasterPortEventGet(&eEvent) == true)
    {
      switch (eEvent)
        {
        case EV_MASTER_READY:
          break;

        case EV_MASTER_FRAME_RECEIVED:
          eStatus =
            peMBMasterFrameReceiveCur(&ucRcvAddress, &ucMBFrame, &usLength);

          /* Check if the frame is for us. If not, send an error process event. */

          if ((eStatus == MB_ENOERR) &&
              (ucRcvAddress == ucMBMasterGetDestAddress()))
            {
              xMBMasterPortEventPost(EV_MASTER_EXECUTE);
            }
          else
            {
              vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA);
              xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS);
            }
          break;

        case EV_MASTER_EXECUTE:
          ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];
          eException = MB_EX_ILLEGAL_FUNCTION;

          /* If receive frame has exception. The receive function code highest
           * bit is 1.
           */

          if (ucFunctionCode >> 7)
            {
              eException = (eMBException) ucMBFrame[MB_PDU_DATA_OFF];
            }
          else
            {
              for (i = 0; i < CONFIG_MB_FUNC_HANDLERS_MAX; i++)
                {
                  /* No more function handlers registered. Abort. */

                  if (xMasterFuncHandlers[i].ucFunctionCode == 0)
                    {
                      break;
                    }
                  else if (xMasterFuncHandlers[i].ucFunctionCode ==
                           ucFunctionCode)
                    {
                      vMBMasterSetCBRunInMasterMode(true);

                      /* If master request is broadcast, the master need
                       * execute function for all slave. */

                      if (xMBMasterRequestIsBroadcast())
                        {
                          usLength = usMBMasterGetPDUSndLength();
                          for (j = 1; j <= CONFIG_MB_MASTER_TOTAL_SLAVE_NUM;
                               j++)
                            {
                              vMBMasterSetDestAddress(j);
                              eException =
                                xMasterFuncHandlers[i].pxHandler(ucMBFrame,
                                                                 &usLength);
                            }
                        }
                      else
                        {
                          eException =
                            xMasterFuncHandlers[i].pxHandler(ucMBFrame,
                                                             &usLength);
                        }

                      vMBMasterSetCBRunInMasterMode(false);
                      break;
                    }
                }
            }

          /* If master has exception, Master will send error process. Otherwise
           * the Master is idle.
           */

          if (eException != MB_EX_NONE)
            {
              vMBMasterSetErrorType(EV_ERROR_EXECUTE_FUNCTION);
              xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS);
            }
          else
            {
              vMBMasterCBRequestSuccess();
              vMBMasterRunResRelease();
            }
          break;

        case EV_MASTER_FRAME_SENT:

          /* Master is busy now. */

          vMBMasterGetPDUSndBuf(&ucMBFrame);
          eStatus =
            peMBMasterFrameSendCur(ucMBMasterGetDestAddress(), ucMBFrame,
                                   usMBMasterGetPDUSndLength());
          break;

        case EV_MASTER_ERROR_PROCESS:

          /* Execute specified error process callback function. */

          errorType = eMBMasterGetErrorType();
          vMBMasterGetPDUSndBuf(&ucMBFrame);
          switch (errorType)
            {
            case EV_ERROR_RESPOND_TIMEOUT:
              vMBMasterErrorCBRespondTimeout(ucMBMasterGetDestAddress(),
                                             ucMBFrame,
                                             usMBMasterGetPDUSndLength());
              break;
            case EV_ERROR_RECEIVE_DATA:
              vMBMasterErrorCBReceiveData(ucMBMasterGetDestAddress(),
                                          ucMBFrame,
                                          usMBMasterGetPDUSndLength());
              break;
            case EV_ERROR_EXECUTE_FUNCTION:
              vMBMasterErrorCBExecuteFunction(ucMBMasterGetDestAddress(),
                                              ucMBFrame,
                                              usMBMasterGetPDUSndLength());
              break;
            }

          vMBMasterRunResRelease();
          break;
        }
    }

  return MB_ENOERR;
}

/* Get whether the Modbus Master is run in master mode.*/

bool xMBMasterGetCBRunInMasterMode(void)
{
  return xMBRunInMasterMode;
}

/* Set whether the Modbus Master is run in master mode.*/

void vMBMasterSetCBRunInMasterMode(bool IsMasterMode)
{
  xMBRunInMasterMode = IsMasterMode;
}

/* Get Modbus Master send destination address. */

uint8_t ucMBMasterGetDestAddress(void)
{
  return ucMBMasterDestAddress;
}

/* Set Modbus Master send destination address. */

void vMBMasterSetDestAddress(uint8_t Address)
{
  ucMBMasterDestAddress = Address;
}

/* Get Modbus Master current error event type. */

eMBMasterErrorEventType eMBMasterGetErrorType(void)
{
  return eMBMasterCurErrorType;
}

/* Set Modbus Master current error event type. */

void vMBMasterSetErrorType(eMBMasterErrorEventType errorType)
{
  eMBMasterCurErrorType = errorType;
}

#endif /* defined(CONFIG_MB_RTU_MASTER) || defined(CONFIG_MB_ASCII_MASTER) */