/* 
 *  inputStream.cpp 
 *
 *	Copyright (C) Alberto Vigata - January 2000 - ultraflask@yahoo.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 <windows.h>
#include <string.h>
#include "inputStream.h"


CinputStream::CinputStream(){
	int i;

	for( i=0; i<100; i++){
		iStream.file[i].handle = NULL;
		iStream.file[i].position=0;
		iStream.file[i].size=0;
	}

  m_pCurrentFile = NULL;

	iStream.actualFile=0;
	iStream.globalPos=0;
	iStream.nFiles=0;
	iStream.totalSize=0;

  m_nCurrentFilePos = 0;
  m_nCurrentFileSize = 0;

	workingMode = NORMAL_MODE;
	ps.sp = NULL;
	ps.span_count =0;
	ps.spanptr = 0;
	ps.total_size=0;

}
CinputStream::~CinputStream(){
  CloseStream();
	// free playsequence memory
	if(ps.sp)
		free(ps.sp);
}

int CinputStream::SetOneFileMode(int n_file){
	if(n_file<0)
		n_file=0;
	if(n_file>=iStream.nFiles)
		n_file=iStream.nFiles-1;
	iStream.actualFile= n_file;
	iStream.multipleFileMode=false;
	SetStreamPos(0);
	return 1;

}

int CinputStream::SetStreamPosExt(ui64 pos)
{
  if(workingMode==PLAYSEQ_MODE)
    return SetStreamPos(pos + ps.start);
  else
    return SetStreamPos(pos);
}

ui64 CinputStream::GetStreamPosExt()
{
  if(workingMode==PLAYSEQ_MODE)
    return GetStreamPos() - ps.start;
  else
    return GetStreamPos();
}

int inline CinputStream::SetStreamPos(i64 pos)
{
  int i;
  i64 a=0;
  i64 posbak;
  if(iStream.multipleFileMode){
    if( (pos+1) > iStream.totalSize )
      pos=iStream.totalSize;
    if(pos<0)
      pos=0;
    posbak = pos;
    //Go trough all files to find the one that owns this pos
    for( i=0; i<iStream.nFiles; i++)
    {
      //a   += iStream.file[i].size;
      if( pos<= iStream.file[i].size ){//this is the one
        fsetpos(iStream.file[i].handle, &pos);

        // Store information for fast access
        m_nCurrentFilePos = pos;
        m_nCurrentFileSize = iStream.file[i].size;
        m_pCurrentFile     = iStream.file[i].handle;

        iStream.actualFile=i;
        iStream.globalPos = posbak;

        return 1;
      }
      pos -= iStream.file[i].size;
    }
  }
  else{
    fsetpos(iStream.file[iStream.actualFile].handle, &pos);
    m_nCurrentFilePos  = pos;
    m_nCurrentFileSize = iStream.file[iStream.actualFile].size;
    m_pCurrentFile     = iStream.file[iStream.actualFile].handle;
    iStream.globalPos = pos;
    return 1;
  }
  return 0;

}
int CinputStream::CloseStream()
{
	int i;
	//Closes opened input_file
	for( i=0; i<iStream.nFiles ;i++)
		fclose( iStream.file[i].handle );
	return 1;
}

int CinputStream::SetMulFileMode(){
	iStream.multipleFileMode=true;
	return 1;
}


i64 CinputStream::GetStreamSize()
{
  if(workingMode==PLAYSEQ_MODE)
  {
    return ps.total_size;
  }
  else
  {
    if(iStream.multipleFileMode==true)
      return iStream.totalSize;
    else
      return iStream.file[iStream.actualFile].size;    
  }
}



i64 inline CinputStream::GetStreamPos()
{ 
	return iStream.globalPos;
}


////////////////////////////////////////////////////////////////
//			OpenStream(Inputfile, mode)
//
//	Opens and prepares a input file for reading.
//  Rules:  if the input file is something like vts_xx_1.vob
//			search for following vts_xx_y.vob files (up to y=9?)
//	If success return 1, 0 otherwise
//
///////////////////////////////////////////////////////////////
int CinputStream::OpenStream(char *inputFile, int mode)
{
  char szTemp1[1024],szTemp2[256],path[1024],*FileName,Message[10000];
  int  nameSize,i,finalNamePos;
  // mode=1 => DVDmode, mode=0=> NORMALmode
  if(!inputFile)
    return 0;
  
  
  strcpy(Message, "DVD MODE. The following files will be used\n as just one logical file\n");
  GetFullPathName( inputFile, 1024, path, &FileName);	
  strlwr( FileName ); //Lowercase input file	
  nameSize=strlen( FileName );
  if((mode&DVD_MODE) && (nameSize>=12)){//Open file in DVDmode
    
    strcpy( szTemp2, FileName );
    // file name to test= vts_xx_1.vob
    szTemp2[4]=szTemp2[5]=szTemp2[7]='x';
    strcpy(szTemp1, "vts_xx_x.vob");
    if( (strcmp(szTemp1, szTemp2) == 0) && FileName[7]!='0' ){ //The file has the format vts_xx_1.vob
      //Create all the files
      i=0;
      iStream.globalPos=0;
      iStream.actualFile=0;
      iStream.totalSize=0;
      do{
        strcpy( iStream.file[i].name, path);
        finalNamePos= strlen( inputFile ) - 5; // point here=> *.vob
        iStream.file[i].name[finalNamePos]= inputFile[finalNamePos] + i;
        iStream.file[i].handle= fopen(iStream.file[i].name, "rb");
        if( iStream.file[i].handle != NULL ){
          //Set buffer size
          setvbuf( iStream.file[i].handle, NULL, _IOFBF, 256000);
          iStream.nFiles=i+1;
          //file size
          fseek( iStream.file[i].handle,0,SEEK_END );
          fgetpos(iStream.file[i].handle, &iStream.file[i].size);
          fseek( iStream.file[i].handle,0,SEEK_SET );
          iStream.file[i].position=0;
          iStream.totalSize+= iStream.file[i].size;
          sprintf(szTemp1, "        %s           size: %.2f  MB\n",iStream.file[i].name, BtoMB(iStream.file[i].size));
          strcat(Message, szTemp1);
        }
        
      }while( iStream.file[i++].handle != NULL );
      if( iStream.file[0].handle == NULL )
        return 0;
      else{
        sprintf(szTemp1, "\nTotal size:     %.2f  MBytes", BtoMB(iStream.totalSize));
        strcat(Message, szTemp1);
//        if(mode & VERBOSE_MODE)
//          MessageBox(hMainWnd, Message, "Flask Warning", MB_OK);
        SetMulFileMode();
        m_nCurrentFilePos  = 0;
        m_pCurrentFile     = iStream.file[0].handle;
        m_nCurrentFileSize = iStream.file[0].size;
        return 1; //SUCCESS!!
      }
    }
  }
	//Not in DVD mode. Either the file was not suitable for DVDmode
	//DVDmode was disabled
	iStream.file[0].handle= fopen(inputFile,"rb");
	if( iStream.file[0].handle==NULL)
		return 0;
	strcpy(iStream.file[0].name, inputFile);
	iStream.globalPos=0;
	iStream.actualFile=0;					
	iStream.nFiles=1;

  m_nCurrentFilePos  = 0;
  m_pCurrentFile     = iStream.file[0].handle;
  m_nCurrentFileSize = iStream.file[0].size;

	fseek( iStream.file[0].handle,0,SEEK_END );
	fgetpos(iStream.file[0].handle, &iStream.file[0].size);
	fseek( iStream.file[0].handle,0,SEEK_SET );
	iStream.totalSize= iStream.file[0].size;
	SetMulFileMode();
	return 1;
	

}

///////////////////////////////////////////////////////////////
//         int ReadInputStream( buffer, size )
//	Reads size bytes and puts them into the buffer.
//  if the funcion is successful returns bytes written = size
//   if not, returns -1 for error, or last written bytes
//////////////////////////////////////////////////////////////
int CinputStream::Read( ui8 *buffer, int size)
{
	
	switch(workingMode)
	{
		case NORMAL_MODE:
			return __read(buffer, size);
		case PLAYSEQ_MODE:
			// The file pointer is not inside the current vob cell.
			if(iStream.globalPos < ps.sp[ps.spanptr].start  ||
			   iStream.globalPos > ps.sp[ps.spanptr].end)
			{
				if(iStream.globalPos > ps.sp[ps.span_count-1].end)
					return 0;
				AlignStreamToSpan();
			}
			remaining_bytes = size;
			while(1)
			{
				if( (partial=(int)(ps.sp[ps.spanptr].end+1 - iStream.globalPos)) < remaining_bytes )
				{
					bytes_read = __read(buffer, partial);
					
					if(bytes_read < partial)
						return size - remaining_bytes + bytes_read;
					buffer       += bytes_read;
					remaining_bytes -= partial;
					AlignStreamToSpan();
					continue;
				}
				else
				{
					// The remaining bytes belong to this cell
					return __read(buffer, remaining_bytes);
				}
			}
			
	}
	return 0;
	
}

int  inline CinputStream::__read( ui8 *buffer, int size)
{
	if(!size)
		return 0;

  if(iStream.multipleFileMode)
  {
    //fgetpos(iStream.file[iStream.actualFile].handle, &iStream.file[iStream.actualFile].position );
    //remainingBytes = iStream.file[iStream.actualFile].size - (iStream.file[iStream.actualFile].position);
    remainingBytes = m_nCurrentFileSize - m_nCurrentFilePos;
    if( size > remainingBytes){ //Oops..
      if( (iStream.actualFile+1) == iStream.nFiles ){ //return remaining bits
        ret = fread(buffer, 1, remainingBytes, iStream.file[iStream.actualFile].handle);
        
        m_nCurrentFilePos += ret;
        iStream.globalPos += ret;
        return ret;
      }
      else{
        //Read data from the actual file
        ret = fread(buffer,                      1, remainingBytes,      iStream.file[iStream.actualFile].handle);
        iStream.globalPos += ret;

        // Switch to the next file
        fseek(iStream.file[++iStream.actualFile].handle, 0L, SEEK_SET);
        ret = fread(&buffer[remainingBytes],     1, size-remainingBytes, iStream.file[iStream.actualFile].handle);

        // Update pointers
        m_nCurrentFilePos  = ret;
        iStream.globalPos += ret;
        
        // Update fast access info
        m_pCurrentFile      = iStream.file[iStream.actualFile].handle;
        m_nCurrentFileSize  = iStream.file[iStream.actualFile].size;

        return size;
      }
    }
    else{
      //Just copy the bytes
      ret = fread( buffer, 1, size, m_pCurrentFile );
      iStream.globalPos += ret;
      m_nCurrentFilePos += ret;
      return ret;
    }
    return 0;
  }
  else{
    ret = fread(buffer, 1, size, iStream.file[iStream.actualFile].handle);
    m_nCurrentFilePos += ret;
    iStream.globalPos += ret;
    return ret;
  }


}
char *CinputStream::GetFileName(){
	return iStream.file[iStream.actualFile].name;
}


int CinputStream::SetWorkingMode(int mode)
{
	workingMode = mode;
	return 1;
}
//  CinputStream::ValidatePlaySequence()
//
//    Checks the validity of the playsequence
//    that was previously set
int CinputStream::ValidatePlaySequence()
{
	if(ps.total_size <=0)
		return 0;
	if(iStream.totalSize < ps.total_size)
		return 0;

  ps.start = ps.sp[0].start;
  ps.end  = ps.sp[ ps.span_count-1 ].end;

	return 1;
}
// This method alings a stream to the next available span
//    if the file pointer is inside a span, it moves the pointer
//    to the beginning of the span.
int inline CinputStream::AlignStreamToSpan()
{
	int i;
	// First update the current cell pointer
	ps.spanptr++;
	if( ps.spanptr >= ps.span_count )
	    ps.spanptr = ps.span_count - 1;
	// Look at this cellptr
	if( iStream.globalPos == ps.sp[ps.spanptr].start){
		// we're on it!
		return 1;
	}

	// Ok. Now check that the pointer is inside a span
	int its_inside = 0;
	for(i=0; i< ps.span_count; i++)
	{
		if( iStream.globalPos >= ps.sp[i].start &&
			iStream.globalPos <= ps.sp[i].end  ){
			// we're between it
				ps.spanptr = i;
				its_inside = 1;
				break;
		}
	}
	if(its_inside)
		//we're done
		return 1;

	//Align the stream to the next available span
	for(i=0; i< ps.span_count; i++)
	{
		if( iStream.globalPos <= ps.sp[i].start){
			// pick this one
			ps.spanptr = i;
			SetStreamPos(ps.sp[i].start);
			break;
		}
		if( i== ps.span_count-1 ){
			//globalPos is greater than the latest cell start
			ps.spanptr = i;
			SetStreamPos(ps.sp[i].start);
			break;
		}
	}

	return 1;
}

int CinputStream::AddSpan(TPlaySequenceSpan *span)
{
	if(!span)
		return 0;
	// check for correct span
	if(span->end <= span->start)
		return 0;
//	if(ps.span_count)
//		if(span->start <= ps.sp[ps.span_count - 1].end)
//			return 0;

	ps.sp = (TPlaySequenceSpan *) realloc( ps.sp, sizeof(TPlaySequenceSpan)*(ps.span_count+1) );
	ps.span_count++;
	ps.sp[ps.span_count - 1 ] = *span;
	// Update ps stuff
	ps.total_size += span->end - span->start + 1;
	return 1; 
}