#include "StateFile.hxx"
#include <iostream>

namespace StateFile {

  // private helper functions

  bool File::write8( const uint8 *in ) {
      return ( stream_->write( (char*)in, 1 ) > 0); 
  }

  bool File::write32( const uint32 *in ) { 
    uint32 out = htonl( *in );
    return ( stream_->write( (char*)&out, 4 ) > 0);
  }

  uint8 File::read8( void ) {
    return (uint8)stream_->get();
  }

  uint32 File::read32( void ) {
    uint32 in = 0, out = 0;
    stream_->read( (char*)&in, 4 );
    out = ntohl( in );
    return out;
  }

  Module* File::getModule( void ) {
    ByteData8 *bdInput = new ByteData8();

    // A
    bdInput->fileRead( stream_ );

    std::string name = bdInput->toString();

    if STATEFILE_DEBUG
      std::cout << "FILE::getModule Getting instance for module: " << name << std::endl;

    Module *thisModule = Module::getInstance( name, true );

    if STATEFILE_DEBUG
      std::cout << "FILE::getModule Setting the version for this module." << std::endl;

    thisModule->setVersion( read32() );

    // B
    if STATEFILE_DEBUG
      std::cout << "FILE::getModule Setting content profile for this module." << std::endl;
    bdInput->fileRead( stream_ );
    thisModule->setContentProfile( bdInput->toString() );
    
    // C
    if STATEFILE_DEBUG
      std::cout << "FILE::getModule Setting content names for this module." << std::endl;
    bdInput->fileRead( stream_ );
    thisModule->setContentNames( bdInput->toString() );
    
    // D
    if STATEFILE_DEBUG
      std::cout << "FILE::getModule Setting any extra data for this module." << std::endl;
    bdInput->fileRead( stream_ );
    thisModule->setExtra( bdInput );

    if STATEFILE_DEBUG
      std::cout << "FILE::getModule complete, returning this module." << std::endl;

    return thisModule;
  }

  // public members
  File::File( void ) {
    version_  = FileVersion; // FileFormat.hxx
    stream_ = NULL;
  }
  
  bool File::fileOpen( char* filename, bool write ) {

    if STATEFILE_DEBUG
    std::cout << "FILE::fileOpen running." << std::endl;

    assert( filename );
    filename_ = filename;

    if ( stream_ ) 
      fileClose();

    assert( !stream_ );
    
    if (write)
      return fileOpenWrite();
    return fileOpenRead();

  }

  bool File::fileOpenWrite() {

    if STATEFILE_DEBUG
      std::cout << "FILE::fileOpenWrite running." << std::endl;

    assert( filename_ != NULL );

    stream_ = new std::fstream();

    stream_->open( filename_, std::ios::binary | std::ios::out );

    assert( stream_->is_open() );

    if (! write32( &MagicNumber )) 
      return false;

    if (! write8(  &FileVersion ))
      return false;
    
    return true;
  }

  bool File::fileOpenRead() {

    if STATEFILE_DEBUG
      std::cout << "FILE::fileOpenRead running." << std::endl;

    assert( filename_ != NULL );

    stream_ = new std::fstream();
    stream_->open( filename_, std::ios::binary | std::ios::in );
    assert( stream_->is_open() );

    uint32 mNum = read32();
    if (mNum != MagicNumber) {
      assert( mNum == MagicNumber );
      return false;
    }
      
    uint8 fVers = read8();
    if (fVers != FileVersion) {
      assert( fVers == FileVersion);
      return false; 
    }

    return true;
  }
  
  void File::fileClose( void ) {

    if STATEFILE_DEBUG
      std::cout << "FILE::fileClose running." << std::endl;

    stream_->close();
    delete stream_;
    stream_ = NULL;
  }

  void File::registerModule( Module *m ) {
    outModules_.push_back( m );
  }
  
  bool File::fileRead_ModuleHeader( void ) {

    if STATEFILE_DEBUG
      std::cout << "FILE::fileRead_ModuleHeader running." << std::endl;

    // FIXME:
    //  This shouldn't be here, there should be a chunk dispatch.
    {
      uint8 chunkId = read8();
      assert( chunkId == ModuleIdentifier );
    }
    uint32 size = read32();

    std::streamoff endPos, nowPos;

    nowPos = (std::streamoff)stream_->tellg();
    endPos = nowPos + size;

    while( nowPos < endPos ) {
      
      Module *m = getModule();

      if STATEFILE_DEBUG
	std::cout << "FILE::fileRead_ModuleHeader: got a pointer to this newly formed module." << std::endl;

      assert( m != NULL );

      if STATEFILE_DEBUG
	std::cout << "FILE::fileRead_ModuleHeader: module appears valid. name = " << (m->getName()) << std::endl;

      inModules_.push_back( m );

      if STATEFILE_DEBUG
	std::cout << "FILE::fileRead_ModuleHeader: module pushed onto inModules." << std::endl;

      nowPos = (std::streamoff)stream_->tellg();
    }

    if STATEFILE_DEBUG
      std::cout << "FILE::fileRead_ModuleHeader: calling moduleReport()" << std::endl;

    moduleReport();
    
    if STATEFILE_DEBUG
      std::cout << "FILE::fileRead_ModuleHeader: rectify the inModules list to the outModules list" << std::endl;

    assert( outModules_.size() != 0 );

    if STATEFILE_DEBUG
      std::cout << "FILE::fileRead_ModuleHeader: outModules_.size() = " << outModules_.size() << std::endl;

    for ( mIter_ = outModules_.begin(); mIter_ != outModules_.end(); mIter_++ ) {
      if STATEFILE_DEBUG
	std::cout << "\nFILE::fileRead_ModuleHeader: setting *mOut to *mIter" << std::endl;
      Module *mOut = *mIter_;

      // Check to see is mOut is valid:
      if( mOut == NULL ) {
	if STATEFILE_DEBUG
	  std::cout << "Skipping this invalid mOut pointer..." << std::endl;
	return true;
      }
    }
    
    // FIXME:
    //  This especially shouldn't be here
    {
      uint8 chunkId = read8();
      assert( chunkId == BasicBodyIdentifier );
      uint32 lenId = read32();
      assert( lenId == VariableLength );
    }
    if STATEFILE_DEBUG
      std::cout << "FILE::fileRead_ModuleHeader: exiting this function.\n\n" << std::endl;
    return true;    
  }
  
  void File::moduleReport( void ) {
    if STATEFILE_DEBUG
      std::cout << "FILE::moduleReport: checking inModules_.size()." << std::endl;
    
    if( inModules_.size() <= 1 ) {
      if STATEFILE_DEBUG
	std::cout << "FILE::moduleReport: inModules_.size() = " << inModules_.size() << " returning." << std::endl;
      return;
    }
    
    std::cout <<  "Modules:  in ["  <<  inModules_.size() - 1  << "] (";
    
    for (mIter_=inModules_.begin(); mIter_ != inModules_.end(); mIter_++) 
      std::cout << ((mIter_!=inModules_.begin()) ? "," : "")
	
		<< ((Module*)*mIter_)->getName().c_str();
    

    std::cout << "), out [" << inModules_.size() << "] (";
    
    for (mIter_= outModules_.begin(); mIter_ != outModules_.end(); mIter_++) 
      std::cout << ((mIter_!=outModules_.begin()) ? "," : "")

		<< ((Module*)*mIter_)->getName().c_str();
    
    std::cout << ")" << std::endl;
  }

  uint32 File::fileRead_Content( void ) {

    uint32 id = read32();
    uint32 size = read32();

    std::streamoff endPos, nowPos;

    nowPos = (std::streamoff)stream_->tellg();
    endPos = nowPos + size;

    for (mIter_ = inModules_.begin(); mIter_ != inModules_.end(); mIter_++) 
      if (*mIter_ != NULL)
	((Module*)*mIter_)->fileRead( stream_ );

    nowPos = (std::streamoff)stream_->tellg();

    assert( nowPos == endPos );

    return id;
  }

  bool File::fileWrite_ModuleHeader( void ) {
    write8( &ModuleIdentifier );

    uint32 size = 0;

    for( mIter_=outModules_.begin(); mIter_ != outModules_.end(); mIter_++ )
      size += ((Module*)*mIter_)->byteLength_header();

    write32( &size );

    for( mIter_=outModules_.begin(); mIter_ != outModules_.end(); mIter_++ )
      ((Module*)*mIter_)->fileWrite_Header( stream_ );
    
    return true;
  }

  bool File::fileWrite_BodyStart( void ) {
    write8( &BasicBodyIdentifier );
    write32( &VariableLength );
    return true;
  }

  bool File::fileWrite_Content( uint32 id ) {
    
    write32( &id );

    uint32 size = 0;

    for (mIter_ = outModules_.begin(); mIter_ != outModules_.end(); mIter_++)
      size += ((Module*)*mIter_)->byteLength_content();

    write32( &size );

    for (mIter_ = outModules_.begin(); mIter_ != outModules_.end(); mIter_++) 
      ((Module*)*mIter_)->fileWrite_Content( stream_ );

    return true;
  }
}
