#ifdef __FB_JS__
  #cmdline "-O 3 -Wc '-msimd128' -Wl '-s LEGACY_GL_EMULATION=1 --embed-file PlayVideo.txt'"
#else
  #cmdline "-gen gcc -O 3 -fpu sse -Wc '-Ofast -march=native -mavx2'"
#endif

#include "pl_mpeg.bi"
#include "GL/gl.bi"
#include "crt.bi"
#include "fbgfx.bi"
#include "file.bi"

#ifndef gluPerspective
sub gluPerspective( fovy as GLdouble, aspect as GLdouble,zNear as GLdouble, zFar as GLdouble )
  dim as GLdouble xmin, xmax, ymin, ymax
  
  ymax = zNear * tan( fovy * M_PI / 360.0 )
  ymin = -ymax
  
  xmin = ymin * aspect
  xmax = ymax * aspect
  
  glFrustum( xmin, xmax, ymin, ymax, zNear, zFar )
end sub
#endif

#ifdef __FB_JS__
  #include "emscripten.bi"
  #include "MiniSndJS.bas"
#else
  #include "MyTDT\GfxResize.bas"
  #include "MyTDT\MiniSnd.bas"
#endif

#ifdef __FB_JS__  
  emscripten_run_script("document.body.style.backgroundColor='black'")  
  dim shared as byte iAsyncLoad=1 , iAsyncLoaded , iAsyncError
  sub OnLoad()  : iAsyncLoaded += 1 : end sub 'puts("Block loaded") : end sub
  sub onError() : iAsyncError += 1  : end sub 'puts("Block error") : end sub
  function WaitAsyncLoad( sFile as string ) as long  
    var sScript = _
      "(function(){" _
        "var a=Module['DynLoaded'];if(!a)return 0;" _
        "var i=a.indexOf('"+sFile+"');" _
        "if(i!==-1){a.splice(i,1);return 1;}return 0;" _
      "})();"
    do until iAsyncLoaded andalso emscripten_run_script_int(sScript)
      sleep 5,1 ': puts(".")
      if iAsyncError then return 0
    loop
    iAsyncLoaded -= 1 : return 1
  end function
  sub AsyncOpen( sFile as string )
    puts(sFile)
    iAsyncError = 0 : emscripten_async_load_script( sFile & ".js" , @OnLoad , @OnError )
  end sub
#else
  #define WaitAsyncLoad(_f) 1
  #define AsyncOpen( _sFile )
#endif

sub frame_to_rgba( pFrame as plm_frame_t ptr , pBuff1 as ulong ptr , iPitch as long )  
  var pInCr = pFrame->Cr.data , pInCb = pFrame->Cb.data  
  with pFrame->y
    var pIn1 = .data , pIn2 = .data+.width
    var pBuff2 = pBuff1 : cast(ubyte ptr,pBuff2) += iPitch
    for iY as long = 0 to .height-1 step 2
      for iX as long = 0 to .width-1 step 4        
        
        ' Fetch chroma and apply the -128 offset immediately
        dim as long Cr = pInCr[0] - 128 , cB = pInCb[0]-128 , Y = any 
                
        ' Fixed-point math (Constants multiplied by 1024)
        ' 1.402 * 1024 = 1436
        ' -0.344 * 1024 = -352
        ' -0.714 * 1024 = -731
        ' 1.772 * 1024 = 1815
        dim as long iB = any , iB_ = (1436 * Cr) shr 10
        dim as long iG = any , iG_ = (-352 * cB - 731 * Cr) shr 10
        dim as long iR = any , iR_ = (1815 * cB) shr 10
        
        #macro DoRGB()
          iB = Y + iB_ : iG = Y + iG_ : iR = Y + iR_          
          ' Your bitwise clamp (relies on FB culng overflow)
          iB = iif( culng(iB) > 255 , (not ((iB shr 31)) and 255) , iB )
          iG = iif( culng(iG) > 255 , (not ((iG shr 31)) and 255) , iG )
          iR = iif( culng(iR) > 255 , (not ((iR shr 31)) and 255) , iR )          
        #endmacro
        
        Y = pIn1[iX+0] : DoRGB()        
        pBuff1  [iX+0] = rgb(iR,iG,iB)
        Y = pIn1[iX+1] : DoRGB()        
        pBuff1  [iX+1] = rgb(iR,iG,iB)        
        Y = pIn2[iX+0] : DoRGB()        
        pBuff2  [iX+0] = rgb(iR,iG,iB)
        Y = pIn2[iX+1] : DoRGB()        
        pBuff2  [iX+1] = rgb(iR,iG,iB)
        
        Cr = pInCr[1]-128 : cB = pInCb[1]-128 
        pInCr += 2 : pInCb += 2
        iB_ = (1436 * Cr) shr 10
        iG_ = (-352 * cB - 731 * Cr) shr 10
        iR_ = (1815 * cB) shr 10
        
        Y = pIn1[iX+2] : DoRGB()        
        pBuff1  [iX+2] = rgb(iR,iG,iB)
        Y = pIn1[iX+3] : DoRGB()        
        pBuff1  [iX+3] = rgb(iR,iG,iB)        
        
        Y = pIn2[iX+2] : DoRGB()        
        pBuff2  [iX+2] = rgb(iR,iG,iB)
        Y = pIn2[iX+3] : DoRGB()        
        pBuff2  [iX+3] = rgb(iR,iG,iB)
        
        
      next iX
      cast(ubyte ptr,pBuff1) += iPitch*2 : pIn1 += .width*2
      cast(ubyte ptr,pBuff2) += iPitch*2 : pIn2 += .width*2        
    next iY
  end with
end sub

#define LocalTest

sub PlayVideo( sFile as string ) export
    
  #define Stream
  const cBufSz = 512*1024 , _cUpdate = 1/20
      
  #ifdef Stream    
    var iFile = freefile(), pBlock = cptr(ubyte ptr,malloc(cBufSz)) , bEof = 0
    var sFullFile = sFile+"/0" : AsyncOpen( sFullFile )
    var iBlock = WaitAsyncLoad(sFullFile)
    if open(sFullFile for binary access read as #iFile) then puts("failed to open file"):exit sub
    var pFileBuff = plm_buffer_create_with_capacity( cBufSz*2 )    
    var pVid = iif( pFileBuff , plm_create_with_buffer( pFileBuff , true ) , cast(any ptr, 0) )
    if pVid=0 then 
      if pFileBuff then plm_buffer_destroy( pFileBuff ): pFileBuff=0 
      close #iFile : puts("failed to open video"): exit sub
    end if
    get #iFile,,*pBlock,cBufSz
    plm_buffer_write(pFileBuff, pBlock, cBufSz)
    close #iFile
    sFullFile = sFile+"/" & iBlock : AsyncOpen( sFullFile )    
  #else
    var pVid = plm_create_with_filename( sFile )
    if pVid=0 then exit sub  
  #endif  
  
  var iWid = plm_get_width( pVid ) , iHei = plm_get_height( pVid )  
  var iSoundRate = plm_get_samplerate( pVid )
  var dRate = plm_get_framerate( pVid ) 
  #define dDur 0 'plm_get_duration( pVid )  
  
  'printf(!"%ix%i %ihz %i:%02i\n",iWid,iHei,cint(iSoundRate), cint(dDur)\60 , cint(dDur) mod 60)
      
  #ifdef LocalTest
    #ifdef __FB_JS__    
      screenres iWid*2,iHei*2,32,,fb.GFX_OPENGL    
    #else
      Gfx.PreResize()
      screenres iWid*1,iHei*1,32,,fb.GFX_OPENGL
      Gfx.Resize(iWid,iHei,0,1)  
    #endif
  #endif
          
  var hSnd = iif( plm_get_audio_enabled(pVid) andalso iSoundRate , AudioOpen( iSoundRate , 16 , 2 , 8 ) , 0 )
  var iSamplesPerFrame = cint(int(iSoundRate/dRate))
  var iReadPos = 0, iReadAvail = 0

  dim as ulong pcm16(iSamplesPerFrame)
  dim as plm_samples_t ptr pAudio
  dim as double dMovie = timer
  dim as GLuint texID
  
  var pBuff = Malloc(iWid*iHei*4)
  
  #ifndef LocalTest
    glPushAttrib(GL_ALL_ATTRIB_BITS)      
    glDisable(GL_DEPTH_TEST) : glDisable(GL_LIGHTING)
    glDisable(GL_CULL_FACE)  : glDisable(GL_BLEND)
    glDisable(GL_FOG)        : glDisable(GL_ALPHA_TEST)    
  #endif
  
  glMatrixMode(GL_PROJECTION)    
  glPushMatrix()
  glLoadIdentity()
  glMatrixMode(GL_MODELVIEW)
  glPushMatrix()
  glLoadIdentity()
  
  glOrtho(-1,1,-1,1,0.1,3)
  'gluPerspective(53, 1, 0.1, 3)
  
  glColor4f(1.0, 1.0, 1.0, 1.0)
  glViewPort(0,0,iWid*2,iHei*2)
    
  glEnable(GL_TEXTURE_2D)      'Texture Mapping
  glClearDepth 0.0           'Depth Buffer Setup
  glClearColor(0f, 0f, 0f, 1.0f)
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
  glDisable(GL_BLEND)
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
  glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE)
  glDisable(GL_DEPTH_TEST)
  'glEnable(GL_DITHER)
  glDisable(GL_DEPTH_WRITEMASK)
    
  glGenTextures(1, @texID)
  glBindTexture(GL_TEXTURE_2D, texID)

  #ifndef GL_CLAMP_TO_EDGE
    #define GL_CLAMP_TO_EDGE &h812F
  #endif

  ' Set texture filtering to NEAREST so our 2x2 texture scales up with crisp, pixelated edges
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)  
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)

  ' Upload the pixel data from system memory to the GPU
  glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA, iWid, iHei, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)
    
  dim as plm_frame_t ptr pFrame
  
  dim fPct as single, iTotalTime as long
  
  do until plm_has_ended( pVid ) orelse len(inkey)
    
    #ifdef LocalTest
      static as long lSmall = (1 shl 31)-1
      dim as zstring*64 zTit = any 
      dim as long iTime : if pFrame then iTime=int(pFrame->time*10)
      sprintf(zTit,"%1.2f%% %02i:%02i.%i of %02i:%02i %ins",fPct,iTime\600,(iTime\10) mod 60,iTime mod 10,iTotalTime\60,iTotalTime mod 60,lSmall)
      windowtitle(zTit)
    #endif
    
    #ifdef Stream
      if bEof=0 andalso plm_buffer_get_remaining( pFileBuff ) < cBufSz then        
        if WaitAsyncLoad(sFullFile)=0 then puts("Failed to load next stream segment"):exit sub
        if open(sFullFile for binary access read as #iFile) then puts("Failed"):exit sub
        dim as uinteger uRead 
        get #iFile,,*pBlock,cBufSz,uRead : close #iFile        
        #ifdef __FB_JS__
          kill(sFullFile)
        #endif
        puts( sFullFile & " " & uRead\1024 & "kb" )
        plm_buffer_write(pFileBuff, pBlock, uRead ) : iBlock += 1
        sFullFile = sFile+"/" & iBlock
        if uRead < (cBufSz-1) then 'fileexists(sFile+"/" & iBlock)=0 then 
          plm_buffer_signal_end( pFileBuff ) : bEof=1
        else
          AsyncOpen( sFullFile )
        end if
      end if
    #endif
    
    pFrame = plm_decode_video( pVid )
    if pFrame = 0 then puts("frame decode failed"): exit do
    
    var iSampleCnt = 0
    while iSampleCnt < iSamplesPerFrame  
      'puts(iSampleCnt & " - " & iSamplesPerFrame & " ? " & iReadPos & ":" & iReadAvail )
      if iReadPos >= iReadAvail then
        iReadPos = 0 : pAudio = plm_decode_audio( pVid )
        if pAudio=0 then exit while
        iReadAvail = pAudio->count*2      
      end if
      var SamL = pAudio->interleaved(iReadPos)  ': if SamL < -1 then SamL = -1 else if SamL > 1 then SamL = 1
      var SamR = pAudio->interleaved(iReadPos+1)': if SamR < -1 then SamR = -1 else if SamR > 1 then SamR = 1        
      pcm16(iSampleCnt) = (cushort(cshort(SamR*32767)) shl 16)+cushort(cshort(SamL*32767))
      iReadPos += 2 : iSampleCnt += 1
    wend  
    
    static as double dFlip
    if abs(timer-dFlip) > .5 then dFlip=timer
    
    if (timer-dFlip) >= _cUpdate then '(.5/dRate) then       
      #if defined(LocalTest) andalso (not defined(__FB_JS__))
        dim as hwnd hWnd
        screencontrol fb.GET_WINDOW_HANDLE,cast(uinteger,hWnd)
        dim as RECT tRc = any : GetClientRect(hWnd,@tRc)
        glViewPort(0,0,tRc.right,tRc.bottom)
      #endif
      dim as byte iBest
      for N as long = 0 to 0 '15
        dim as double dChk = timer
        'plm_frame_to_rgba( pFrame , pBuff , iWid*4 )
        frame_to_rgba( pFrame , pBuff , iWid*4 )        
        var lChk = cint((timer-dChk)*1000000)
        if lChk < lSmall then lSmall = lChk : iBest=1
      next N
      'if iBest then printf(!"%i\n",lSmall)
      glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, iWid, iHei, GL_RGBA, GL_UNSIGNED_BYTE, pBuff)      
      
      glBegin(GL_TRIANGLES)
        ' 1. Top-Left (Anchored perfectly at your original top-left corner)
        glTexCoord2f(0.0, 0.0) : glVertex3f(-1.0,  1.0, -2.0)
        ' 2. Bottom-Left (Shoots straight down, twice the height of the screen)
        glTexCoord2f(0.0, 2.0) : glVertex3f(-1.0, -3.0, -2.0)
        ' 3. Top-Right (Shoots straight right, twice the width of the screen)
        glTexCoord2f(2.0, 0.0) : glVertex3f( 3.0,  1.0, -2.0)
      glEnd()

      ' Swap the front and back buffers to display the frame
      #ifdef __FB_JS__
        sleep 1,1
      #endif
      flip : dFlip += _cUpdate 'timer 'dFlip += (.5/dRate)
    end if
    
    if multikey(fb.SC_RSHIFT)=0 then '#if 1
      if hSnd andalso iSampleCnt then    
        'AudioWrite( hSnd , @pcm16(0) , sizeof(ulong)*iSampleCnt ) ': sleep 1,1
        while AudioWrite( hSnd , @pcm16(0) , sizeof(ulong)*iSampleCnt ) < 0
          sleep 1,1      
        wend  
      else      
        if abs((timer-dMovie)-(pFrame->time)) > 1 then '.1/dRate then 
          dMovie = timer-pFrame->time
        else
          while (timer-dMovie) < (pFrame->time)
            sleep 1,1      
          wend      
        end if
      end if
    elseif hSnd andalso iSampleCnt then
      AudioWrite( hSnd , @pcm16(0) , sizeof(ulong)*iSampleCnt )
    end if
    
  loop
  
  glDeleteTextures(1, @texID)
  
  #ifndef LocalTest
    glMatrixMode(GL_PROJECTION)
    glPopMatrix()   ' Restore 3D Projection  
    glMatrixMode(GL_MODELVIEW)
    glPopMatrix()   ' Restore 3D Camera/World  
    glPopAttrib()  
  #endif  
  
  if hSnd then AudioClose(hSnd)

  plm_destroy( pVid )
  free(pBuff)
  
end sub

#ifdef LocalTest
  'var sVideo = "H:\Ext-Drive\Videos\GamePlay\tlv.mpg"
  'var sVideo = "E:\Programas\dosboxwin95\windows95b\funstuff\videos\mpeg\goodtime2.mpg"
  dim as long iVideo = -1
  dim as zstring ptr pzVideo(...) = { _
    @"INTRO.MPG.stream", _
    @"goodtime2.mpg.stream" _
  }
  #ifdef __FB_JS__
    var sScript = _
      "iChoice=-1; " _
      "document.getElementById('canvasTitle').innerHTML = " _
      "'Which Video? <button onclick=""iChoice = 0;"">Game Intro</button>" _
      "<button onclick=""iChoice = 1;"">Good Times</button>';"
    emscripten_run_script(sScript)
    do
      iVideo = emscripten_run_script_int("iChoice;")
      if iVideo >= 0 then exit do
      sleep 50,1
    loop
  #else
    print " Mpeg Player"
    print 
    print " 1 - Game Intro"
    print " 2 - Good Times"
    do
      var sKey = inkey()
      if len(sKey)=0 then sleep 50,1 : continue do
      select case sKey[0]
      case 27      : exit do
      case asc("1"): iVideo=0 : exit do
      case asc("2"): iVideo=0 : exit do
      end select
    loop
  #endif
  if iVideo >= 0 then PlayVideo(*pzVideo(iVideo))
  puts("done")
#endif