
#include "stdio.h"
#include "assert.h"
#include "windows.h"
#include "math.h"
#include "lusb0_usb.h"
#include "Shared.h"

DWORD TMO;

void Mode0_ProcessLine(TUSBItem_Line* USBItem);
void Mode1_ProcessLine(TUSBItem_Line* USBItem);
void Mode2_ProcessLine(TUSBItem_Line* USBItem);
void Mode3_ProcessLine(TUSBItem_Line* USBItem);
void Mode4_ProcessLine(TUSBItem_Line* USBItem);
void Mode5_ProcessLine(TUSBItem_Line* USBItem);
void Mode6_ProcessLine(TUSBItem_Line* USBItem);

//################################################### usbOpenDevice
int usbOpenDevice(usb_dev_handle **device, int vendor, int product)
{
  enum TError
  {
    USB_ERROR_NOTFOUND = 1,
    USB_ERROR_ACCESS   = 2,
    USB_ERROR_IO       = 3
  };
  struct usb_bus    *bus;
  struct usb_device *dev;
  usb_dev_handle    *handle = NULL;
  int               errorCode = USB_ERROR_NOTFOUND;
  int n1 = usb_find_busses();
  int n2 = usb_find_devices();
  bus = usb_get_busses();
  for(bus=usb_get_busses(); bus; bus=bus->next)
  {
    for(dev=bus->devices; dev; dev=dev->next)
    {
      if(dev->descriptor.idVendor ==vendor &&
         dev->descriptor.idProduct==product)
      {
        handle = usb_open(dev); /* we need to open the device in order to query strings */
        if(handle==NULL)
        {
          errorCode = USB_ERROR_ACCESS;
          continue;
        }
      }
    }
    if(handle) break;
  }
  if(handle!=NULL)
  {
    errorCode = 0;
    *device = handle;
  }
  return errorCode;
}

//################################################### ProcessLine
static void ProcessLine(TUSBItem_Line* USBItem)
{
  if(_Mode==0)
  {
    Mode0_ProcessLine(USBItem);
  }
  if(_Mode==1)
  {
    Mode1_ProcessLine(USBItem);
  }
  if(_Mode==2)
  {
    Mode2_ProcessLine(USBItem);
  }
  if(_Mode==3)
  {
    Mode3_ProcessLine(USBItem);
  }
  if(_Mode==4)
  {
    Mode4_ProcessLine(USBItem);
  }
  if(_Mode==5)
  {
    Mode5_ProcessLine(USBItem);
  }
  if(_Mode==6)
  {
    Mode6_ProcessLine(USBItem);
  }
}

//################################################### main
int main(int argc, char **argv)
{
  // Default-Werte
  _Mode = 6;
  _BrightnessBias = 0;
  _ContrastScale = 230; // Technisat
  // _ContrastScale = 250; // Raspberry
  // _ContrastScale = 300; // VC20
  _ColorScale    = 100*1;
  bool AutoGain = false;

  // Dbg: Write Stream
  LPCSTR StreamFileNameW = NULL;
  // StreamFileNameW = "s:\\1.dat";

  // Dbg: Read Stream
  LPCSTR StreamFileNameR = NULL;
  // StreamFileNameR = "s:\\1x.dat";
  // StreamFileNameR = "s:\\1y.dat";
  // StreamFileNameR = "s:\\1z.dat";
  // StreamFileNameR = "s:\\13.dat";

  if(argc<2)
  {
    printf("\n");
    printf("- Usage\n");
    printf("  STM32VidGrab.exe <Mode> [<Brightness> <Contrast> <Color>]\n");
    printf("\n");
    printf("- Arguments\n");
    printf("  <Mode>       : 0: 2.56s / 1880 x 576\n");
    printf("                 1: 1.28s /  940 x 576\n");
    printf("                 2: 0.64s /  940 x 576  LowColorRes\n");
    printf("                 3: 0.32s /  940 x 576  XY-HalfRes & LowColorRes\n");
    printf("                 4: 0.32s /  480 x 576  XY-HalfRes\n");
    printf("                 5: 0.16s /  480 x 576  XY-HalfRes & LowColorRes\n");
    printf("                 6: 0.04s /  480 x 576  XY-HalfRes & LowColorRes & KindOfDeinterlacing\n");
    printf("  <Brightness> : percentage; -50...+50; default 0\n");
    printf("  <Contrast>   : percentage; 50...500; default 230\n");
    printf("  <Color>      : percentage; 0...150; default 100\n");
    printf("\n");
    printf("(STRG-C copies image to clipboard)\n");
    printf("\n");
    return 0;
  }
                                                                   
  if(argc>1) _Mode=atoi(argv[1]);
  if(argc>4)
  {
    _BrightnessBias = atoi(argv[2]);
    _ContrastScale  = atoi(argv[3]);
    _ColorScale     = atoi(argv[4]);
  }

  // Config & Start
  _Config.Init       = 1;
  _Config.ImageBias  = 0;
  _Config.ImageScale = AutoGain ? 0 : 4096; // 4096 -> fest 100%; 0 -> zeilenbasierte automatische Bestimmung
  _Config.ImageOfs   = _ImageOfs;
  _Config.Mode       = _Mode;
  switch(_Mode)
  {
    case 2 : _Config.dADC2 = 16;
             break;
    case 3 : _Config.dADC2 = 16;
             break;
    case 5 : _Config.dADC2 = 15;
             break;
    case 6 : _Config.dADC2 = 15;
             break;
  }
  if(_Config.dADC2!=0)
  {
    _Config.ImageScale = 0;
  }

  const int BulkBufferKB  = 64; //  64 -> 64ms  (=64*1024/1000)
  const int BulkBufferCnt = 4;  // 4  -> 256 ms (=4*64)
  const int BulkBufferTimeout = BulkBufferCnt*(BulkBufferKB*1024/1000);

  // Open USB Device
  usb_dev_handle* hUsbDev = NULL;
  if(StreamFileNameR==NULL)
  {
    usb_init();
    {
      const int vid = 0x0815;
      const int pid = 0x4711;
      int Err = usbOpenDevice(&hUsbDev, vid, pid);
      if(Err!=0)
      {
        printf("Error: usbOpenDevice failed (%i).\n", Err);
        return 0;
      }
    }
    printf("Ok, Device found.\n\n");

    // Init USB Device
    usb_set_configuration(hUsbDev, 1);
    usb_claim_interface(hUsbDev, 0);
    int cnt = usb_control_msg(hUsbDev,
                              USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_ENDPOINT_OUT,
                              123,
                              77, // pInformation->USBwValue0 USBwValues.bw.bb0 
                              88, // pInformation->USBwIndex0 USBwIndexs.bw.bb0
                              (char*)&_Config,
                              sizeof(_Config),
                              1000);
  }

  struct TBulkBuffer
  {
    void* Context;
    BYTE* Buffer;
  };

  TBulkBuffer BulkBufferArr[BulkBufferCnt] = { 0 };
  int         BulkBufferCur = 0;
  for(int i=0; i<BulkBufferCnt; i++)
  {
    BulkBufferArr[i].Buffer = (BYTE*)malloc(BulkBufferKB*1024);
    if(StreamFileNameR==NULL)
    {
      usb_bulk_setup_async(hUsbDev, &BulkBufferArr[i].Context, 0x83);
      usb_submit_async(BulkBufferArr[i].Context,
                       (char*)BulkBufferArr[i].Buffer,
                       BulkBufferKB*1024);
    }
  }

  int hStreamFile = 0;
  if(StreamFileNameW!=NULL && StreamFileNameR==NULL)
  {
    hStreamFile = _lcreat(StreamFileNameW, 0);
    assert(hStreamFile>0);
  }
  if(StreamFileNameR!=NULL)
  {
    hStreamFile = _lopen(StreamFileNameR, 0);
    assert(hStreamFile>0);
  }

  LARGE_INTEGER TM0 = { 0 };

  int32_t nRecvSum    = 0;
  int32_t IdleCount   = 0;
  int32_t nFrameError = 0;
  int32_t PrevSndBufMinErrCnt = 0;
  for(int iLoop=0; ; iLoop++)
  {
    TBulkBuffer* bb = BulkBufferArr+BulkBufferCur;

    // USB-Daten lesen
    if(StreamFileNameR==NULL)
    {
      int nRead = usb_reap_async(bb->Context, BulkBufferTimeout);
      if(nRead!=BulkBufferKB*1024)
      {
        printf("Error: usb_reap_async(%i)\n", nRead); // "-116" -> nothing to read
        break;
      }
      nRecvSum += nRead;
    }
    if(hStreamFile>0)
    {
      if(StreamFileNameW!=NULL && StreamFileNameR==NULL)
      {
        int nWrite = BulkBufferKB*1024;
        int n = _lwrite(hStreamFile, (char*)bb->Buffer, nWrite);
        assert(n==nWrite);
      }
      if(StreamFileNameR!=NULL)
      {
        int nRead = _lread(hStreamFile, bb->Buffer, BulkBufferKB*1024);
        if(nRead==0) break;
        assert(nRead==BulkBufferKB*1024);
        nRecvSum += nRead;
      }
    }
    for(int p=0; p<BulkBufferKB*1024; p+=64)
    {
      TUSBItem_Line* USBItem = (TUSBItem_Line*)(bb->Buffer+p);
      // Idle-Item
      if((USBItem->Flags&1)==0)
      {
        IdleCount++;
      }
      // Data-Item
      if((USBItem->Flags&1)!=0)
      {
        _iLine++;
        _TotalLine++;
        // VSync-Line?
        if((USBItem->Flags&2)!=0)
        {
          if(_iLine!=312 && _iLine!=313) nFrameError++;
          _iLine = 0;
          CopyMemory(&_State, USBItem, sizeof(_State));
        }
        ProcessLine(USBItem);
      }
    }

    // Puffer zur Wiederverwendung übergeben
    usb_submit_async(bb->Context, (char*)bb->Buffer, BulkBufferKB*1024);
    BulkBufferCur++;
    if(BulkBufferCur>=BulkBufferCnt) BulkBufferCur=0;

    // Debug-Ausgabe
    if((iLoop%(1024/BulkBufferKB))==0 && 1==1)
    {
      LARGE_INTEGER TM;
      LARGE_INTEGER fre;
      QueryPerformanceCounter(&TM);
      QueryPerformanceFrequency(&fre);
      double dt = 1.0*(TM.QuadPart-TM0.QuadPart)/fre.QuadPart;
      int HSync_Threshold_pct = 0;
      if(_State.RefBlank-_State.HSync_Min>0)
      {
        HSync_Threshold_pct = 100*(_State.HSync_Threshold-_State.HSync_Min)/(_State.RefBlank-_State.HSync_Min);
      }
      double USB_Idle_pct = 0;
      if(_State.RefBlank-_State.HSync_Min>0)
      {
        USB_Idle_pct = 100.0*IdleCount/(BulkBufferKB*1024);
      }
      LPCSTR InfoStr = "";
      if(nFrameError> 0) InfoStr="### USB etwas zu langsam? ###";
      if(nFrameError>40) InfoStr="### Kein Video Signal? ###";
      if(PrevSndBufMinErrCnt!=_State.SndBufMinErrCnt) InfoStr="### USB zu langsam! ###";
      printf("\n");
      printf("FrameErrorRate =%6i Frames/s %s\n", nFrameError, InfoStr);
      printf("dfPAL          =%6i PPM\n", int(_dPPM_PAL));
      printf("HSync_Min      =%6i mV\n", _State.HSync_Min*3300/4096);
      printf("HSync_Threshold=%6i mV (%i %%)\n", _State.HSync_Threshold*3300/4096, HSync_Threshold_pct);
      printf("RefBlank       =%6i mV\n", _State.RefBlank*3300/4096);
      printf("BurstAmp       =%6i mV\n", int(sqrt(_State.BurstAmp*2/3)/3*3300/4096*2));
      printf("USB_Speed      =%6i kByte/s\n", int(nRecvSum/dt/1000));
      printf("USB_Idle       =%6.2f %%\n", USB_Idle_pct);
      printf("Time           =%8i ms\n", TMO/2100000);
      TMO = 0;
      nFrameError = 0;
      PrevSndBufMinErrCnt = _State.SndBufMinErrCnt;
      TM0 = TM;
      nRecvSum = 0;
      IdleCount = 0;
    }
    {
      MSG Msg = { 0 };
      while(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
      {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
        if(Msg.message==WM_QUIT) break;
      };
      if(Msg.message==WM_QUIT) break;
    }
  }
  for(i=0; i<BulkBufferCnt; i++)
  {
    free(BulkBufferArr[i].Buffer);
    usb_free_async(&BulkBufferArr[i].Context);
  }
  if(hStreamFile>0) _lclose(hStreamFile);
  return 0;
}
