/* 
 *  Thunder DVD MISM 1.0 for flaskmpeg by idude33@hotmail.com
 *    
 *  This file is part of FlasKMPEG, a free MPEG to MPEG/AVI converter
 *	
 *  FlasKMPEG is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  FlasKMPEG is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */

#include "stdafx.h"

#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include "flaskmpegmism.h"
#include "inputstream.h"
#include "resource.h"
#include "dvdselector.h"

#include <string>
#include <vector>
using namespace std;


#define BtoMB(A) ((double)A/1048576)


#define SWAP_INT32(x)  ((((ui8*)&x)[0] << 24) |  \
                         (((ui8*)&x)[1] << 16) |  \
                         (((ui8*)&x)[2] << 8) |   \
                         ((ui8*)&x)[3])   
#define INPUT_NOEXIST			     1
#define IS_TRANSPORTSTREAM     2
#define NOT_RECOGNIZED			   3
#define NO_AUDIOVIDEO          4
#define PACK_START_CODE        0x000001BA

// This points to the last error that ocurred
static char *lastError;

// Global params from initialization
FMMismStdParms params;


struct TRunTimeInfo
{
  CIFOParser   *pIFOParser;
  TDVDSelector  sSelectionInfo;
  TExtraInfo    sExtraInfo;
};

static TRunTimeInfo gsRunTimeInfo;
static FMMismProperties properties;


HWND   hDlgParser, hSelectStream;
unsigned int videoFound, audioFound;
fpos_t pos;


#define GS(x) (params.GetString(x))



int PrepareInputFromIFO(CinputStream *input, CIFOParser *ifo, TDVDSelector *sel)
{
  int i,val;
  TPlaySequenceSpan inpSpan;
  
  if(!input || !ifo || !sel)
    return 0;
  for(i=0; i<ifo->PGCs[sel->selected_pgc].BuiltPGC[sel->selected_angle].playinfo.GetCount(); i++)
  {
    inpSpan.start  =  	
      ifo->PGCs[sel->selected_pgc].BuiltPGC[sel->selected_angle].playinfo[i].start;
    inpSpan.end    =  	
      ifo->PGCs[sel->selected_pgc].BuiltPGC[sel->selected_angle].playinfo[i].end;
    
    val=input->AddSpan(&inpSpan);
    if(!val)
      return 0;
  }
  return input->ValidatePlaySequence();
}

bool GetVobStreams(fmStreamIds *ids, char *file)
{
  // This is kinda a hack but it should work.
  // Use the NULL mism to look for stream ids
  fmGetMismPtr          fmGetMism;
  HINSTANCE hModule;
  FMMismInfo sMismInfo;

  if( hModule = LoadLibrary( "null.mism.flask" ) )
  { 
    if( fmGetMism = (fmGetMismPtr)GetProcAddress( hModule, "fmGetMism" ) )
    { 
      fmGetMism( &sMismInfo );
      
      // Initialize Mism
      sMismInfo.Init( &params );
      // Get Streams
      if(sMismInfo.GetStreams(ids, file)!=FM_MISM_OK)
      {
        FreeLibrary(hModule);
        return false;
      }
      FreeLibrary(hModule);
      return true;
    }
  }
  return false;
}

int FindStreams(fmStreamIds *ids, char *file)
{
  int subpic_stream_id = -1, subpic_substream_id = -1;
  int len;
  
  // Create an IFO parser
  if(gsRunTimeInfo.pIFOParser)
    delete gsRunTimeInfo.pIFOParser;
  gsRunTimeInfo.pIFOParser = new CIFOParser;
  
  
  // Detect the streams
  // Strulct to store the results
  if(gsRunTimeInfo.pIFOParser->Load(file))
  {
    if(OpenDVDSelector(params.hWnd, params.hInst, 
                       gsRunTimeInfo.pIFOParser, 
                      &gsRunTimeInfo.sSelectionInfo)){
      
      // Preparing input file
      len = strlen(file);
      
      file[len-5] = '1';
      file[len-4] = '.';
      file[len-3] = 'v';
      file[len-2] = 'o';
      file[len-1] = 'b';
      
      // Try opening the file
      FILE *testFile;
      if( !(testFile=fopen(file, "rb")) )
        return 0;
      fclose(testFile);

 

      // Check that the input is valid.
      CinputStream *pTestInput = new CinputStream;
      pTestInput->OpenStream(file, DVD_MODE);
      pTestInput->SetWorkingMode(PLAYSEQ_MODE);
      
      // Prepare CinputStream
      if(!PrepareInputFromIFO(pTestInput, 
                              gsRunTimeInfo.pIFOParser , 
                             &gsRunTimeInfo.sSelectionInfo)){
        delete gsRunTimeInfo.pIFOParser;
        delete pTestInput;
        gsRunTimeInfo.pIFOParser = NULL;
        return 0;
      }     
      
      if(gsRunTimeInfo.sSelectionInfo.has_subpic){
        subpic_stream_id = 0xBD;
        subpic_substream_id = 0x20 + gsRunTimeInfo.sSelectionInfo.selected_subpic;
      }
      
      vector<fmStreamId> vAudioIds;

      if(gsRunTimeInfo.sSelectionInfo.has_audio)
      { 
        int nSelected = gsRunTimeInfo.sSelectionInfo.selected_audio;

        // Loop through all the audio streams
        for(int i=0; i<gsRunTimeInfo.sSelectionInfo.audio_count; i++)
        {
          fmStreamId strId;

          strId.streamId    = gsRunTimeInfo.sSelectionInfo.audio[i].type ==  Ac3Audio ? 
                              0xBD : (gsRunTimeInfo.sSelectionInfo.audio[i].type==MpegAudio ? 
                              (0xC0 + i ) : 0 );

          strId.subStreamId = gsRunTimeInfo.sSelectionInfo.audio[i].type == Ac3Audio ? 
                              (0x80+ i ) : 0; 

          if( strId.streamId  )
          {
            strcpy( strId.streamName, gsRunTimeInfo.sSelectionInfo.audio[i].name );
            vAudioIds.push_back(strId);
          }
        }
      }

      // Put the information into ids
      ids->idCount = 0;
      
      // The video is pressuposed?
      ids->vIds[ids->idCount].streamId    = 0xE0;
      ids->vIds[ids->idCount].subStreamId = 0xFF;
      ids->idCount++;

      // If we have audio
      if(gsRunTimeInfo.sSelectionInfo.has_audio && vAudioIds.size() )
      {
        // Audio default is indexed from vIds base.
        ids->idAudioDef = ids->idCount + (gsRunTimeInfo.sSelectionInfo.selected_audio % vAudioIds.size());
        for(int i=0; i<vAudioIds.size(); i++)
        {
          ids->vIds[ids->idCount] = vAudioIds[i];
          ids->idCount++;
        }
        
      }
      // If we have subpictures
      if(gsRunTimeInfo.sSelectionInfo.has_subpic){
        ids->vIds[ids->idCount].streamId    = subpic_stream_id;
        ids->vIds[ids->idCount].subStreamId = subpic_substream_id;
        ids->idCount++;
      }
      // Store the selected clut
      int selectedPgc = gsRunTimeInfo.sSelectionInfo.selected_pgc;
      gsRunTimeInfo.sExtraInfo.clut = &gsRunTimeInfo.pIFOParser->pgcs[selectedPgc].clut;
    }
  }
	return true;
}


bool IsVob(char *file)
{
  string fileTemp;
  
  fileTemp = file;

  strlwr( (char *)fileTemp.c_str() ); //Lowercase input file	
  int len = fileTemp.length();
  // May be its not an ifo. Maybe its a vob file.
  return fileTemp[len-4] == '.'  &&
         fileTemp[len-3] == 'v'  &&
         fileTemp[len-2] == 'o'  &&
         fileTemp[len-1] == 'b';

}

bool IsIfo(char *file)
{
  string fileTemp;
  
  fileTemp = file;
  
  strlwr( (char *)fileTemp.c_str() ); //Lowercase input file	
  int len = fileTemp.length();
  // May be its not an ifo. Maybe its a vob file.
  return fileTemp[len-4] == '.'  &&
    fileTemp[len-3] == 'i'  &&
    fileTemp[len-2] == 'f'  &&
    fileTemp[len-1] == 'o';
  
}
static bool IsVTS (char *id)
{
   return strncmp (id, "DVDVIDEO-VTS", 12)==0;
}
static bool IsVMG (char *id)
{
  return strncmp (id, "DVDVIDEO-VMG", 12)==0;
}

  // File IO ptrs
bool CanOpenFile(char *fileName, DWORD *merit)
{
  FILE *file;
  char pIfoStart[13];
  bool bIsVob=false;

  // Opening file
  if( !(file = fopen( fileName, "rb" )) )
  {
    lastError = "NullMISM :: Couldn't open file";
    return false;
  }


  fseek(file, 0, SEEK_SET);

  fread( &pIfoStart, 13, 1, file ); //Read first 13 bytes

  fclose( file );  

  bool bIsIfo = IsVTS(pIfoStart) || IsVMG(pIfoStart);
  if(bIsIfo) *merit = FM_MISM_MERIT_NORMAL + 1;

  //////////////////////////////////////////////
  // vob testing
  if(IsVob(fileName))
  {
    int startcode;
    startcode = *((int *)pIfoStart);
    bIsVob = (SWAP_INT32(startcode) == PACK_START_CODE);
    if(bIsVob) *merit = FM_MISM_MERIT_NORMAL + 1;
  }

  return  bIsIfo || bIsVob;
}

ui64 InternalGetStreamSize(fmHandle file);

fmHandle OpenStream(char *file)
{
  // We identify input streams with the handle.
  // Create a new input stream

  // An IFO parser must be created befor calling this, if this 
  // is an ifo
  if(IsIfo(file))
    if(!gsRunTimeInfo.pIFOParser)
      return NULL;

  // Make copy of input file name
  // because CinputStream modifies it
  string tmpStr = file;
  // Preparing input file
  int len = strlen(file);

  if(IsIfo(file))
  {
    tmpStr[len-5] = '1';
    tmpStr[len-4] = '.';
    tmpStr[len-3] = 'v';
    tmpStr[len-2] = 'o';
    tmpStr[len-1] = 'b';    
  }

  CinputStream *pNewInStream = new CinputStream;


  // Opening file and return it
  if( !pNewInStream->OpenStream((char *)tmpStr.c_str(), DVD_MODE) )
  {
    lastError = "DVDMISM :: Couldn't open file";
    return NULL;
  }

  // Select working mode
  int nWorkingMode = IsVob(file) ? NORMAL_MODE : PLAYSEQ_MODE;

  pNewInStream->SetWorkingMode(nWorkingMode);

  if(IsIfo(file))
  {
    // Prepare CinputStream
    if(!PrepareInputFromIFO(pNewInStream, 
                            gsRunTimeInfo.pIFOParser , 
                           &gsRunTimeInfo.sSelectionInfo))
      return NULL;
  }

  return (fmHandle)pNewInStream;
}

int ReadStream(fmHandle file, ui8 *buf, unsigned int size)
{
  return ((CinputStream *)file)->Read(buf, size);
}

int SetStreamPos(fmHandle file, ui64 pos)
{
  return ((CinputStream *)file)->SetStreamPosExt((i64)pos)>0 ? FM_MISM_OK : FM_MISM_ERROR;
}

// Set the position of the stream if the module supports FM_MISM_READFILES feature
// Return FM_MISM_ERROR otherwise.
int GetStreamPos(fmHandle file, ui64 *pos)
{
  *pos = (ui64)((CinputStream *)file)->GetStreamPosExt();
  return FM_MISM_OK;
}


// FIXME different handles could yield different streamSizes.
ui64 GetStreamSize( fmHandle strHandle )
{
  return (ui64)((CinputStream *)strHandle)->GetStreamSize();
}

int CloseStream(fmHandle file)
{
  if(!file)
    return FM_MISM_ERROR;
  
  delete ((CinputStream *)file);
  return FM_MISM_OK;
}

// Windows already defines GetLastError
char* GetLastErrorInt(void)
{
  return lastError;
}

int GetStreams(fmStreamIds *ids, char* fileName)
{
  if(IsVob(fileName))
  {
    if(!GetVobStreams(ids, fileName))
      return FM_MISM_ERROR;
  }
  else
  {
    // Backup file name
    // copy the name first
    string tmpStr = fileName;
    // Open the stream and look for stream Ids.
    if(!FindStreams( ids, (char *)tmpStr.c_str() ))
      return FM_MISM_ERROR;    
  }
  return FM_MISM_OK;
}

char* GetStreamStatus(fmHandle file)
{
  return gsRunTimeInfo.pIFOParser>0 ? "Normal" : "Idle";
}
char *GetFileName(fmHandle file)
{
  return ((CinputStream *)file)->GetFileName();
}

bool initDone = false;
void Init(LPFMMismStdParms parms)
{
  gsRunTimeInfo.pIFOParser = NULL;
  memcpy( &params, parms, sizeof FMMismStdParms );
  properties.dwMismId = 'DVDM';
  properties.dwSize  = sizeof properties;
  initDone = true;
}

void DeInit(void)
{
  if(gsRunTimeInfo.pIFOParser) 
    delete gsRunTimeInfo.pIFOParser;

  return;
}


extern "C"
{
  __declspec(dllexport) void __cdecl fmGetMism(LPFMMismInfo pMismInfo)
  {
    pMismInfo->dwSize         =  sizeof FMMismInfo;
    pMismInfo->dwVersion      =  FM_MISM_VERSION;
    pMismInfo->dwOutputTypes  =  FM_MISM_PROGRAMSTREAM;
    pMismInfo->pProperties    =  &properties;
    strcpy( pMismInfo->sDescription, "Thunder dvd MISM" );
    strcpy( pMismInfo->sExtensions, "*.ifo;*.vob" );
    pMismInfo->CanOpenFile      =   CanOpenFile;
    pMismInfo->OpenStream       =   OpenStream;
    pMismInfo->ReadStream       =   ReadStream;
    pMismInfo->SetStreamPos     =   SetStreamPos;
    pMismInfo->GetStreamPos     =   GetStreamPos;
    pMismInfo->CloseStream      =   CloseStream;
    pMismInfo->GetStreams       =   GetStreams;
    pMismInfo->GetLastError     =   GetLastErrorInt;
    pMismInfo->GetStreamStatus  =   GetStreamStatus;
    pMismInfo->GetStreamSize    =   GetStreamSize;
    pMismInfo->Init             =   Init;
    pMismInfo->DeInit           =   DeInit;
    pMismInfo->GetFileName      =   GetFileName;
    pMismInfo->dwReserved       =   (DWORD)&gsRunTimeInfo.sExtraInfo;
  }
}

