#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"
#include "cwText.h"
#include "cwTextBuf.h"

#include "cwIo.h"

#include "cwMidi.h"
#include "cwMidiPort.h"


#include "cwObject.h"

#include "cwThread.h"
#include "cwSerialPort.h"
#include "cwSerialPortSrv.h"

#include "cwAudioDevice.h"
#include "cwAudioBuf.h"
#include "cwAudioDeviceAlsa.h"


namespace cw
{
  namespace io
  {

    typedef struct serialPort_str
    {
      char*                   name;
      char*                   device;
      unsigned                baudRate;
      unsigned                flags;
      unsigned                pollPeriodMs;      
      serialPortSrv::handle_t serialH;
    } serialPort_t;

    typedef struct audioCfg_str
    {
      unsigned enableFl;
      char*    name;
      char*    device;
      double   srate;
      unsigned dspFrameCnt;
      unsigned cycleCnt;
      unsigned devIdx;
    } audioCfg_t;
    
    typedef struct io_str
    {
      cbFunc_t               cbFunc;
      void*                  cbArg;
      
      thread::handle_t       threadH;
      
      serialPort_t*          serialA;
      unsigned               serialN;
      
      midi::device::handle_t midiH;

      audio::device::handle_t       audioH;
      audio::device::alsa::handle_t alsaH;
      
      unsigned    audioCfgN;
      audioCfg_t* audioCfgA;      
      
    } io_t;
  

    io_t* _handleToPtr( handle_t h )
    { return handleToPtr<handle_t,io_t>(h); }

    rc_t _destroy( io_t* p )
    {
      rc_t rc = kOkRC;

      if((rc = thread::destroy(p->threadH)) != kOkRC )
        return rc;

      mem::release(p);

      return rc;
    }
  
    bool _mainThreadFunc( void* arg )
    {
      //io_t* p = static_cast<io_t*>(arg);


      return true;
    }

    void _serialPortCb( void* arg, const void* byteA, unsigned byteN )
    {
      //io_t* p = static_cast<io_t*>(arg);
      
    }

    rc_t _serialPortParseCfg( const object_t& e, serialPort_t* port )
    {
      rc_t        rc          = kOkRC;
      char*       parityLabel = nullptr;
      unsigned    bits        = 8;
      unsigned    stop        = 1;
      
      idLabelPair_t parityA[] =
        {
         { serialPort::kEvenParityFl, "even" },
         { serialPort::kOddParityFl,  "odd"  },
         { serialPort::kNoParityFl,   "no"   },
         { 0, nullptr }
        };
             
      if((rc = e.getv(
            "name",         port->name,
            "device",       port->device,
            "baud",         port->baudRate,
            "bits",         bits,
            "stop",         stop,
            "parity",       parityLabel,
            "pollPeriodMs", port->pollPeriodMs )) != kOkRC )
      {
        rc = cwLogError(kSyntaxErrorRC,"Serial configuration parse failed.");
      }

      switch( bits )
      {
        case 5:  port->flags |= serialPort::kDataBits5Fl;
        case 6:  port->flags |= serialPort::kDataBits6Fl;
        case 7:  port->flags |= serialPort::kDataBits7Fl;
        case 8:  port->flags |= serialPort::kDataBits8Fl;
        default:
          rc = cwLogError(kSyntaxErrorRC,"Invalid serial data bits cfg:%i.",bits);
      }

      switch( stop )
      {
        case 1: port->flags |= serialPort::k1StopBitFl;
        case 2: port->flags |= serialPort::k2StopBitFl;
        default:
          rc = cwLogError(kSyntaxErrorRC,"Invalid serial stop bits cfg:%i.",stop);
      }

      unsigned i;
      for(i=0; parityA[i].label != nullptr; ++i)
        if( textCompare(parityLabel,parityA[i].label) == 0 )
        {
          port->flags |= parityA[i].id;
          break;
        }

      if( parityA[i].label == nullptr )
        rc = cwLogError(kSyntaxErrorRC,"Invalid parity cfg:'%s'.",cwStringNullGuard(parityLabel));
      

      return rc;
    }
        
    rc_t _serialPortCreate( io_t* p, const object_t* c )
    {
      rc_t            rc   = kOkRC;
      const object_t* cfgL = nullptr;

      // get the serial port list node
      if((cfgL = c->find("serial")) == nullptr || !cfgL->is_list())
        return cwLogError(kSyntaxErrorRC,"Unable to locate the 'serial' configuration list.");

      p->serialN = cfgL->child_count();
      p->serialA = mem::allocZ<serialPort_t>(p->serialN);
      
      // for each serial port cfg
      for(unsigned i=0; i<p->serialN; ++i)
      {
        const object_t* e = cfgL->child_ele(i);
        serialPort_t*   r = p->serialA + i;
        
        if( e == nullptr )
        {
          rc = cwLogError(kSyntaxErrorRC,"Unable to access a 'serial' port configuration record at index:%i.",i);
          break;
        }
        else
        {
          // parse the cfg record
          if((rc = _serialPortParseCfg(*e,r)) != kOkRC )
          {
            rc = cwLogError(rc,"Serial configuration parse failed on record index:%i.", i );
            break;
          }

          /*
          // create the serial port object
          if((rc = serialPortSrv::create( r->serialH, r->device, r->baudRate, r->flags, _serialPortCb, p, r->pollPeriodMs )) != kOkRC )
          {
            rc = cwLogError(rc,"Serial port create failed on record index:%i.", i );
            break;
          }
          */
        }
      }

      return rc;
    }


    void _midiCallback( const midi::packet_t* pktArray, unsigned pktCnt )
    {
      unsigned i,j;
      for(i=0; i<pktCnt; ++i)
      {
        const midi::packet_t* pkt = pktArray + i;
        
        //io_t* p = static_cast<io_t*>(pkt->cbDataPtr);

        for(j=0; j<pkt->msgCnt; ++j)
          if( pkt->msgArray != NULL )
            printf("%ld %ld 0x%x %i %i\n", pkt->msgArray[j].timeStamp.tv_sec, pkt->msgArray[j].timeStamp.tv_nsec, pkt->msgArray[j].status,pkt->msgArray[j].d0, pkt->msgArray[j].d1);
          else
            printf("0x%x ",pkt->sysExMsg[j]);

      }
    }
    
    rc_t _midiPortCreate( io_t* p, const object_t*& c )
    {
      rc_t     rc             = kOkRC;
      unsigned parserBufByteN = 1024;
      
      if((rc = c->getv(
            "parserBufByteN", parserBufByteN )) != kOkRC )
      {
        rc = cwLogError(kSyntaxErrorRC,"MIDI configuration parse failed.");
      }
          
      // initialie the MIDI system
      if((rc = create(p->midiH, _midiCallback, p, parserBufByteN, "app")) != kOkRC )
        return rc;

      
      return rc;
    }

    void _audioDeviceCallback( void* cbArg, audio::device::audioPacket_t* inPktArray, unsigned inPktCnt, audio::device::audioPacket_t* outPktArray, unsigned outPktCnt )
    {
      //io_t* p = (io_t*)cbArg;
      
    }

    rc_t _audioDeviceConfig( io_t* p, const object_t* c )
    {
      rc_t            rc      = kOkRC;
      unsigned        meterMs = 50;
      const object_t* node    = nullptr;
      const object_t* deviceL = nullptr;
      
      // get the audio port node
      if((node = c->find("audio")) == nullptr )
        return cwLogError(kSyntaxErrorRC,"Unable to locate the 'audio' configuration node.");

      // get the meterMs value
      if((rc = node->get("meterMs", meterMs )) != kOkRC )
      {
        rc = cwLogError(kSyntaxErrorRC,"Audio 'meterMs' parse failed.");
        goto errLabel;
      }

      // get the audio device list
      if((deviceL = node->find("deviceL")) == nullptr )
      {
        rc = cwLogError(kSyntaxErrorRC,"Audio 'deviceL' failed.");
        goto errLabel;
      }

      // create an audio device cfg list
      p->audioCfgN = deviceL->child_count();
      p->audioCfgA = mem::allocZ<audioCfg_t>(p->audioCfgN);

      // fill in the audio device cfg list
      for(unsigned i=0; i<p->audioCfgN; ++i)
      {
        audioCfg_t* r = p->audioCfgA + i;
        if((node = deviceL->child_ele(i)) == nullptr )
        {
          if(( rc = node->getv(
                "enableFl",    r->enableFl,
                "name",        r->name,
                "device",      r->device,
                "srate",       r->srate,
                "dspFrameCnt", r->dspFrameCnt,
                "cycleCnt",    r->cycleCnt )) != kOkRC )
          {
            rc = cwLogError(rc,"Error parsing audio cfg record at index:%i",i);
            goto errLabel;
          }          
        }

        // if the configuration is enabled
        if( r->enableFl )
        {
          // get the hardware device index
          if((r->devIdx = audio::device::labelToIndex( p->audioH, r->device)) == kInvalidIdx )
          {
            rc = cwLogError(rc,"Unable to locate the audio hardware device:'%s'.", r->device);
            goto errLabel;
          }

          // setup the device based on the configuration
          if((rc = audio::device::setup(p->audioH,r->devIdx,r->srate,r->dspFrameCnt,_audioDeviceCallback,p)) != kOkRC )
          {
            rc = cwLogError(rc,"Unable to setup the audio hardware device:'%s'.", r->device);
            goto errLabel;
          }          
        }
      }

    errLabel:
      return rc;
    }


    rc_t _audioDeviceCreate( io_t* p, const object_t*& c )
    {      
      rc_t                     rc       = kOkRC;
      audio::device::driver_t* audioDrv = nullptr;

      // initialize the audio device interface  
      if((rc = audio::device::create(p->audioH)) != kOkRC )
      {
        cwLogInfo("Initialize failed.");
        goto errLabel;
      }

      // initialize the ALSA device driver interface
      if((rc = audio::device::alsa::create(p->alsaH, audioDrv )) != kOkRC )
      {
        cwLogInfo("ALSA initialize failed.");
        goto errLabel;
      }

      // register the ALSA device driver with the audio interface
      if((rc = audio::device::registerDriver( p->audioH, audioDrv )) != kOkRC )
      {
        cwLogInfo("ALSA driver registration failed.");
        goto errLabel;
      }

      // read the configuration information and setup the audio hardware
      if((rc = _audioDeviceConfig( p, c )) != kOkRC )
      {
        cwLogInfo("Audio device configuration failed.");
        goto errLabel;
      }


    errLabel:
      return rc;
    }

  }
}



cw::rc_t cw::io::create( handle_t& h, const char* cfgStr, cbFunc_t cbFunc, void* cbArg, const char* cfgLabel )
{
  rc_t            rc;
  object_t*       obj_base = nullptr;
  const object_t* o        = nullptr;
  
  if((rc = destroy(h)) != kOkRC )
    return rc;

  // create the io_t object
  io_t* p = mem::allocZ<io_t>();

  // parse the configuration string
  if((rc = objectFromString( cfgStr, obj_base)) != kOkRC )
  {
    rc = cwLogError(rc,"Configuration parse failed.");
    goto errLabel;
  }

  // get the main io cfg object.
  if((o = obj_base->find(cfgLabel)) == nullptr )
  {
    rc = cwLogError(kSyntaxErrorRC,"Unable to locate the I/O cfg. label:%s.",cfgLabel);
    goto errLabel;
  }

  // create the serial port device
  if((rc = _serialPortCreate(p,o)) != kOkRC )
    goto errLabel;

  // create the MIDI port device
  if((rc = _midiPortCreate(p,o)) != kOkRC )
    goto errLabel;

  // create the Audio device interface
  if((rc = _audioDeviceCreate(p,o)) != kOkRC )
    goto errLabel;
  
  // create the the thread
  if((rc = thread::create( p->threadH, _mainThreadFunc, p)) != kOkRC )
    goto errLabel;

  p->cbFunc = cbFunc;
  p->cbArg  = cbArg;
  
 errLabel:
  if(rc != kOkRC )
    _destroy(p);
  
  return rc;
}


cw::rc_t cw::io::destroy( handle_t& h )
{
  rc_t rc = kOkRC;
  
  if( !h.isValid() )
    return rc;

  io_t* p = _handleToPtr(h);

  if((rc = _destroy(p)) != kOkRC )
    return rc;

  h.clear();
  
  return rc;
}

cw::rc_t cw::io::start( handle_t h )
{
  io_t* p = _handleToPtr(h);
  return thread::pause( p->threadH, thread::kWaitFl );
}

cw::rc_t cw::io::pause( handle_t h )
{
  io_t* p = _handleToPtr(h);
  return thread::pause( p->threadH, thread::kPauseFl | thread::kWaitFl );
}




unsigned cw::io::serialDeviceCount( handle_t h )
{
  io_t* p = _handleToPtr(h);
  return p->serialN;
}

const char* cw::io::serialDeviceName(  handle_t h, unsigned devIdx )
{
  io_t* p = _handleToPtr(h);
  return p->serialA[devIdx].name;
}

unsigned cw::io::serialDeviceIndex( handle_t h, const char* name )
{
  io_t* p = _handleToPtr(h);
  for(unsigned i=0; i<p->serialN; ++i)
    if( textCompare(name,p->serialA[i].name) == 0 )
      return i;

  return kInvalidIdx;
}

cw::rc_t cw::io::serialDeviceSend(  handle_t h, unsigned devIdx, const void* byteA, unsigned byteN )
{
  rc_t  rc = kOkRC;
  //io_t* p  = _handleToPtr(h);
  return rc;
}



unsigned    cw::io::midiDeviceCount( handle_t h )
{
  io_t* p = _handleToPtr(h);
  return midi::device::count(p->midiH);
}

const char* cw::io::midiDeviceName( handle_t h, unsigned devIdx )
{
  io_t* p = _handleToPtr(h);
  return midi::device::name(p->midiH,devIdx);
}

unsigned cw::io::midiDeviceIndex( handle_t h, const char* devName )
{
  io_t* p = _handleToPtr(h);
  return midi::device::nameToIndex(p->midiH, devName);
}

unsigned cw::io::midiDevicePortCount( handle_t h, unsigned devIdx, bool inputFl )
{
  io_t* p = _handleToPtr(h);
  return midi::device::portCount(p->midiH, devIdx, inputFl ? midi::kInMpFl : midi::kOutMpFl );
}

const char* cw::io::midiDevicePortName( handle_t h, unsigned devIdx, bool inputFl, unsigned portIdx )
{
  io_t* p = _handleToPtr(h);
  return midi::device::portName( p->midiH, devIdx, inputFl ? midi::kInMpFl : midi::kOutMpFl, portIdx ); 
}

unsigned cw::io::midiDevicePortIndex( handle_t h, unsigned devIdx, bool inputFl, const char* portName )
{
  io_t* p = _handleToPtr(h);
  return midi::device::portNameToIndex( p->midiH, devIdx, inputFl ? midi::kInMpFl : midi::kOutMpFl, portName );
}
      
cw::rc_t cw::io::midiDeviceSend( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t status, uint8_t d0, uint8_t d1 )
{
  rc_t rc = kOkRC;
  //io_t* p = _handleToPtr(h);
  //return midi::device::send( p->midiH, devIdx, portIdx, status, d0, d1 );
  return rc;
}


unsigned    cw::io::audioDeviceCount(          handle_t h )
{
  io_t* p = _handleToPtr(h);
  return p->audioCfgN;
}

unsigned    cw::io::audioDeviceLabelToIndex(   handle_t h, const char* label )
{
  io_t* p = _handleToPtr(h);
  for(unsigned i=0; i<p->audioCfgN; ++i)
    if( strcmp(p->audioCfgA[i].name,label) == 0 )
      return i;
  
  return kInvalidIdx;
}

const char* cw::io::audioDeviceLabel(          handle_t h, unsigned devIdx )
{
  io_t* p = _handleToPtr(h);
  assert( devIdx < p->audioCfgN );
  return p->audioCfgA[ devIdx ].name;
}

cw::rc_t    cw::io::audioDeviceStart(          handle_t h, unsigned devIdx )
{
  io_t* p = _handleToPtr(h);
  assert( devIdx < p->audioCfgN );
  return audio::device::start( p->audioH, p->audioCfgA[ devIdx ].devIdx ); 
}

cw::rc_t    cw::io::audioDeviceStop(           handle_t h, unsigned devIdx )
{
  io_t* p = _handleToPtr(h);
  assert( devIdx < p->audioCfgN );
  return audio::device::stop( p->audioH, p->audioCfgA[ devIdx ].devIdx );
}

bool        cw::io::audioDeviceIsStarted(      handle_t h, unsigned devIdx )
{
  io_t* p = _handleToPtr(h);
  assert( devIdx < p->audioCfgN );
  return audio::device::isStarted( p->audioH, p->audioCfgA[ devIdx ].devIdx );
}