/*
 * I/O driver for serial port streams
 *
 * Copyright 2014 Przemyslaw Czerpak <druzus / at / priv.onet.pl>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file LICENSE.txt.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA (or visit https://www.gnu.org/licenses/).
 *
 * As a special exception, the Harbour Project gives permission for
 * additional uses of the text contained in its release of Harbour.
 *
 * The exception is that, if you link the Harbour libraries with other
 * files to produce an executable, this does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * Your use of that executable is in no way restricted on account of
 * linking the Harbour library code into it.
 *
 * This exception does not however invalidate any other reasons why
 * the executable file might be covered by the GNU General Public License.
 *
 * This exception applies only to the code released by the Harbour
 * Project under the name Harbour.  If you copy code from other
 * Harbour Project or Free Software Foundation releases into a copy of
 * Harbour, as the General Public License permits, the exception does
 * not apply to the code that you add in this way.  To avoid misleading
 * anyone as to the status of such modified files, you must delete
 * this exception notice from them.
 *
 * If you write modifications of your own for Harbour, it is your choice
 * whether to permit this exception to apply to your modifications.
 * If you do not wish that, delete this exception notice.
 *
 */

/* this has to be declared before hbapifs.h is included */
#define _HB_FILE_IMPLEMENTATION_

#include "hbapi.h"
#include "hbapifs.h"
#include "hbapiitm.h"
#include "hbapierr.h"
#include "hbinit.h"

#include "hbapicom.h"

typedef struct _HB_FILE
{
   const HB_FILE_FUNCS * pFuncs;
   int                   port;
   HB_MAXINT             timeout;
   HB_BOOL               fRead;
   HB_BOOL               fWrite;
} HB_FILE;

static PHB_FILE s_fileNew( int port, HB_MAXINT timeout, HB_BOOL fRead, HB_BOOL fWrite );

static int s_fileGetValue( const char * pszName, int * piLen )
{
   int iLen = 0, iValue = 0;

   while( HB_ISDIGIT( pszName[ iLen ] ) )
      iValue = iValue * 10 + ( pszName[ iLen++ ] - '0' );

   *piLen = iLen;
   return iValue;
}

static int s_filePortParams( const char * pszName, HB_MAXINT * pTimeout,
                             int * piBaud, int * piParity,
                             int * piSize, int * piStop,
                             int * piFlow )
{
   int iPort = 0, iLen, iValue;

   *pTimeout = -1;
   *piBaud = *piParity = *piSize = *piStop = *piFlow = 0;

   pszName += 3;
   if( *pszName == '$' )
   {
      const char * pszParams = strchr( pszName, ':' );

      if( pszParams != NULL && pszParams - pszName > 1 )
      {
         char * pszPort = hb_strndup( pszName + 1, pszParams - pszName - 1 );

         iPort = hb_comFindPort( pszPort, HB_TRUE );
         hb_xfree( pszPort );
         pszName = pszParams;
      }
   }
   else
   {
      while( HB_ISDIGIT( *pszName ) )
         iPort = iPort * 10 + ( *pszName++ - '0' );
   }

   while( iPort > 0 && *pszName )
   {
      if( HB_ISDIGIT( *pszName ) )
      {
         iValue = s_fileGetValue( pszName, &iLen );
         if( iLen == 1 )
         {
            if( iValue >= 1 && iValue <= 2 && *piStop == 0 )
               *piStop = iValue;
            else if( iValue >= 5 && iValue <= 8 && *piSize == 0 )
               *piSize = iValue;
            else
               iPort = -1;
         }
         else if( iLen == 2 && *piStop == 0 && *piSize == 0 )
         {
            if( pszName[ 0 ] >= '1' && pszName[ 0 ] <= '2' &&
                pszName[ 1 ] >= '5' && pszName[ 1 ] <= '8' )
            {
               *piStop = pszName[ 0 ] - '0';
               *piSize = pszName[ 1 ] - '0';
            }
            else if( pszName[ 0 ] >= '5' && pszName[ 0 ] <= '8' &&
                     pszName[ 1 ] >= '1' && pszName[ 1 ] <= '2' )
            {
               *piStop = pszName[ 1 ] - '0';
               *piSize = pszName[ 0 ] - '0';
            }
            else if( *piBaud )
               iPort = -1;
            else
               *piBaud = iValue;
         }
         else if( *piBaud )
            iPort = -1;
         else
            *piBaud = iValue;
         pszName += iLen;
      }
      else if( HB_ISALPHA( *pszName ) )
      {
         if( hb_strnicmp( pszName, "RTS", 3 ) == 0 )
         {
            *piFlow |= HB_COM_FLOW_IRTSCTS;
            pszName += 3;
         }
         else if( hb_strnicmp( pszName, "CTS", 3 ) == 0 )
         {
            *piFlow |= HB_COM_FLOW_ORTSCTS;
            pszName += 3;
         }
         else if( hb_strnicmp( pszName, "DTR", 3 ) == 0 )
         {
            *piFlow |= HB_COM_FLOW_IDTRDSR;
            pszName += 3;
         }
         else if( hb_strnicmp( pszName, "DSR", 3 ) == 0 )
         {
            *piFlow |= HB_COM_FLOW_ODTRDSR;
            pszName += 3;
         }
         else if( hb_strnicmp( pszName, "DCD", 3 ) == 0 )
         {
            *piFlow |= HB_COM_FLOW_DCD;
            pszName += 3;
         }
         else if( hb_strnicmp( pszName, "XOFF", 4 ) == 0 )
         {
            *piFlow |= HB_COM_FLOW_XOFF;
            pszName += 4;
         }
         else if( hb_strnicmp( pszName, "XON", 3 ) == 0 )
         {
            *piFlow |= HB_COM_FLOW_XON;
            pszName += 3;
         }
         else if( *piParity == 0 && ! HB_ISALPHA( pszName[ 1 ] ) )
         {
            switch( *pszName )
            {
               case 'N':
               case 'n':
               case 'E':
               case 'e':
               case 'O':
               case 'o':
               case 'S':
               case 's':
               case 'M':
               case 'm':
                  *piParity = HB_TOUPPER( *pszName );
                  pszName++;
                  break;
               default:
                  iPort = -1;
                  break;
            }
         }
         else
            iPort = -1;
      }
      else if( *pszName == ':' || *pszName == ',' || *pszName == ' ' )
         pszName++;
      else
         iPort = -1;
   }

   if( *piBaud == 0 )
      *piBaud = 9600;
   if( *piParity == 0 )
      *piParity = 'N';
   if( *piSize == 0 )
      *piSize = 8;
   if( *piStop == 0 )
      *piStop = 1;

   return iPort;
}

static HB_BOOL s_fileAccept( PHB_FILE_FUNCS pFuncs, const char * pszFileName )
{
   HB_SYMBOL_UNUSED( pFuncs );

   if( HB_TOUPPER( pszFileName[ 0 ] ) == 'C' &&
       HB_TOUPPER( pszFileName[ 1 ] ) == 'O' &&
       HB_TOUPPER( pszFileName[ 2 ] ) == 'M' )
   {
      if( pszFileName[ 3 ] == '$' )
         return strchr( pszFileName + 4, ':' ) != NULL;
      else if( pszFileName[ 3 ] >= '1' && pszFileName[ 3 ] <= '9' )
      {
         pszFileName += 4;
         while( HB_ISDIGIT( *pszFileName ) )
            ++pszFileName;
         return *pszFileName == ':';
      }
   }
   return HB_FALSE;
}

static PHB_FILE s_fileOpen( PHB_FILE_FUNCS pFuncs, const char * pszName,
                            const char * pszDefExt, HB_FATTR nExFlags,
                            const char * pPaths, PHB_ITEM pError )
{
   PHB_FILE pFile = NULL;
   HB_ERRCODE errcode = 0;
   int iPort, iBaud, iParity, iSize, iStop, iFlow;
   HB_BOOL fRead, fWrite;
   HB_MAXINT timeout;

   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pszDefExt );
   HB_SYMBOL_UNUSED( pPaths );

   fRead = fWrite = HB_TRUE;
   iPort = s_filePortParams( pszName, &timeout,
                             &iBaud, &iParity, &iSize, &iStop, &iFlow );
   if( iPort > 0 )
   {
      if( hb_comOpen( iPort ) == 0 &&
          hb_comInit( iPort, iBaud, iParity, iSize, iStop ) == 0 &&
          hb_comFlowControl( iPort, NULL, iFlow ) == 0 )
      {
         switch( nExFlags & ( FO_READ | FO_WRITE | FO_READWRITE ) )
         {
            case FO_READ:
               fWrite = HB_FALSE;
               break;
            case FO_WRITE:
               fRead = HB_FALSE;
               break;
         }
         pFile = s_fileNew( iPort, timeout, fRead, fWrite );
      }
      else
         errcode = hb_comGetError( iPort );
   }
   else
      errcode = HB_COM_ERR_WRONGPORT;

   hb_fsSetError( errcode );

   if( pError )
   {
      hb_errPutFileName( pError, pszName );
      if( pFile == NULL )
      {
         hb_errPutOsCode( pError, errcode );
         hb_errPutGenCode( pError, ( HB_ERRCODE ) EG_OPEN );
      }
   }

   return pFile;
}

static void s_fileClose( PHB_FILE pFile )
{
   hb_comClose( pFile->port );
   hb_fsSetError( hb_comGetError( pFile->port ) );
   hb_xfree( pFile );
}

static HB_SIZE s_fileRead( PHB_FILE pFile, void * data,
                           HB_SIZE nSize, HB_MAXINT timeout )
{
   HB_ERRCODE errcode;
   long lRead = -1;

   if( pFile->fRead )
   {
      lRead = nSize > LONG_MAX ? LONG_MAX : ( long ) nSize;
      if( timeout == -1 )
         timeout = pFile->timeout;
      lRead = hb_comRecv( pFile->port, data, lRead, timeout );
      errcode = hb_comGetError( pFile->port );
      if( lRead <= 0 && errcode == HB_COM_ERR_TIMEOUT )
         lRead = 0;
      else if( lRead == 0 )
         lRead = -1;
   }
   else
      errcode = HB_COM_ERR_ACCESS;

   hb_fsSetError( errcode );

   return lRead;
}

static HB_SIZE s_fileWrite( PHB_FILE pFile, const void * data,
                            HB_SIZE nSize, HB_MAXINT timeout )
{
   HB_ERRCODE errcode;
   long lSent = -1;

   if( pFile->fWrite )
   {
      lSent = nSize > LONG_MAX ? LONG_MAX : ( long ) nSize;
      if( timeout == -1 )
         timeout = pFile->timeout;
      lSent = hb_comSend( pFile->port, data, lSent, timeout );
      errcode = hb_comGetError( pFile->port );
      if( lSent <= 0 && errcode == HB_COM_ERR_TIMEOUT )
         lSent = 0;
      else if( lSent == 0 )
         lSent = -1;
   }
   else
      errcode = HB_COM_ERR_ACCESS;

   hb_fsSetError( errcode );

   return lSent;
}

static HB_BOOL s_fileConfigure( PHB_FILE pFile, int iIndex, PHB_ITEM pValue )
{
   switch( iIndex )
   {
      case HB_VF_TIMEOUT:
      {
         HB_MAXINT timeout = pFile->timeout;

         if( HB_IS_NUMERIC( pValue ) )
            pFile->timeout = hb_itemGetNInt( pValue );
         hb_itemPutNInt( pValue, timeout );
         return HB_TRUE;
      }

      case HB_VF_PORT:
         hb_itemPutNInt( pValue, pFile->port );
         return HB_TRUE;

      case HB_VF_SHUTDOWN:
      {
         int iMode = pFile->fRead ?
                     ( pFile->fWrite ? FO_READWRITE : FO_READ ) :
                     ( pFile->fWrite ? FO_WRITE : -1 );

         if( HB_IS_NUMERIC( pValue ) )
         {
            switch( hb_itemGetNI( pValue ) )
            {
               case FO_READ:
                  pFile->fRead = HB_FALSE;
                  break;
               case FO_WRITE:
                  pFile->fWrite = HB_FALSE;
                  break;
               case FO_READWRITE:
                  pFile->fRead = pFile->fWrite = HB_FALSE;
                  break;
            }
         }
         hb_itemPutNI( pValue, iMode );
         return HB_TRUE;
      }
      case HB_VF_RDHANDLE:
      case HB_VF_WRHANDLE:
         hb_itemPutNInt( pValue, ( HB_NHANDLE ) hb_comGetDeviceHandle( pFile->port ) );
         return HB_TRUE;

      case HB_VF_IONAME:
         hb_itemPutC( pValue, "COM:" );
         return HB_TRUE;
   }

   return HB_FALSE;
}

static HB_FHANDLE s_fileHandle( PHB_FILE pFile )
{
   return pFile ? hb_comGetDeviceHandle( pFile->port ) : FS_ERROR;
}

static HB_FILE_FUNCS s_fileFuncs =
{
   s_fileAccept,

   NULL, /* s_fileExists */
   NULL, /* s_fileDelete */
   NULL, /* s_fileRename */
   NULL, /* s_fileCopy */

   NULL, /* s_fileDirExists */
   NULL, /* s_fileDirMake */
   NULL, /* s_fileDirRemove */
   NULL, /* s_fileDirSpace */
   NULL, /* s_fileDirectory */

   NULL, /* s_fileTimeGet */
   NULL, /* s_fileTimeSet */
   NULL, /* s_fileAttrGet */
   NULL, /* s_fileAttrSet */

   NULL, /* s_fileLink */
   NULL, /* s_fileLinkSym */
   NULL, /* s_fileLinkRead */

   s_fileOpen,
   s_fileClose,
   NULL, /* s_fileLock */
   NULL, /* s_fileLockTest */
   s_fileRead,
   s_fileWrite,
   NULL, /* s_fileReadAt */
   NULL, /* s_fileWriteAt */
   NULL, /* s_fileTruncAt */
   NULL, /* s_fileSeek */
   NULL, /* s_fileSize */
   NULL, /* s_fileEof */
   NULL, /* s_fileFlush */
   NULL, /* s_fileCommit */
   s_fileConfigure,
   s_fileHandle
};

static PHB_FILE s_fileNew( int port, HB_MAXINT timeout, HB_BOOL fRead, HB_BOOL fWrite )
{
   PHB_FILE pFile = ( PHB_FILE ) hb_xgrab( sizeof( HB_FILE ) );

   pFile->pFuncs  = &s_fileFuncs;
   pFile->port    = port;
   pFile->timeout = timeout;
   pFile->fRead   = fRead;
   pFile->fWrite  = fWrite;

   return pFile;
}

HB_FUNC( HB_COMIO ) { ; }


HB_CALL_ON_STARTUP_BEGIN( _hb_file_comio_init_ )
   hb_fileRegisterPart( &s_fileFuncs );
HB_CALL_ON_STARTUP_END( _hb_file_comio_init_ )

#if defined( HB_PRAGMA_STARTUP )
   #pragma startup _hb_file_comio_init_
#elif defined( HB_DATASEG_STARTUP )
   #define HB_DATASEG_BODY  HB_DATASEG_FUNC( _hb_file_comio_init_ )
   #include "hbiniseg.h"
#endif
