//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org> 
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwTest.h"
#include "cwMem.h"
#include "cwText.h"
#include "cwFile.h"
#include "cwThread.h"
#include "cwMutex.h"
#include "cwObject.h"
#include "cwTime.h"
#include "cwAudioFile.h"
#include "cwAudioDeviceDecls.h"
#include "cwAudioDevice.h"
#include "cwAudioDeviceFile.h"
#include "cwVectOps.h"

namespace cw
{
  namespace audio
  {
    namespace device
    {
      namespace file      
      {
        enum {
          kInFl  = 0x01,
          kOutFl = 0x02
        };

        typedef struct cache_block_str
        {
          sample_t* buf;
          sample_t* ebuf;
          unsigned  frameN;  // allocated frames
          unsigned  frameIdx; // next frame to fill
          struct cache_block_str* link;
        } cache_block_t;

        typedef struct dev_str
        {
          char*               label;
          
          std::atomic_uint    readyCnt;
          
          unsigned            framesPerCycle;
          double              srate;

          cbFunc_t            cbFunc;
          void*               cbArg;
          unsigned            cbDevIdx;
          
          bool                isStartedFl;

          char*               iFname;
          audiofile::handle_t iFileH;
          unsigned            iFlags;
          unsigned            iChCnt;
          bool                iEnableFl;
          audioPacket_t       iPkt;
          sample_t*           iPktAudioBuf;
          unsigned            iCbCnt;
          sample_t**          iChArray;  // iChArray[ iChCnt ]
          sample_t*           iChSmpBuf; //
          unsigned            iErrCnt;   // count of errors
          unsigned            iFrmCnt;   // count of frames read
          
          sample_t*           iCacheBuf;     // iCacheFrameBuf[ iCacheFrameN * iChCnt ] (interleaved)
          unsigned            iCacheFrameN;  // count of frames in iCacheBuf
          unsigned            iCacheIdx;     // next frame to read (always at channel 0)
          
          char*               oFname;
          audiofile::handle_t oFileH;
          unsigned            oFlags;
          unsigned            oChCnt;
          bool                oEnableFl;
          unsigned            oBitsPerSample;
          audioPacket_t       oPkt;
          sample_t*           oPktAudioBuf;
          unsigned            oCbCnt;
          sample_t**          oChArray;  // oChArray[2]
          sample_t*           oChSmpBuf; //
          unsigned            oErrCnt;   // count of errors
          unsigned            oFrmCnt;   // count of frames written

          cache_block_t*      oCacheBeg;          // Output cache output linked list 
          cache_block_t*      oCacheEnd;          // Last output cache output node.
          unsigned            oCacheBlockSec;     // output cache block size in seconds
          unsigned            oCacheBlockFrameN;  // output cache block size in frames
                    
          struct dev_str*     link;
        } dev_t;
        
        typedef struct dev_mgr_str
        {
          driver_t         driver;
          dev_t*           list;
          unsigned         threadTimeOutMs;
          unsigned         threadCbCnt;
          thread::handle_t threadH;
          mutex::handle_t  mutexH;
        } dev_mgr_t;

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

        dev_mgr_t* _driverToPtr( driver_t* drvr )
        { return (dev_mgr_t*)drvr->drvArg; }

        bool _is_thread_active( dev_mgr_t* p )
        { return p->threadTimeOutMs > 0; }

        dev_t* _labelToDev( dev_mgr_t* p, const char* label )
        {
          dev_t* d = p->list;
          for(; d!=nullptr; d=d->link)
            if( textCompare(d->label,label) == 0 )
              return d;

          return nullptr;          
        }

        dev_t* _indexToDev( dev_mgr_t* p, unsigned devIdx )
        {
          unsigned i = 0;
          dev_t*   d = p->list;
          
          for(; d!=nullptr; d=d->link)
          {
            if( i == devIdx )
              return d;
            ++i;
          }

          return nullptr;
        }

        rc_t _indexToDev( dev_mgr_t* p, unsigned devIdx, dev_t*& devPtrRef )
        {
          rc_t rc = kOkRC;
          
          if((devPtrRef = _indexToDev(p,devIdx)) == nullptr )
            rc = cwLogError(kInvalidArgRC,"The audio file device index %i is invalid.",devIdx);
          
          return rc;
        }

        dev_t* _findOrCreateDev( dev_mgr_t* p, const char* label )
        {
          dev_t* d;
          if((d = _labelToDev(p,label)) != nullptr )
            return d;
          
          d = mem::allocZ<dev_t>();
          d->label = mem::duplStr(label);

          if(p->list == nullptr)
            p->list = d;
          else
          {
            // set d0 to the last dev on the list
            dev_t* d0 = p->list;
            while( d0->link != nullptr )
              d0 = d0->link;
            
            d0->link = d;
          }
          
          return d;          
        }

        void _devReport( dev_t* d )
        {
          cwLogInfo("%s : FpC:%i sr:%f",cwStringNullGuard(d->label),d->framesPerCycle,d->srate);
          
          if( d->iFileH.isValid() )
            cwLogInfo(" in:  flags:0x%x ch:%i sr:%f : %s", d->iFlags,channelCount(d->iFileH),sampleRate(d->iFileH),cwStringNullGuard(d->iFname));
          
          if( d->oFileH.isValid() )
            cwLogInfo(" out: flags:0x%x ch:%i sr:%f : %s", d->oFlags,d->oChCnt,d->srate,cwStringNullGuard(d->oFname));
        }

        void _close_input( dev_t* d )
        {
          if( d->iFileH.isValid() )
          {
            close(d->iFileH);
            d->iCbCnt = 0;
            mem::release(d->iFname);
            mem::release(d->iPktAudioBuf);
            mem::release(d->iChArray);
            mem::release(d->iChSmpBuf);
            mem::release(d->iCacheBuf);
          }
        }

        rc_t _fill_input_packet_from_cache( dev_t* d )
        {
          rc_t     rc = kOkRC;
          unsigned n  = d->framesPerCycle;
          unsigned m  = d->framesPerCycle;

          // set n to the count of frames to copy from the cache into the pkt
          if( d->iCacheIdx + n > d->iCacheFrameN )
            n = d->iCacheFrameN - d->iCacheIdx;
          
          if( n > 0 )
          {
            memcpy( d->iPktAudioBuf, d->iCacheBuf + (d->iCacheIdx * d->iChCnt), n * d->iChCnt * sizeof(sample_t));
            d->iCacheIdx += n;

            // set m to the count of frames to zero at the end of the pkt
            m = d->framesPerCycle - n;
          }
          
          if( m > 0 )
          {
            memset( d->iPktAudioBuf + (n*d->iChCnt), 0, m * d->iChCnt * sizeof(sample_t));
            d->iCacheIdx = d->iCacheFrameN;
          }
          
          return rc;
        }
        
        rc_t _fill_input_packet_from_file( dev_t* d )
        {
          rc_t     rc             = kOkRC;
          unsigned actualFrameCnt = 0;

          // read the file
          if((rc = readFloat(d->iFileH, d->framesPerCycle, 0, d->iChCnt, d->iChArray, &actualFrameCnt)) != kOkRC )
          {
            rc = cwLogError(rc,"File read failed on audio device file: %s.",cwStringNullGuard(d->label));
            d->iErrCnt += 1;
            goto errLabel;
          }

          // interleave into the iPkt audio buffer
          vop::interleave( d->iPktAudioBuf, d->iChSmpBuf, actualFrameCnt, d->iChCnt );

          if( actualFrameCnt < d->framesPerCycle )
          {
            for(unsigned i=0; i<d->iChCnt; ++i)
              vop::fill( d->iPktAudioBuf + (actualFrameCnt * d->iChCnt) + i, 0, d->framesPerCycle-actualFrameCnt, d->iChCnt );            
          }

          // d->iPktAudioBuf[] now contains d->framesPerCycle*d->iChCnt samples

        errLabel:
          return rc;
        }

        rc_t _cache_input( dev_t* d, unsigned frameN )
        {
          rc_t     rc = kOkRC;
          unsigned n  = 0;
          
          d->iCacheFrameN = frameN;
          d->iCacheBuf    = mem::resize<sample_t>(d->iCacheBuf, d->iCacheFrameN * d->iChCnt);
          d->iCacheIdx    = 0;
          
          while(n < d->iCacheFrameN )
          {
            if((rc = _fill_input_packet_from_file(d)) != kOkRC )
            {
              rc = cwLogError(rc,"Read failed while reading the input cache.");
              d->iCacheFrameN = 0; // this will prevent the cache from being used
              goto errLabel;
            }

            unsigned copyFrameN = d->framesPerCycle;
            if( n + copyFrameN > d->iCacheFrameN )
              copyFrameN = d->iCacheFrameN - n;
                        
            memcpy( d->iCacheBuf + n * d->iChCnt, d->iPktAudioBuf, copyFrameN * d->iChCnt * sizeof(sample_t));

            n += copyFrameN;
          }

          if( n % d->iChCnt != 0 )
          {
            cwLogWarning("The cache buffer seems to have a size mismatch with the input audio file.");
          }

        errLabel:
          return rc;          
        }

        rc_t _open_input( dev_t* d, unsigned devIdx )
        {
          rc_t rc = kOkRC;
          audiofile::handle_t iFileH;
          audiofile::info_t info;

          if( d->iFname == nullptr )
            return rc;
                      
          // open the requested audio flie
          if((rc = open( iFileH, d->iFname, &info )) != kOkRC )
          {
            rc = cwLogError(rc,"Audio file device open failed on '%s'.",cwStringNullGuard(d->iFname));
            goto errLabel;
          }

          // if the device input file is already open - then close it
          if( d->iFileH.isValid() )
          {
            cwLogWarning("The audio file device '%s' input file '%s' has been closed prior to opening '%s'.",name(d->iFileH),cwStringNullGuard(d->iFname));
            _close_input(d);
          }

          if( info.srate != d->srate )
            cwLogWarning("The audio file sample rate (%f) does not match device sample rate (%f).",info.srate,d->srate);

          d->iFileH              = iFileH;
          d->iPktAudioBuf        = mem::resize<sample_t>(d->iPktAudioBuf,d->framesPerCycle*info.chCnt);
          d->iChArray            = mem::resize<sample_t*>(d->iChArray,info.chCnt);
          d->iChSmpBuf           = mem::resize<sample_t>(d->iChSmpBuf, d->framesPerCycle*info.chCnt );
          d->iChCnt              = info.chCnt;
          d->iEnableFl           = true;
          d->iErrCnt             = 0;
          d->iFrmCnt             = 0;
          d->iPkt.devIdx         = d->cbDevIdx;
          d->iPkt.begChIdx       = 0;
          d->iPkt.chCnt          = info.chCnt;
          d->iPkt.audioFramesCnt = d->framesPerCycle;
          d->iPkt.bitsPerSample  = 0;
          d->iPkt.flags          = kInterleavedApFl | kFloatApFl;
          d->iPkt.audioBytesPtr  = d->iPktAudioBuf;
          d->iPkt.cbArg          = d->cbArg;
          
          for(unsigned i=0; i<d->iChCnt; ++i)
            d->iChArray[i] = d->iChSmpBuf + i*d->framesPerCycle;

          // if input caching was requested
          if( cwIsFlag(d->iFlags,kCacheFl) )
            if((rc = _cache_input(d,info.frameCnt)) != kOkRC )
              cwLogError(rc,"Cache load failed on '%s'.",cwStringNullGuard(d->iFname));
          
        errLabel:
          return rc;
        }

        rc_t _read_input( dev_t* d )
        {
          rc_t rc = kOkRC;


          if( !d->iEnableFl )
          {
            memset(d->iPkt.audioBytesPtr,0,d->iChCnt*d->framesPerCycle*sizeof(sample_t));
          }
          else
          {
            if( d->iCacheFrameN )
              rc = _fill_input_packet_from_cache(d);
            else
              rc = _fill_input_packet_from_file(d);

            if( rc == kOkRC )
              d->iFrmCnt += d->framesPerCycle;

          }
          return rc;
        }


        rc_t _write_cache_to_file( dev_t* d )
        {
          rc_t rc = kOkRC;
          
          for(cache_block_t* b = d->oCacheBeg; b!=nullptr; b=b->link)
            if((rc = writeFloatInterleaved( d->oFileH, b->frameIdx, d->oChCnt, b->buf )) != kOkRC )            
              rc = cwLogError(rc,"Audio device file cache write failed on device '%s'.",d->label);
          
          return rc;
        }
        
        void _close_output( dev_t* d )
        {
          if( d->oFileH.isValid() )
          {
            _write_cache_to_file(d);            
            
            close(d->oFileH);
            d->oCbCnt = 0;
            mem::release(d->oFname);
            mem::release(d->oPktAudioBuf);
            mem::release(d->oChArray);
            mem::release(d->oChSmpBuf);

            for(cache_block_t* b=d->oCacheBeg; b!=nullptr; )
            {
              cache_block_t* b0 = b->link;
              mem::release(b);
              b = b0;
            }
            
          }
        }

        rc_t _alloc_ocache_block( dev_t* d )
        {
          unsigned bufByteN    = sizeof(cache_block_t) + d->oCacheBlockFrameN * d->oChCnt * sizeof(sample_t);
          uint8_t* bytes       = mem::allocZ<uint8_t>( bufByteN );
          cache_block_t* block = (cache_block_t*)bytes;
          block->buf           = (sample_t*)(block + 1);
          block->ebuf          = (sample_t*)(bytes + bufByteN);
          block->frameN        = d->oCacheBlockFrameN;
          block->frameIdx      = 0;
          block->link          = nullptr;

          if( d->oCacheEnd != nullptr )
            d->oCacheEnd->link = block;
          
          d->oCacheEnd         = block;
          
          if( d->oCacheBeg == nullptr )
            d->oCacheBeg = block;

          return kOkRC;
        }

        rc_t _open_output( dev_t* d, unsigned devIdx )
        {
          rc_t rc = kOkRC;
          audiofile::handle_t oFileH;
          unsigned floatBitsPerSample = 0; // set bits per sample to 0 to indicate a floating point file
          if( d->oFname == nullptr )
            return rc;
          
          // open the requested audio output flie
          
          if((rc = create( oFileH, d->oFname, d->srate, floatBitsPerSample, d->oChCnt )) != kOkRC )
          {
            rc = cwLogError(rc,"Audio file device open failed on '%s'.",cwStringNullGuard(d->oFname));
            goto errLabel;
          }

          d->oFileH              = oFileH;
          d->oPktAudioBuf        = mem::resize<sample_t>(d->oPktAudioBuf,d->framesPerCycle*d->oChCnt);
          d->oChArray            = mem::resize<sample_t*>(d->oChArray,d->oChCnt);
          d->oChSmpBuf           = mem::resize<sample_t>(d->oChSmpBuf, d->framesPerCycle*d->oChCnt );
          d->oEnableFl           = true;
          d->oFrmCnt             = 0;
          d->oErrCnt             = 0;
          d->oPkt.devIdx         = d->cbDevIdx;
          d->oPkt.begChIdx       = 0;
          d->oPkt.chCnt          = d->oChCnt;
          d->oPkt.audioFramesCnt = d->framesPerCycle;
          d->oPkt.bitsPerSample  = 0; // 0==floating point sample format
          d->oPkt.flags          = kInterleavedApFl | kFloatApFl;
          d->oPkt.audioBytesPtr  = d->oPktAudioBuf;
          d->oPkt.cbArg          = d->cbArg;

          d->oCacheBlockFrameN   = 0;
          if( cwIsFlag(d->oFlags,kCacheFl) )
          {
            // note each cache block has a whole multiple of framesPerCycle
            d->oCacheBlockFrameN =  ((unsigned)lround((d->oCacheBlockSec * d->srate) + d->framesPerCycle)/d->framesPerCycle) * d->framesPerCycle;

            assert( d->oCacheBlockFrameN >= d->framesPerCycle && d->oCacheBlockFrameN % d->framesPerCycle == 0 );
            
            _alloc_ocache_block(d);
          }

          for(unsigned i=0; i<d->oChCnt; ++i)
            d->oChArray[i] = d->oChSmpBuf + i*d->framesPerCycle;

        errLabel:
          return rc;
        }

        rc_t _write_cache_output( dev_t* d )
        {
          rc_t rc = kOkRC;
          //sample_t s;
          
          assert( d->oCacheEnd != nullptr );
            
          if( d->oCacheEnd->frameIdx + d->framesPerCycle > d->oCacheEnd->frameN )
            if((rc = _alloc_ocache_block(d)) != kOkRC )
              goto errLabel;

          assert( d->oCacheEnd->frameIdx + d->framesPerCycle <= d->oCacheEnd->frameN );
          
          memcpy(d->oCacheEnd->buf + d->oCacheEnd->frameIdx * d->oChCnt, d->oPktAudioBuf, d->framesPerCycle * d->oChCnt * sizeof(sample_t));
          d->oCacheEnd->frameIdx += d->framesPerCycle;

          //s = vop::sum(d->oPktAudioBuf,d->framesPerCycle * d->oChCnt);
          
          //printf("w:%i %i %f\n",d->oCacheEnd->frameIdx,d->framesPerCycle * d->oChCnt,s);
          
        errLabel:
          return rc;
        }

        rc_t _write_file_output( dev_t* d )
        {
          rc_t rc;
          
          if((rc = writeFloatInterleaved( d->oFileH, d->framesPerCycle, d->oChCnt, d->oPktAudioBuf )) != kOkRC )            
          {
            rc = cwLogError(rc,"Audio device file write failed on device '%s'.",d->label);
            d->oErrCnt += 1;
          }
          
          return rc;
        }

        rc_t _write_output( dev_t* d )
        {
          rc_t rc = kOkRC;

          if( d->oFileH.isValid() && d->oEnableFl )
          {
            if( cwIsFlag(d->oFlags,kCacheFl) )
              rc = _write_cache_output(d);
            else
              rc = _write_file_output(d);

            if( rc != kOkRC )
              d->oFrmCnt += d->framesPerCycle;

          }

          return rc;
        }

        rc_t _update_device( dev_t* d, bool inThreadFl )
        {
          rc_t           rc0   = kOkRC;
          rc_t           rc1   = kOkRC;
          
          audioPacket_t* iPkt  = nullptr;
          unsigned       iPktN = 0;
          audioPacket_t* oPkt  = nullptr;
          unsigned       oPktN = 0;
          
          if( d->iFileH.isValid() && inThreadFl == cwIsFlag(d->iFlags,kUseInternalClockFl) )
          {
            iPkt       = &d->iPkt;
            iPktN      = 1;
            d->iCbCnt += 1;
            rc0        = _read_input( d );    
          }

          if( d->oFileH.isValid()  && inThreadFl == cwIsFlag(d->iFlags,kUseInternalClockFl)  )
          {
            oPkt       = &d->oPkt;
            oPktN      = 1;
            d->oCbCnt += 1;
            vop::zero( d->oPktAudioBuf, d->oChCnt * d->framesPerCycle );
          }
  
          d->cbFunc( d->cbArg, iPkt, iPktN, oPkt, oPktN );
  
          if( d->oFileH.isValid() )
            rc1 = _write_output( d );

          return rcSelect(rc0,rc1);          
        }

        bool _threadCbFunc( void* arg )
        {
          rc_t       rc = kOkRC;
          dev_mgr_t* p  = (dev_mgr_t*)arg;
          dev_t*     d  = nullptr;
          
          // block on the cond. var - unlock the mutex
          if((rc = mutex::waitOnCondVar(p->mutexH,false,p->threadTimeOutMs)) != kOkRC )
          {
            if( rc != kTimeOutRC )
            {
              cwLogError(rc,"Audio device file thread Wait-on-condition-var failed.");
              return false;
            }
          }

          p->threadCbCnt += 1;

          // if the cond. var was signaled and p->mutexH is locked
          if( rc == kOkRC )
          {
            // check for ready devices
            for(d=p->list; d!=nullptr; d=d->link)
            {
              // if this device is ready
              if( std::atomic_load_explicit(&d->readyCnt, std::memory_order_acquire) > 0  ) // ACQUIRE
              {
                // atomic incr  - note that the ordering doesn't matter because the update does not control access to any other variables from another thread
                std::atomic_store_explicit(&d->readyCnt, d->readyCnt-1,  std::memory_order_relaxed); // decrement

                if((rc = _update_device(d,true)) != kOkRC )
                  cwLogError(rc,"The update of audio device file %s failed.",cwStringNullGuard(d->label));
              
              }
            }
          }
          return true;
        }

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

          // destroy the thread
          if((rc = thread::destroy(p->threadH)) != kOkRC )
          {
            rc = cwLogError(rc,"Audio file device thread destroy failed.");
            goto errLabel;
          }
          
          // destroy the mutex
          if((rc = mutex::destroy( p->mutexH )) != kOkRC )
          {
            rc = cwLogError(rc,"Audio file device thread mutex destroy failed.");
            goto errLabel;
          }
          
          // destroy each device          
          for(dev_t* d = p->list; d != nullptr; )
          {
            dev_t* d0 = d->link;
            _close_input(d);
            _close_output(d);
            mem::release(d->label);
            mem::release(d);
            
            d = d0;            
          }

          mem::release(p);

        errLabel:          
          return rc;
        }
      }
    }
  }
}


cw::rc_t    cw::audio::device::file::create( handle_t& hRef, struct driver_str*& drvRef )
{
  rc_t rc;
  if((rc = destroy(hRef)) != kOkRC )
    return rc;

  dev_mgr_t* p = mem::allocZ<dev_mgr_t>();

  p->driver.drvArg = p;
  p->driver.deviceCount          = deviceCount;
  p->driver.deviceLabel          = deviceLabel;
  p->driver.deviceChannelCount   = deviceChannelCount;
  p->driver.deviceSampleRate     = deviceSampleRate;
  p->driver.deviceFramesPerCycle = deviceFramesPerCycle;
  p->driver.deviceSetup          = deviceSetup;
  p->driver.deviceStart          = deviceStart;
  p->driver.deviceStop           = deviceStop;
  p->driver.deviceIsStarted      = deviceIsStarted;
  p->driver.deviceExecute        = deviceExecute;
  p->driver.deviceEnable         = deviceEnable;
  p->driver.deviceSeek           = deviceSeek;
  p->driver.deviceRealTimeReport = deviceRealTimeReport;

  if((rc = create( p->threadH, _threadCbFunc, p, "audio_dev_test" )) != kOkRC )
  {
    rc = cwLogError(rc,"Audio device file thread create failed.");
    goto errLabel;
  }

  // create the audio group thread mutex/cond var
  if((rc = mutex::create(p->mutexH)) != kOkRC )
  {
    rc = cwLogError(rc,"Audio device file mutex create failed.");
    goto errLabel;
  }

  
  drvRef = &p->driver;

  hRef.set(p);

 errLabel:
  if( rc != kOkRC )
  {
    _destroy(p);
    rc = cwLogError(rc,"Audio device file create failed.");
  }
  return rc;
}
  
cw::rc_t    cw::audio::device::file::destroy( handle_t& hRef )
{
  rc_t rc = kOkRC;
  if( !hRef.isValid() )
    return rc;

  dev_mgr_t* p = _handleToPtr(hRef);

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

  hRef.clear();

  return rc;  
}

cw::rc_t   cw::audio::device::file::start( handle_t h )
{
  rc_t       rc = kOkRC;
  dev_mgr_t* p  = _handleToPtr(h);

  // if there are any audio device files
  if( p->list != nullptr )
  {
    if( _is_thread_active(p) )
      if((rc = thread::unpause(p->threadH)) != kOkRC )
      {
        rc = cwLogError(rc,"Audio file device thread start failed.");
        goto errLabel;
      }
  }
 errLabel:
  return rc;
}

cw::rc_t   cw::audio::device::file::stop( handle_t h )
{
  rc_t       rc = kOkRC;
  dev_mgr_t* p  = _handleToPtr(h);

  if( _is_thread_active(p) )
    if((rc = thread::pause(p->threadH)) != kOkRC )
    {
      rc = cwLogError(rc,"Audio file device thread stop failed.");
      goto errLabel;
    }

 errLabel:
  return rc;
}


unsigned    cw::audio::device::file::deviceCount(          struct driver_str* drv)
{
  dev_mgr_t* p = _driverToPtr(drv);
  dev_t* d = p->list;
  unsigned n = 0;
  for(; d!=nullptr; d=d->link)
    ++n;
  return n;
}

const char* cw::audio::device::file::deviceLabel(          struct driver_str* drv, unsigned devIdx )
{
  rc_t       rc= kOkRC;
  dev_t*     d = nullptr;
  dev_mgr_t* p = _driverToPtr(drv);
  
  if((rc = _indexToDev( p, devIdx, d )) != kOkRC )
    return nullptr;

  return d->label;
}

unsigned    cw::audio::device::file::deviceChannelCount(   struct driver_str* drv, unsigned devIdx, bool inputFl )
{
  rc_t       rc = kOkRC;
  dev_t*     d = nullptr;
  dev_mgr_t* p = _driverToPtr(drv);
  
  if((rc = _indexToDev( p, devIdx, d )) != kOkRC )
    return 0;

  audiofile::handle_t fH = inputFl ? d->iFileH : d->oFileH;
  
  return fH.isValid() ? channelCount( fH ) : 0;
}

double      cw::audio::device::file::deviceSampleRate(     struct driver_str* drv, unsigned devIdx )
{
  rc_t       rc= kOkRC;
  dev_t*     d = nullptr;
  dev_mgr_t* p = _driverToPtr(drv);
  
  if((rc = _indexToDev( p, devIdx, d )) != kOkRC )
    return 0;

  return d->srate;
}

unsigned    cw::audio::device::file::deviceFramesPerCycle( struct driver_str* drv, unsigned devIdx, bool inputFl )
{
  rc_t       rc= kOkRC;
  dev_t*     d = nullptr;
  dev_mgr_t* p = _driverToPtr(drv);
  
  if((rc = _indexToDev( p, devIdx, d )) != kOkRC )
    return 0;

  return d->framesPerCycle;
  
}

cw::rc_t    cw::audio::device::file::deviceSetup( struct driver_str* drv, unsigned devIdx, double srate, unsigned frmPerCycle, cbFunc_t cbFunc, void* cbArg, unsigned cbDevIdx )
{
  rc_t       rc0 = kOkRC;
  rc_t       rc1 = kOkRC;
  dev_t*     d   = nullptr;
  dev_mgr_t* p   = _driverToPtr(drv);
  
  if((rc0 = _indexToDev( p, devIdx, d )) != kOkRC )
    return rc0;

  _close_input(d);
  _close_output(d);

  d->framesPerCycle = frmPerCycle;
  d->srate          = srate;
  d->cbFunc         = cbFunc;
  d->cbArg          = cbArg;
  d->cbDevIdx       = cbDevIdx;

  if( cwIsFlag(d->iFlags,kUseInternalClockFl) || cwIsFlag(d->oFlags,kUseInternalClockFl) )
  {
    unsigned ms = (frmPerCycle * 1000) / srate;
    if( p->threadTimeOutMs==0 || ms < p->threadTimeOutMs )
    {
      p->threadTimeOutMs = ms;
      cwLogInfo("Audio device file time out %i ms.",p->threadTimeOutMs);
    }
  }
  
  rc0 = _open_input(d, devIdx);
  rc1 = _open_output(d, devIdx);
  
  rc0 = rcSelect(rc0,rc1);

  if(rc0 != kOkRC )
    rc0 = cwLogError(rc0,"Audio device file '%s' setup failed.",cwStringNullGuard(d->label));

  return rc0;
}

cw::rc_t    cw::audio::device::file::deviceStart(          struct driver_str* drv, unsigned devIdx )
{
  rc_t rc0 = kOkRC;
  rc_t rc1 = kOkRC;
  dev_t*     d = nullptr;
  dev_mgr_t* p = _driverToPtr(drv);
  
  if((rc0 = _indexToDev( p, devIdx, d )) == kOkRC )
  {
    if( d->iFileH.isValid() && cwIsFlag(d->iFlags,kRewindOnStartFl) )
      if((rc0 = seek(d->iFileH,0)) != kOkRC )
        rc0 = cwLogError(rc0,"Rewind on start failed on the audio device file input file.");

    if( d->oFileH.isValid() && cwIsFlag(d->oFlags,kRewindOnStartFl) )    
      if((rc1 = seek(d->oFileH,0)) != kOkRC )
        rc1 = cwLogError(rc1,"Rewind on start failed on the audio device file output file.");
  }
  
  if((rc0 = rcSelect(rc0,rc1)) == kOkRC )
    d->isStartedFl = true;
  
  return rc0;
}

cw::rc_t    cw::audio::device::file::deviceStop(           struct driver_str* drv, unsigned devIdx )
{
  rc_t       rc = kOkRC;
  dev_t*     d  = nullptr;
  dev_mgr_t* p  = _driverToPtr(drv);

  if((rc = _indexToDev( p, devIdx, d )) == kOkRC )
    if( d->isStartedFl && d->oFileH.isValid() )
    {
      // TODO: implement audiofile::flush()
      // if((rc = flush(d->ofileH)) != kOkRC )
      //   rc = cwLogError(rc,"Flush on stop failed on the audio device file output file.");
      
      d->isStartedFl = false;
    }  

  return rc;
}

bool        cw::audio::device::file::deviceIsStarted(      struct driver_str* drv, unsigned devIdx )
{
  rc_t       rc = kOkRC;
  dev_t*     d  = nullptr;
  dev_mgr_t* p  = _driverToPtr(drv);
  
  if((rc = _indexToDev( p, devIdx, d )) == kOkRC )
    return d->isStartedFl;
  return false;
}

cw::rc_t    cw::audio::device::file::deviceExecute(        struct driver_str* drv, unsigned devIdx )
{
  rc_t           rc    = kOkRC;
  dev_mgr_t*     p     = _driverToPtr(drv);
  dev_t*         d     = nullptr;
  
  if((rc = _indexToDev( p, devIdx, d )) != kOkRC )
  {
    goto errLabel;
  }
  
  std::atomic_store_explicit(&d->readyCnt, d->readyCnt+1, std::memory_order_relaxed); // atomic incr

  if( cwIsFlag(d->iFlags,kUseInternalClockFl)  )
  {    
    mutex::signalCondVar(p->mutexH);
  }
  else
  {
    rc = _update_device(d,false);
  }
  
 errLabel:
  return rc; 
}

cw::rc_t  cw::audio::device::file::deviceEnable( struct driver_str* drv, unsigned devIdx, bool inputFl, bool enableFl )
{
  rc_t           rc    = kOkRC;
  dev_mgr_t*     p     = _driverToPtr(drv);
  dev_t*         d     = nullptr;
  
  if((rc = _indexToDev( p, devIdx, d )) != kOkRC )
    goto errLabel;

  if( inputFl )
    d->iEnableFl = enableFl;
  else
    d->oEnableFl = enableFl;

 errLabel:
  return rc; 
}

cw::rc_t  cw::audio::device::file::deviceSeek(   struct driver_str* drv, unsigned devIdx, bool inputFl, unsigned frameOffset )
{
  rc_t           rc    = kOkRC;
  dev_mgr_t*     p     = _driverToPtr(drv);
  dev_t*         d     = nullptr;
  
  if((rc = _indexToDev( p, devIdx, d )) != kOkRC )
    goto errLabel;

  if( inputFl )
  {
    if( d->iFileH.isValid() )
    {
      if( d->iCacheFrameN )
      {
        d->iCacheIdx = std::min( frameOffset, d->iCacheFrameN );
      }
      else
      {
        if((rc = seek(d->iFileH,frameOffset)) != kOkRC )
          rc = cwLogError(rc,"Seek failed on the audio device file input file.");
      }
    }
  }
  else
  {
    if( d->oFileH.isValid())
      if((rc = seek(d->oFileH,frameOffset)) != kOkRC )
        rc = cwLogError(rc,"Seek failed on the audio device file output file.");
  }

 errLabel:
  return rc;
}

void        cw::audio::device::file::deviceRealTimeReport( struct driver_str* drv, unsigned devIdx )
{
  rc_t       rc = kOkRC;
  dev_t*     d  = nullptr;
  dev_mgr_t* p  = _driverToPtr(drv);
  
  if((rc = _indexToDev( p, devIdx, d )) != kOkRC )
    return;
    
  cwLogInfo("file cb i:%i o:%i err i:%i  o:%i frames: i:%i o:%i : th:%i :  %s",d->iCbCnt,d->oCbCnt,d->iErrCnt,d->oErrCnt,d->iFrmCnt,d->oFrmCnt,p->threadCbCnt,cwStringNullGuard(d->label));

}

cw::rc_t    cw::audio::device::file::createInDevice(  handle_t& h, const char* label, const char* audioInFName, unsigned flags )
{
  rc_t       rc = kOkRC;
  handle_t   iFileH;
  dev_t*     d;  
  dev_mgr_t* p = _handleToPtr(h);
  
  // create or find a device
  if((d = _findOrCreateDev(p, label )) == nullptr )
  {
    rc = cwLogError(kOpFailRC,"Audio file device create failed.");
    goto errLabel;
  }

  d->iFname  = mem::duplStr(audioInFName);
  d->iFlags  = flags;
  
 errLabel:
      
  return rc;
}

cw::rc_t    cw::audio::device::file::createOutDevice( handle_t& h, const char* label, const char* audioOutFName, unsigned flags, unsigned chCnt, unsigned bitsPerSample, unsigned cacheBlkSec )
{
  rc_t       rc = kOkRC;
  dev_t*     d;  
  dev_mgr_t* p = _handleToPtr(h);
  
  // create or find a device
  if((d = _findOrCreateDev(p, label )) == nullptr )
  {
    rc = cwLogError(kOpFailRC,"Audio file device create failed.");
    goto errLabel;
  }

  d->oFname         = mem::duplStr(audioOutFName);
  d->oFlags         = flags;
  d->oChCnt         = chCnt;
  d->oBitsPerSample = bitsPerSample;
  d->oCacheBlockSec = cacheBlkSec;
  
 errLabel:
  
  return rc;  
}


cw::rc_t    cw::audio::device::file::report(handle_t h )
{
  rc_t rc = kOkRC;
  dev_mgr_t* p  = _handleToPtr(h);
  dev_t* d = p->list;

  for(; d!=nullptr; d=d->link)
    _devReport(d);
  
  return rc;
}

cw::rc_t    cw::audio::device::file::report()
{
  rc_t rc = kOkRC;
  return rc;
}


namespace cw {
  namespace audio {
    namespace device {
      namespace file {

        typedef struct cb_object_str
        {
          uint8_t*         buf;
          unsigned         byteCnt;
          std::atomic<int> readyCnt;
        } cb_object_t;
        
        void driverCallback( void* cbArg, audioPacket_t* inPktArray, unsigned inPktCnt, audioPacket_t* outPktArray, unsigned outPktCnt )
        {
          cb_object_t* p = (cb_object_t*)cbArg;

          if( inPktCnt )
          {
            audioPacket_t* pkt      = inPktArray;
            unsigned       pktByteN = pkt->audioFramesCnt * pkt->chCnt * sizeof(sample_t);
            unsigned       byteN    = std::min(p->byteCnt,pktByteN);
            vop::copy(p->buf,(const uint8_t*)pkt->audioBytesPtr,byteN);
            p->readyCnt++;
          }

          if( outPktCnt && p->readyCnt.load() > 0)
          {
            audioPacket_t* pkt      = outPktArray;
            unsigned       pktByteN = pkt->audioFramesCnt * pkt->chCnt * sizeof(sample_t);
            unsigned       byteN    = std::min(p->byteCnt,pktByteN);
            vop::copy((uint8_t*)pkt->audioBytesPtr,p->buf,byteN);
            p->readyCnt--;
          }
        }
      }
    }
  }
}

cw::rc_t cw::audio::device::file::test( const object_t* cfg)
{
  rc_t               rc             = kOkRC;
  rc_t               rc1            = kOkRC;
  rc_t               rc2            = kOkRC;
  const char*        ifname         = nullptr;
  const char*        ofname         = nullptr;
  struct driver_str  driver         = {};
  struct driver_str* driver_ptr     = &driver;
  unsigned           bitsPerSample  = 0; // zero indicates floating point sample format for output audio file
  unsigned           sleepMicrosec  = 0;
  const char*        devLabel       = "dev_file";
  unsigned           devIdx         = 0;
  unsigned           framesPerCycle = 0;
  cb_object_t        obj            = {};
  void*              cbArg          = &obj;
  audiofile::info_t  info;
  handle_t           h;

  // parse the test args
  if((rc = cfg->getv("inAudioFname",ifname,
                     "outAudioFname",ofname,
                     "framesPerCycle",framesPerCycle)) != kOkRC || ifname==nullptr || ofname==nullptr )
  {
    rc = cwLogError(rc,"Parsing audiio device file test cfg. failed.");
    goto errLabel;
  }

  // create the audioDeviceFile mgr
  if((rc = create(h,driver_ptr)) != kOkRC )
  {
    rc = cwLogError(rc,"Error creating audio device file mgr.");
    goto errLabel;
  }

  // get input file srate and channel count
  if((rc = getInfo( ifname, &info )) != kOkRC )
  {
    rc = cwLogError(rc,"Error parsing input audio file '%s' header.",cwStringNullGuard(ifname));
    goto errLabel;
  }

  // create an input audio file device
  if((rc = createInDevice( h, devLabel, ifname,  kRewindOnStartFl | kCacheFl)) != kOkRC )
  {
    rc = cwLogError(rc,"Error creating the audio device file input device from the file:%s.",cwStringNullGuard(ifname));
    goto errLabel;    
  }

  // create an output audio file device
  if((rc = createOutDevice( h, devLabel, ofname, kRewindOnStartFl | kCacheFl, info.chCnt, bitsPerSample )) != kOkRC )
  {
    rc = cwLogError(rc,"Error creating the audio device file output device from the file:%s.",cwStringNullGuard(ofname));
    goto errLabel;        
  }

  // setup the audio device file device
  if((rc =  deviceSetup( driver_ptr, devIdx, info.srate, framesPerCycle, driverCallback, cbArg, devIdx )) != kOkRC )
  {
    rc = cwLogError(rc,"Error setting up the audio device file.");
    goto errLabel;
  }

  sleepMicrosec = (framesPerCycle * 1e6) / info.srate;
  
  report(h);

  obj.byteCnt = framesPerCycle * info.chCnt * sizeof(sample_t);
  obj.buf = mem::allocZ<uint8_t>( obj.byteCnt );

  // start the audio device file
  if((rc = start(h)) != kOkRC )
  {
    rc = cwLogError(rc,"Audio device file start failed.");
    goto errLabel;
  }

  // run the audio device file
  for(unsigned i=0; i<10; ++i)
  {
    deviceExecute( driver_ptr, devIdx );
    deviceRealTimeReport( driver_ptr, devIdx );
    sleepUs( sleepMicrosec );
  }
    
 errLabel:
  mem::release(obj.buf);

  // stop the audio device file mgr.
  if((rc2 = stop(h)) != kOkRC )
    rc2 = cwLogError(rc2,"Audio device file mgr. stop failed.");

  // destroy the audio device file mgr
  if((rc1 = destroy(h)) != kOkRC )
    rc1 = cwLogError(rc1,"Audio device file mgr. destroy failed.");
  
  return rcSelect(rc,rc1,rc2);  
}