/*****************************************************************************
#   Copyright (C) 1994-2008 by David Gordon.
#   All rights reserved.                           
#                                                                           
#   This software is part of a beta-test version of the Consed/Autofinish
#   package.  It should not be redistributed or
#   used for any commercial purpose, including commercially funded
#   sequencing, without written permission from the author and the
#   University of Washington.
#   
#   This software is provided ``AS IS'' and any express or implied
#   warranties, including, but not limited to, the implied warranties of
#   merchantability and fitness for a particular purpose, are disclaimed.
#   In no event shall the authors or the University of Washington be
#   liable for any direct, indirect, incidental, special, exemplary, or
#   consequential damages (including, but not limited to, procurement of
#   substitute goods or services; loss of use, data, or profits; or
#   business interruption) however caused and on any theory of liability,
#   whether in contract, strict liability, or tort (including negligence
#   or otherwise) arising in any way out of the use of this software, even
#   if advised of the possibility of such damage.
#
#   Building Consed from source is error prone and not simple which is
#   why I provide executables.  Due to time limitations I cannot
#   provide any assistance in building Consed.  Even if you do not
#   modify the source, you may introduce errors due to using a
#   different version of the compiler, a different version of motif,
#   different versions of other libraries than I used, etc.  For this
#   reason, if you discover Consed bugs, I can only offer help with
#   those bugs if you first reproduce those bugs with an executable
#   provided by me--not an executable you have built.
# 
#   Modifying Consed is also difficult.  Although Consed is modular,
#   some modules are used by many other modules.  Thus making a change
#   in one place can have unforeseen effects on many other features.
#   It may takes months for you to notice these other side-effects
#   which may not seen connected at all.  It is not feasable for me to
#   provide help with modifying Consed sources because of the
#   potentially huge amount of time involved.
#
#*****************************************************************************/
#include    "rwcstring.h"
#include    "rwctokenizer.h"
#include    <fstream.h>
#include    "please_wait.h"
#include    "filename.h"
#include    "gotoList.h"
#include    "guiMultiContigNavigator.h"
#include    "mbt_exception.h"
#include    "filePopupAndGetFilename.h"
#include    "consed.h"
#include    "contig.h"
#include    "locatedFragment.h"
#include    "bIsNumeric.h"
#include    "customNavigation.h"
#include    "soLine.h"


static void parseSingleRegion( 
                        RWCString& soConsensusOrReadRegion, 
                        RWCString& soContig,
                        RWCString& soRead,
                        bool& bNext2AreUnpaddedReadPosInDirectionOfSequencing,
                        int& nUnpaddedConsPosStart, 
                        int& nUnpaddedConsPosEnd,
                        RWCString& soComment );


static FileName filCustomNavigation;
static int nCurLine = 0;
static FILE *pCustomNavigationFile;




void askUserForCustomNavigationFileAndPopupWindowAndParseFile() {


   filCustomNavigation = "";

   filCustomNavigation = filePopupAndGetFilename( "*.nav", 
                                          "", 
                                          "Select custom navigation file:" );

   // case in which user pushed 'cancel'
      
   if ( filCustomNavigation.isNull() ) return;

   gotoList* pGotoList = new gotoList();
   RWCString soTitle;
   PleaseWait* pPleaseWait;

   try {

      pPleaseWait = new PleaseWait( GuiApp::pGetGuiApp()->widGetTopLevel()  );
      readCustomNavigationFile( filCustomNavigation, soTitle, pGotoList );
      delete pPleaseWait;

   }
   catch(ExceptionBase eb) {
      // this is one of ours, report it if not done so
      if (! eb.bUserNotified() ) {
         GuiApp::popupErrorMessage(eb.szGetDesc());
      }
      delete pPleaseWait;
      delete pGotoList;
      fclose( pCustomNavigationFile );
      return;
   }


   guiMultiContigNavigator* pGui = 
      new guiMultiContigNavigator( soTitle, soTitle );

   ConsEd::pGetConsEd()->addGuiMultiContigNavigator( pGui );

   for( int n = 0; n < pGotoList->nGetNumGotoItems(); ++n ) {
      gotoItem* pGotoItem = pGotoList->pGetGotoItem( n );
      pGui->appendToMultiContigNavigator( pGotoItem );
   }
}


#define PARSE_ERROR( szMessage ) \
{  ostrstream ost; \
   ost << "Error detected from source file " \
     << __FILE__ << " at " << __LINE__ <<endl; \
   ost << "Error in file " << filCustomNavigation << " at line " << \
     nCurLine << ".  \nLine: " \
      << soLine << "\n" << szMessage << endl \
        << ends; \
   SysRequestFailed srf(ost.str()); \
   srf.includeErrnoDescription(); \
   throw srf; }

#define THROW_ERROR_CN(  ) \
{  SysRequestFailed srf; \
   srf.setUserNotified(); \
   throw srf; }





static bool bGetNextLine() {

   if ( fgets( soLine.sz_, nMaxLineSize, pCustomNavigationFile ) == 0 ) {
      soLine = "";
      return false;
   }
    
   soLine.nCurrentLength_ = strlen( soLine.sz_ );

   soLine.stripTrailingWhitespaceFast();
   

   // bump line counter - actually read one
   nCurLine++;
   return true;
}



void readCustomNavigationFile( const FileName& filCustomNavigation,
                               RWCString& soTitle,
                               gotoList* pGotoList ) {


   RWTPtrOrderedVector<Contig> 
      aContigsAlreadySetUnpaddedPositionsArray( 
                          ConsEd::pGetAssembly()->nNumContigs(), 
                          "aContigsAlreadySetUnpaddedPositionsArray" );

   pCustomNavigationFile = 0;
   pCustomNavigationFile = fopen( filCustomNavigation.data(), "r" );

   // file open?
   if (!pCustomNavigationFile ) { 
      ostrstream ost;
      ost << "Unable to open file " << filCustomNavigation << endl << ends;
      SysRequestFailed srf(ost.str());
      srf.includeErrnoDescription();
      throw srf;
   }

   // count items in order to resize pGotoList

   int nNumberOfRegions = 0;
   while( bGetNextLine() ) {
      if ( soLine == "BEGIN_REGION" ) 
         ++nNumberOfRegions;
   }

   rewind( pCustomNavigationFile );
   nCurLine = 0;


   pGotoList->resizeGotoList( nNumberOfRegions );



   // look for TITLE:

   if (! bGetNextLine() )
      PARSE_ERROR( "Premature end of file" );

   RWCTokenizer tokLine( soLine );

   RWCString soTitleLabel = tokLine();

   if ( soTitleLabel != "TITLE:" )
      PARSE_ERROR( "Are you sure this is a navigation file?  The first line in a navigation file must start with TITLE:" );

   soTitle = "";
   if ( soLine.length() >= 8 ) {
      soTitle = soLine( 7, soLine.length() - 7 );
   }

   bool bUserDefinedPositions = 
      soTitle.bContains( "user-defined positions", cCaseInsensitive );
   cerr << "using user-defined positions: " << szPrintBool( bUserDefinedPositions ) << endl;

// now looking for:

// BEGIN_REGION
// TYPE: CONSENSUS
// CONTIG: Contig1
// UNPADDED_CONS_POS: 1 5
// COMMENT: This is a comment
// END_REGION

   // the only acceptable options are whitespace, BEGIN_REGION, or EOF

   bool bFoundARegion = false;

   while( true ) {

      bool bWhitespace = true;
      while( bWhitespace ) {
         if ( !bGetNextLine() ) {
            if (!bFoundARegion ) 
               GuiApp::popupErrorMessage( "There were no regions in the custom navigation file." );
            fclose( pCustomNavigationFile );
            return;
         }
         else if (! soLine.bIsWhitespace() ) {
            bWhitespace = false;
         }
         // otherwise it is whitespace so get another line
      }
   
      if (soLine != "BEGIN_REGION" )
            PARSE_ERROR( "Are you sure this is navigation file?  At this point there should be BEGIN_REGION" );

      RWCString soConsensusOrReadRegion;
      RWCString soContig;
      RWCString soRead;
      bool bNext2AreUnpaddedReadPos;
      int nUnpaddedConsPosStart;
      int nUnpaddedConsPosEnd;
      RWCString soComment;


   
      parseSingleRegion( soConsensusOrReadRegion, 
                         soContig,
                         soRead,
                         bNext2AreUnpaddedReadPos,
                         nUnpaddedConsPosStart, 
                         nUnpaddedConsPosEnd,
                         soComment );

      bFoundARegion = true;

      Contig* pContig = 
         ConsEd::pGetAssembly()->pGetContigByName( soContig );
      if (!pContig && soConsensusOrReadRegion == "CONSENSUS" ) {
         
         char szError[200];
         sprintf( szError, "Could not find contig %s", 
                 (char*) soContig.data() );
         PARSE_ERROR( szError );
      }

      LocatedFragment* pLocFrag = 0;
      if ( soConsensusOrReadRegion == "READ" ) {
         pLocFrag = 
            ConsEd::pGetAssembly()->pGetLocatedFragmentByName( soRead );
         if ( !pLocFrag ) {
            RWCString soError = "Could not find read ";
            soError += soRead;
            soError += " in contig ";
            soError += soContig;
            PARSE_ERROR( soError );
         }
         pContig = pLocFrag->pGetContig();
      }


      int nUnpaddedStartContigConsPos;
      int nUnpaddedEndContigConsPos;


      // don't call setUnpaddedPositionsArray every time (DG, June 2009)
      if ( !aContigsAlreadySetUnpaddedPositionsArray.bContains( pContig ) ) {

         // needed by nUnpaddedIndex
         pContig->setUnpaddedPositionsArray();
         aContigsAlreadySetUnpaddedPositionsArray.insert( pContig );
      }


      if ( bNext2AreUnpaddedReadPos ) {
         int nUnpadded1 = 
            pLocFrag->nUnpaddedConsPosFromOrientedUnpaddedFragPos( nUnpaddedConsPosStart );
         int nUnpadded2 =
            pLocFrag->nUnpaddedConsPosFromOrientedUnpaddedFragPos( nUnpaddedConsPosEnd );

         nUnpaddedConsPosStart = MIN( nUnpadded1, nUnpadded2 );
         nUnpaddedConsPosEnd   = MAX( nUnpadded1, nUnpadded2 );
      }


      pContig->setStartNumberingUnpaddedConsensusAtUserDefinedIfNecessary();

      if ( bUserDefinedPositions ) {

         if ( soConsensusOrReadRegion == "READ" ) {
            nUnpaddedStartContigConsPos = 
               pContig->nUnpaddedIndexStartingAtUserDefinedPosition2(
                           pContig->nGetFirstDisplayableContigPos()
                                                                 );
            nUnpaddedEndContigConsPos =
               pContig->nUnpaddedIndexStartingAtUserDefinedPosition2(
                           pContig->nGetLastDisplayableContigPos()
                                                                 );
         }
         else {
            nUnpaddedStartContigConsPos =
               pContig->nUnpaddedIndexStartingAtUserDefinedPosition2(
                           pContig->nGetStartConsensusIndex()
                                                                    );
            nUnpaddedEndContigConsPos =
               pContig->nUnpaddedIndexStartingAtUserDefinedPosition2(
                           pContig->nGetEndConsensusIndex()
                           );
         }
      }
      else {

         if ( soConsensusOrReadRegion == "READ" ) {
            nUnpaddedStartContigConsPos = pContig->nUnpaddedIndex(
                          pContig->nGetFirstDisplayableContigPos() 
                                                                   );
                                                              

            nUnpaddedEndContigConsPos = pContig->nUnpaddedIndex(
                          pContig->nGetLastDisplayableContigPos()
                                                                 );


         }
         else {
            nUnpaddedStartContigConsPos = 
               pContig->nUnpaddedIndex(
                          pContig->nGetStartConsensusIndex()
                                                                   );

            nUnpaddedEndContigConsPos = 
               pContig->nUnpaddedIndex(
                          pContig->nGetEndConsensusIndex()
                                                                   );
         }
      }

      if (! (
                (nUnpaddedStartContigConsPos <= nUnpaddedConsPosStart) &&
                (nUnpaddedConsPosStart <= nUnpaddedConsPosEnd ) &&
                (nUnpaddedConsPosEnd <= nUnpaddedEndContigConsPos ) 
                )) {
         RWCString soErrorMessage( (size_t) 1000 );
         soErrorMessage.nCurrentLength_ = 
            sprintf( 
             soErrorMessage.data(),
             "Range of region (%d to %d) must be in order and within the range of the contig (%d to %d) %s",
             nUnpaddedConsPosStart,
             nUnpaddedConsPosEnd,
             nUnpaddedStartContigConsPos,
             nUnpaddedEndContigConsPos,
             pContig->soGetName().data() );

         if ( bUserDefinedPositions ) {
            soErrorMessage += " trying to use user-defined positions from tag ";
         }


         soErrorMessage += " and contig starts at ";

         if ( pContig->nStartNumberingUnpaddedConsensusAtUserDefined_ != 1 ) {
            soErrorMessage += "(user-defined) ";
         }

         soErrorMessage += RWCString( (long) pContig->nStartNumberingUnpaddedConsensusAtUserDefined_ );

         
         if ( !bUserDefinedPositions &&
              pContig->nStartNumberingUnpaddedConsensusAtUserDefined_ != 1 ) {
            soErrorMessage += ".  Put \"(user-defined positions)\" in the title of the .nav file if you want to use user-defined consensus positions rather than 1-based consensus positions.";
         }
              


         
         THROW_ERROR( soErrorMessage );
      }


      // now convert nUnpaddedConsPosStart and nUnpaddedConsPosEnd to 
      // 1-based positions

      if ( bUserDefinedPositions ) {

         if ( !pContig->bSetStartNumberingUnpaddedConsensusAtUserDefined_ ) 
            pContig->setStartNumberingUnpaddedConsensusAtUserDefinedIfNecessary();

         nUnpaddedConsPosStart -= 
            ( pContig->nStartNumberingUnpaddedConsensusAtUserDefined_ - 1 );
         nUnpaddedConsPosEnd -=
            ( pContig->nStartNumberingUnpaddedConsensusAtUserDefined_ - 1 );
      }

      // changed from nPaddedIndex to nPaddedIndexFast for performance
      // (DG, June 2009)

      int nPaddedConsPosStart = pContig->nPaddedIndexFast( nUnpaddedConsPosStart );
      int nPaddedConsPosEnd = pContig->nPaddedIndexFast( nUnpaddedConsPosEnd );

      if ( soConsensusOrReadRegion == "READ" ) {

         if ( !(
                ( pLocFrag->nGetAlignStart() <= nPaddedConsPosStart ) &&
                ( nPaddedConsPosStart <= nPaddedConsPosEnd ) &&
                ( nPaddedConsPosEnd <= pLocFrag->nGetAlignEnd() )
                )) {

            GuiApp::popupErrorMessage(
                    "Range of the region (%d to %d in padded coordinates) of read %s must be in order and lie within the range of the read (%d to %d)",
                    nPaddedConsPosStart,
                    nPaddedConsPosEnd,
                    pLocFrag->soGetName().data(),
                    pLocFrag->nGetAlignStart(),
                    pLocFrag->nGetAlignEnd() );
            THROW_ERROR_CN();
         }
      }



      gotoItem* pGotoItem = new gotoItem( pContig,
                                      pLocFrag, 
                                      nPaddedConsPosStart,
                                      nPaddedConsPosEnd,
                                      nUnpaddedConsPosStart,
                                      nUnpaddedConsPosEnd,
                                      soComment );

      pGotoList->addToList( pGotoItem);
   } //    while( true ) {



   fclose( pCustomNavigationFile );
}





void parseSingleRegion( RWCString& soConsensusOrReadRegion, 
                        RWCString& soContig,
                        RWCString& soRead,
                        bool& bNext2AreUnpaddedReadPosInDirectionOfSequencing,
                        int& nUnpaddedConsPosStart, 
                        int& nUnpaddedConsPosEnd,
                        RWCString& soComment ) {


// BEGIN_REGION
// TYPE: CONSENSUS
// CONTIG: Contig1
// UNPADDED_CONS_POS: 1 5
// COMMENT: This is a comment
// END_REGION

   // now looking for TYPE:
   if ( !bGetNextLine() )
      PARSE_ERROR( "premature end of file while looking for TYPE:" );

   RWCTokenizer tokType( soLine );

   RWCString soTypeLineToken = tokType();

   if ( soTypeLineToken != "TYPE:" )
      PARSE_ERROR( "Expected this line to start with TYPE:" );
   
   soConsensusOrReadRegion = tokType();
   soConsensusOrReadRegion.toUpper();

   if (soConsensusOrReadRegion != "CONSENSUS" &&
       soConsensusOrReadRegion != "READ" )
      PARSE_ERROR( "Expected this line to be TYPE: CONSENSUS or TYPE: READ" );

   if ( !bGetNextLine() )
      PARSE_ERROR( "premature end of file while looking for CONTIG:" );

   RWCTokenizer tokContig( soLine );

   RWCString soContigLabel = tokContig();

   bool bPutBackLineForReadType = false;
   if ( soContigLabel != "CONTIG:" ) {
      if ( soConsensusOrReadRegion == "CONSENSUS" ) {
         PARSE_ERROR( "Expected this line to start with CONTIG:" );
      }
      else {
         soContig = "(not specified)";
         bPutBackLineForReadType = true;
      }
   }
   else {
      soContig = tokContig();
   }

   if ( soConsensusOrReadRegion == "READ" ) {
      
      if ( !bPutBackLineForReadType ) {
         if (!bGetNextLine() )
            PARSE_ERROR( "premature end of file while looking for READ:" );
      }

      RWCTokenizer tokRead( soLine );
      
      RWCString soReadLabel = tokRead();
      if (soReadLabel != "READ:" )
         PARSE_ERROR( "Expected this line to start with READ:" );

      soRead = tokRead();
   }

   if ( !bGetNextLine() )
      PARSE_ERROR( "premature end of file while looking for UNPADDED_CONS_POS:" );

   RWCTokenizer tokUnpaddedConsPos( soLine );
   RWCString soUnpaddedConsPosLabel = tokUnpaddedConsPos();
   if (soUnpaddedConsPosLabel == "UNPADDED_CONS_POS:" ) {
      bNext2AreUnpaddedReadPosInDirectionOfSequencing = false;
   }
   else if (soUnpaddedConsPosLabel == "UNPADDED_READ_POS:" ) {
      bNext2AreUnpaddedReadPosInDirectionOfSequencing = true;
   }
   else {
      PARSE_ERROR( "Expected this line to start with UNPADDED_CONS_POS: or UNPADDED_READ_POS:" );
   }


   if ( soConsensusOrReadRegion == "CONSENSUS" &&
        bNext2AreUnpaddedReadPosInDirectionOfSequencing ) {
      PARSE_ERROR( "TYPE: CONSENSUS cannot be used with UNPADDED_READ_POS: only with UNPADDED_CONS_POS:" );
   }


   RWCString soUnpaddedConsPosStart = tokUnpaddedConsPos();
   RWCString soUnpaddedConsPosEnd = tokUnpaddedConsPos();

   if (soUnpaddedConsPosStart.isNull() || soUnpaddedConsPosEnd.isNull() )
      PARSE_ERROR( "UNPADDED_CONS_POS: must be followed by the start and end of the region" );

   if ( !bIsNumeric( soUnpaddedConsPosStart ) || !bIsNumeric( soUnpaddedConsPosEnd ) )
        PARSE_ERROR( "UNPADDED_CONS_POS: must be followed by two numbers--the start and end of the region" );

   nUnpaddedConsPosStart = atoi( (char*) soUnpaddedConsPosStart.data() );
   nUnpaddedConsPosEnd = atoi( (char*) soUnpaddedConsPosEnd.data() );

   if ( !bGetNextLine() )
      PARSE_ERROR( "premature end of file while looking for COMMENT:" );

   RWCTokenizer tokComment( soLine );
   RWCString soCommentLabel = tokComment();
   if (soCommentLabel != "COMMENT:" )
      PARSE_ERROR( "Expected this line to start with COMMENT:" );

   if ( soLine.length() < 10 )
      soComment = "";
   else {
      soComment = soLine( 9, soLine.length() - 9 );
      soComment = soComment.strip( RWCString::LEADING );
   }

   // now read lines until see END_REGION

   bool bFoundEndRegion = false;
   while( !bFoundEndRegion ) {
      if (!bGetNextLine() )
         PARSE_ERROR( "premature end of file while looking for END_REGION" );

      RWCTokenizer tokEndRegion( soLine );
      RWCString soEndRegionLabel = tokEndRegion();
      if ( soEndRegionLabel == "END_REGION" )
         bFoundEndRegion = true;
   }

}