/**@file wxCamera.cpp: 
   wx aware part of camera that allows user to change resolution. */

#include <wx/wxprec.h>
#ifndef WX_PRECOMP
 #include <wx/wx.h>
#endif

#include "camera.h"
#include "../../wxTools.h"

#include <Dvdmedia.h>			// VIDEOINFOHEADER2

#include <initguid.h>		// Ensure GUID instantionalise.
DEFINE_GUID(MEDIASUBTYPE_I420, 0x30323449, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); 
DEFINE_GUID(MEDIASUBTYPE_NV12, 0x3231564E, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
DEFINE_GUID(MEDIASUBTYPE_YUV420P, 0xc528b79f, 0xf025, 0x4eb7, 0x92, 0xb0, 0x46, 0xce, 0x81, 0x67, 0xe9, 0xf8);
DEFINE_GUID(MEDIASUBTYPE_ARGB32,0x773C9AC0,0x3274, 0x11D0, 0xB7, 0x24, 0x00, 0xAA, 0x00, 0x6C, 0x1A, 0x01);
DEFINE_GUID(MEDIASUBTYPE_Y422, 0x32323459, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
DEFINE_GUID(MEDIASUBTYPE_v210, 0x30313276, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
DEFINE_GUID(MEDIASUBTYPE_R210, 0x30313272, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
DEFINE_GUID(MEDIASUBTYPE_A2B10G10R10,0x576F7893,0xBDF6,0x48C4, 0x87,0x5F, 0xAE, 0x7B, 0x81, 0x83, 0x45, 0x67);
DEFINE_GUID(MEDIASUBTYPE_A2R10G10B10,0x2F8BB76D,0xB644,0x4550, 0xAC,0xF3, 0xD3, 0x0C, 0xAA, 0x65, 0xD5, 0xC5);
DEFINE_GUID(MEDIASUBTYPE_R16G16B16, 0x36315242, 0x0000,0x0010, 0x80,0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
DEFINE_GUID(MEDIASUBTYPE_B16G16R16, 0x36314252, 0x0000,0x0010, 0x80,0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
DEFINE_GUID(MEDIASUBTYPE_A16R16G16B16, 0x36314241, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
DEFINE_GUID(MEDIASUBTYPE_A16B16G16R16, 0x36315241, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
DEFINE_GUID(MEDIASUBTYPE_HDYC, 0x43594448, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);

//DEFINE_GUID(CLSID_KsDataTypeHandlerVideo2,0xf72a76a0, 0xeb0a, 0x11d0, 0xac, 0xe4, 0x00, 0x00, 0xc0, 0xcc, 0x16, 0xba);

#ifdef _DEBUG
DEFINE_GUID(CLSID_ColorSpaceConverter,0x1643E180, 0x90F5, 0x11CE, 0x97, 0xD5, 0x00, 0xAA, 0x00, 0x55, 0x59, 0x5A);
#endif


// https://gix.github.io/media-types/#video-formats
wxString TranslateVideoMode(const GUID & MediaSubType)
{
  if(MediaSubType==MEDIASUBTYPE_AYUV) return "AYUV";
  if(MediaSubType==MEDIASUBTYPE_CFCC) return "!CFCC";
  if(MediaSubType==MEDIASUBTYPE_CLJR) return "!CLJR";
  if(MediaSubType==MEDIASUBTYPE_CLPL) return "!CLPL";
  if(MediaSubType==MEDIASUBTYPE_CPLA) return "!CPLA";
  if(MediaSubType==MEDIASUBTYPE_DVCS) return "!DVCS";
  if(MediaSubType==MEDIASUBTYPE_dvsd) return "dvsd";
  if(MediaSubType==MEDIASUBTYPE_DVSD) return "!DVSD";
  if(MediaSubType==MEDIASUBTYPE_dvsl) return "dvsl";
  if(MediaSubType==MEDIASUBTYPE_dvsl) return "dvhd";
  if(MediaSubType==MEDIASUBTYPE_I420) return "I420";
  if(MediaSubType==MEDIASUBTYPE_IF09) return "!IF09";
  if(MediaSubType==MEDIASUBTYPE_IJPG) return "!IJPG";
  if(MediaSubType==MEDIASUBTYPE_IYUV) return "IYUV";
  if(MediaSubType==MEDIASUBTYPE_MDVF) return "!MDVF";
  if(MediaSubType==MEDIASUBTYPE_MJPG) return "MJPG";
  if(MediaSubType==MEDIASUBTYPE_Plum) return "!Plum";
  if(MediaSubType==MEDIASUBTYPE_RGB1) return "!RGB1";
  if(MediaSubType==MEDIASUBTYPE_RGB24) return "RGB24";
  if(MediaSubType==MEDIASUBTYPE_RGB32) return "RGB32";
  if(MediaSubType==MEDIASUBTYPE_RGB4) return "!RGB4";
  if(MediaSubType==MEDIASUBTYPE_RGB565) return "RGB565";
  if(MediaSubType==MEDIASUBTYPE_RGB555) return "RGB555";
  if(MediaSubType==MEDIASUBTYPE_RGB8) return "RGB8";
  if(MediaSubType==MEDIASUBTYPE_TVMJ) return "!TVMJ";
  if(MediaSubType==MEDIASUBTYPE_UYVY) return "UYVY";
  if(MediaSubType==MEDIASUBTYPE_HDYC) return "HDYC";			// https://fourcc.org/pixel-format/yuv-uyvy/
  if(MediaSubType==MEDIASUBTYPE_WAKE) return "!WAKE";
  if(MediaSubType==MEDIASUBTYPE_YUYV) return "YUYV";
  if(MediaSubType==MEDIASUBTYPE_YUY2) return "YUY2";
  if(MediaSubType==MEDIASUBTYPE_YVYU) return "YVYU";
  if(MediaSubType==MEDIASUBTYPE_YVU9) return "!YVU9";
  if(MediaSubType==MEDIASUBTYPE_YV12) return "YV12";
  if(MediaSubType==MEDIASUBTYPE_Y211) return "!Y211";
  if(MediaSubType==MEDIASUBTYPE_Y411) return "!Y411";
  if(MediaSubType==MEDIASUBTYPE_Y41P) return "!Y41P";
  if(MediaSubType==MEDIASUBTYPE_YUV420P) return "YUV420P";		// YUV420P =  NV12 = I420

  if(MediaSubType==MEDIASUBTYPE_NV12) return "NV12";
  if(MediaSubType==MEDIASUBTYPE_ARGB32) return "ARGB32";
  if(MediaSubType==MEDIASUBTYPE_Y422) return "Y422";
  if(MediaSubType==MEDIASUBTYPE_v210) return "V210";
  if(MediaSubType==MEDIASUBTYPE_R210) return "R210";			// XXrrrrrr rrrrgggg ggggggbb bbbbbbbb https://wiki.multimedia.cx/index.php/R210
  if(MediaSubType==MEDIASUBTYPE_A2B10G10R10) return "A2B10G10R10";
  if(MediaSubType==MEDIASUBTYPE_A2R10G10B10) return "A2R10G10B10";
  if(MediaSubType==MEDIASUBTYPE_R16G16B16) return "R16G16B16";
  if(MediaSubType==MEDIASUBTYPE_B16G16R16) return "B16G16R16";
  if(MediaSubType==MEDIASUBTYPE_A16R16G16B16) return "A16R16G16B16";
  if(MediaSubType==MEDIASUBTYPE_A16B16G16R16) return "A16B16G16R16";

  return wxString::Format("{%8.8X-%4.4X-%4.4X-%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X}", MediaSubType.Data1, MediaSubType.Data2, MediaSubType.Data3,
      MediaSubType.Data4[0], MediaSubType.Data4[1], MediaSubType.Data4[2], MediaSubType.Data4[3],
      MediaSubType.Data4[4], MediaSubType.Data4[5], MediaSubType.Data4[6], MediaSubType.Data4[7]);
}


/** Scan camera capabilities and select best video mode. */
void CameraInput::ChangeResolution(void)
{
TAudVidConfCaps *PAudVidConfCaps;
wxArrayString ResolutionModes;
wxString Str;
HRESULT hr;
int CurrentMode;

  if(camera==NULL) return;  
 
  int count = 0;
  int size = 0;
  hr = camera->GetNumberOfCapabilities(&count, &size);
  if(FAILED(hr)) return;

  if(count <= 1)	// Provide format unlock, only AV object camera needs this.
  {
    camera->SetFormat(NULL);
    hr = camera->GetNumberOfCapabilities(&count, &size);
    if(FAILED(hr)) return;
  }
  if(size > 0x10000) return;	// More than 64kiB is suspicious.

  AM_MEDIA_TYPE* mt;
  int planes;  

  if(size<sizeof(TAudVidConfCaps)) size=sizeof(TAudVidConfCaps);
  PAudVidConfCaps = (TAudVidConfCaps *)CoTaskMemAlloc(size);
  if(PAudVidConfCaps==NULL) return;

  CurrentMode = 0; 
  mt = NULL;
  if(count<=0) count = 1;
  for(int i=0; i<count; i++)
    {          
      hr = camera->GetStreamCaps(i, &mt, (BYTE*)PAudVidConfCaps);
      if(hr==S_FALSE) break;		// index too high
      if(FAILED(hr)) continue;
 
      const BITMAPINFOHEADER *pBmi = NULL;
      if(mt->pbFormat!=NULL)
      {
        if(mt->formattype==FORMAT_VideoInfo)
        {
          const VIDEOINFOHEADER *pVIH = (const VIDEOINFOHEADER*)mt->pbFormat;
          pBmi = &pVIH->bmiHeader;
        }
        if(mt->formattype==FORMAT_VideoInfo2)
        {
          const VIDEOINFOHEADER2 *pVIH2 = (const VIDEOINFOHEADER2*)mt->pbFormat;
          pBmi = &pVIH2->bmiHeader;
        }
      }

      if(pBmi==NULL || 
         (pBmi->biWidth==PAudVidConfCaps->v.InputSize.cx && pBmi->biHeight==PAudVidConfCaps->v.InputSize.cy))
      {
        planes = (8*mt->lSampleSize)/labs(PAudVidConfCaps->v.InputSize.cx*PAudVidConfCaps->v.InputSize.cy);
        Str.Printf(wxT("%dx%d: %dbpp "),PAudVidConfCaps->v.InputSize.cx,PAudVidConfCaps->v.InputSize.cy,planes);
        if(Width==PAudVidConfCaps->v.InputSize.cx && labs(Height)==labs(PAudVidConfCaps->v.InputSize.cy) && planes==Planes)
            CurrentMode=i;
      }
      else
      {
        planes = (8*mt->lSampleSize)/labs(pBmi->biWidth*pBmi->biHeight);
        Str.Printf(wxT("%dx%d: %dbpp Input:%dx%d "), pBmi->biWidth, pBmi->biHeight, planes, PAudVidConfCaps->v.InputSize.cx, PAudVidConfCaps->v.InputSize.cy);
        if(Width==pBmi->biWidth && labs(Height)==labs(pBmi->biHeight) && planes==Planes)
            CurrentMode=i;
      }
      Str += TranslateVideoMode(mt->subtype);

      if(mt->formattype==FORMAT_VideoInfo2) Str+=" *INF2*";

      ResolutionModes.Add(Str);
      

      if(mt->pbFormat != NULL)		// There was a leak, when this was not checked!
        {
	CoTaskMemFree(mt->pbFormat);
	mt->pbFormat = NULL;
        }
      CoTaskMemFree(mt); mt=NULL;
    }

   if(ResolutionModes.GetCount()>1)
   {
    int result = wxGetSingleChoiceIndex(_("Select video mode:"),
	   _("Available Video Modes"), CurrentMode, ResolutionModes, NULL //wxFindSuitableParent()
	   );
    if(result>=0)
    {   	//setup an user chosen format
      VideoFormat = vfUnspecified;
      hr = camera->GetStreamCaps(result, &mt, (BYTE*)PAudVidConfCaps);
      if(SUCCEEDED(hr))
      {
        const BITMAPINFOHEADER *pBmi = NULL;
        if(mt->formattype == FORMAT_VideoInfo)
        {
          VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)mt->pbFormat;
          if(pVIH)
          {
            if(FrameDuration!=0) pVIH->AvgTimePerFrame=FrameDuration; //use a same frame rate
            FrameDuration = pVIH->AvgTimePerFrame;
	    pBmi = &pVIH->bmiHeader;
          }
        }
        else if(mt->formattype == FORMAT_VideoInfo2)
        {
          VIDEOINFOHEADER2 *pVIH2 = (VIDEOINFOHEADER2*)mt->pbFormat;
          if(pVIH2)
          {
            if(FrameDuration!=0) pVIH2->AvgTimePerFrame=FrameDuration; //use a same frame rate
            FrameDuration = pVIH2->AvgTimePerFrame;
            pBmi = &pVIH2->bmiHeader;
          }
        }

        hr = camera->SetFormat(mt);
        if(FAILED(hr))		// Try to RESET video format.
        {
          hr = camera->SetFormat(NULL);
          hr = camera->SetFormat(mt);
        }

        if(SUCCEEDED(hr))
        {
          VideoFormat = GetVideoformat(&mt->subtype);
	  if(VideoFormat == vfRGB8)
	  {	    
	    if(mt->pbFormat!=NULL)
            {
              if(mt->cbFormat>=sizeof(VIDEOINFO) &&  mt->formattype==FORMAT_VideoInfo)
	      {
	        VIDEOINFO *pvi = (VIDEOINFO*)mt->pbFormat;
                if(pvi->bmiHeader.biSize == sizeof(BITMAPINFOHEADER))	// WARNING!!! DO NOT use this structure unless you are sure that the BITMAPINFOHEADER
                {							// has a normal biSize == sizeof(BITMAPINFOHEADER)!
	          if(pCameraPalette==NULL) pCameraPalette=(TCameraPalette*)malloc(sizeof(TCameraPalette)*256);
	          if(pCameraPalette != NULL)
	          {
		    for(int i=0; i<256; i++)
		    {
		      pCameraPalette[i].R = pvi->TrueColorInfo.bmiColors[i].rgbRed;
		      pCameraPalette[i].G = pvi->TrueColorInfo.bmiColors[i].rgbGreen;
		      pCameraPalette[i].B = pvi->TrueColorInfo.bmiColors[i].rgbBlue;
                    }
		  }
	        }
	      }	    
	      //if(mt->cbFormat>=sizeof(VIDEOINFO2) &&  mt->formattype==FORMAT_VideoInfo2)
	      //{
              //}
	    }
          }
          
          if(VideoFormat==vfdvsl || VideoFormat==vfdvsd || VideoFormat==vfdvhd
             //mt->subtype==MEDIASUBTYPE_dv25 || mt->subtype==MEDIASUBTYPE_dv50 ||
             //mt->subtype==MEDIASUBTYPE_dvh1  //proffessional types, CLSID not found.
              )  
             {
	     UseFilter(CLSID_DVVideoCodec);
             if(filter_base==NULL)
                 wxMessageBox(_T("Cannot obtain COM object CLSID_DVVideoCodec!"), _T("Camera Viewer demo"), wxOK|wxICON_EXCLAMATION);
             mt->subtype = MEDIASUBTYPE_RGB24;
	     grabber->SetMediaType(mt);
             }
          else if(VideoFormat == vfMJPG)
              {
              UseFilter(CLSID_MjpegDec);
              if(filter_base==NULL)
                 wxMessageBox(_T("Cannot obtain COM object CLSID_MjpegDec!"), _T("Camera Viewer demo"), wxOK|wxICON_EXCLAMATION);
              mt->subtype = MEDIASUBTYPE_RGB24;
              grabber->SetMediaType(mt);
              }
          else
	  {
              if(filter_base != NULL)
              {
                IFilterGraphPtr ifg(graph);
                if(ifg!=NULL) hr=ifg->RemoveFilter(filter_base);
	        filter_base=NULL;
              }
          }

          if(filter_base==NULL) grabber->SetMediaType(NULL);		// Reset forced media type.
          //if(mt->subtype==MEDIASUBTYPE_YUY2) mode = 1;

          if(pBmi)
          {
            Width = pBmi->biWidth;
            Height = pBmi->biHeight;
          }
          else
          {
            Width = PAudVidConfCaps->v.InputSize.cx;
            Height = PAudVidConfCaps->v.InputSize.cy;
          }
          if(filter_base==NULL)
          {
            Planes = (8*mt->lSampleSize)/labs(Width*Height);
            if(Planes<8)
            {
              if(pBmi) Planes = pBmi->biPlanes*pBmi->biBitCount;
            }
          }
          else		// Grabber is ussing a different video type than camera.
          {   
            if(mt->subtype==MEDIASUBTYPE_RGB24) Planes=24;
            else if(mt->subtype==MEDIASUBTYPE_RGB32) Planes=32;
            else Planes=24;
          }

          MinFrameDuration = PAudVidConfCaps->v.MinFrameInterval;
          MaxFrameDuration = PAudVidConfCaps->v.MaxFrameInterval;

          if(pBmi!=NULL && pBmi->biCompression!=0 && filter_base==NULL && Planes<8)             
          {            
            wxMessageBox(
               _T("Compressed camera stream is not supported, please select another mode!"),
	       _T("Camera Viewer demo"), wxOK|wxICON_EXCLAMATION);
	  }

          hr = BuildGraph();
          if(FAILED(hr))
          {
             wxString str;
             str.Printf(wxT(_("Cannot build graph, error %Xh!")), hr);
             wxMessageBox(str, _T("Camera Viewer demo"), wxOK|wxICON_EXCLAMATION);
             return;
          }
        }
      }
      if(FAILED(hr))
      {
	wxString str;
        str.Printf(wxT(_("Changing camera resolution failed, try it again!\nSetFormat returned error %Xh.")), hr);
        wxMessageBox(str, _T("Camera Viewer demo"), wxOK|wxICON_EXCLAMATION);
      }
    }
  }

  if(PAudVidConfCaps) {CoTaskMemFree(PAudVidConfCaps);PAudVidConfCaps=NULL;}
  if(mt) 
    {
    if(mt->pbFormat != NULL)		// There was a leak!
      {
      CoTaskMemFree(mt->pbFormat);
      mt->pbFormat = NULL;
      }
    CoTaskMemFree(mt); mt=NULL;
    }
}