// CnfDoc.cpp : implementation of the CnfDoc class
//

#include  "stdafx.h"

#include  <algorithm>

#include  "modifier.h"
#include  "WgsCnf.h"

#include  "CnfDoc.h"
#include  "CantExit.h"
#include  "ExitDlg.h"
#include  "OptEdit.h"
#include  "MainFrm.h"
#include  "OptTree.h"
#include  "SrchDlg.h"
#include  "CnfAsrt.h"

#include  "gcommlib.h"
#include  "levels.h"
#include  "option.h"
#include  "cnf.h"
#include  "cstrutil.h"
#include  "lingo.h"
#include  "msgrdr.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define FILREV "$Revision: $"

class TdHolder : public CObject
{
public:
     TdHolder() {}
     virtual ~TdHolder() {}

     TdHolder( const TreeData &td )
     {
          treeData = td;
     }

     TreeData treeData;
};

/////////////////////////////////////////////////////////////////////////////
// CnfDoc

IMPLEMENT_DYNCREATE(CnfDoc, CDocument)

BEGIN_MESSAGE_MAP(CnfDoc, CDocument)
     //{{AFX_MSG_MAP(CnfDoc)
     ON_COMMAND(ID_FILE_SAVE, OnFileSave)
     ON_COMMAND(ID_FILE_SAVE_AS, OnFileSaveAs)
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

BOOL CnfDoc::m_searching = FALSE;

/////////////////////////////////////////////////////////////////////////////
// CnfDoc construction/destruction

CnfDoc::CnfDoc()
{
     m_entries = 0;
     SetFullyLoaded( FALSE );

     m_searchingForward = TRUE;
     m_searching = FALSE;
     m_searchInValue = TRUE;
     m_searchInNameAndDesc = TRUE;
     m_searchInHelpText = TRUE;

     m_modFileCount = 0;
}

CnfDoc::~CnfDoc()
{
     CString        theKey;
     CObject    *theData;

     POSITION  toDel = map.GetStartPosition();

     while (toDel != NULL)
     {
          map.GetNextAssoc( toDel, theKey, theData );

          TdHolder *realData = static_cast< TdHolder * >( theData );

          delete realData;
          theData = NULL;
     }

     closeMsgFiles();
}

BOOL CnfDoc::OnNewDocument()
{
     if (!CDocument::OnNewDocument())
          return FALSE;

     CString   caption = "" SVR_NAME " " + GetLevelDesc( NULL, level );

     SetPathName( caption, FALSE );
     SetTitle( caption );

     CDocument::UpdateAllViews( NULL, NewDocument );

     return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CnfDoc diagnostics

#ifdef _DEBUG
void CnfDoc::AssertValid() const
{
     CDocument::AssertValid();
}

void CnfDoc::Dump(CDumpContext& dc) const
{
     CDocument::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CnfDoc commands

void
CnfDoc::AddOption(              // add an option or level to the tree
LPCTSTR filename,                  //   filename whence it came
struct option *ldrop,              //   the option data
const CString &helpText,           //   this item's help text
long bytesSoFar,                   //   bytes read from all files so far
long totalBytes,                   //   bytes to be read from all files
int itemLevel,                     //   this item's level
FILE *fp,                          //   this item's file pointer
const CString *levelHelpTextPtr)   //   current levels help text
{
     static CString oldFn;         // previous file name
     static int oldLevel = 0;      // previous level
     static CString parentKey;     // current parent level or filename
     static int opCount = 0;       // options seen so far
     static CString levelStr;      // current level key

     TreeData ourData;             // used for inserting into option map
     Modifier theMod;              // used to pass update info around

     if ((level <= 0) && (itemLevel != oldLevel))
     {
          levelStr = GetLevelDesc( NULL, itemLevel );

          if (! Lookup( levelStr, ourData ))
          {
               ourData.header = levelStr;

               ourData.parentKey = "";
               ourData.treePtr = NULL;
               ourData.itemLevel = itemLevel;
               ourData.modCount = 0;
               ourData.lineDesc = "";
               ourData.helpText = "";
               ourData.opPtr = NULL;
               ourData.filename = filename;

               Set( levelStr, ourData );

               theMod.treeData = &ourData;
               theMod.itemKey = levelStr;
               theMod.sofar = bytesSoFar;
               theMod.totalBytes= totalBytes;
               theMod.itemCount = opCount;

               UpdateAllViews( NULL, ItemAdded, &theMod );
          }

          parentKey = levelStr + ":" + oldFn;
          oldLevel = itemLevel;

          oldFn = "";
     }

      //       don't re-add files that are already here (for instance,
      //       if we're getting called due to a hinge condition being
      //       newly satisfied)
      //
     if (oldFn.CompareNoCase( filename ) != 0)
     {
          oldFn = filename;
          oldFn.MakeUpper();

          CString   theKey;
          
          if (level > 0)
               theKey = oldFn;
          else
               theKey = levelStr + ":" + oldFn;

          if (! Lookup( theKey, ourData ))
          {
               ourData.header = GetLevelDesc( oldFn, itemLevel );

               ourData.parentKey = (level > 0) ? "" : levelStr;
               ourData.treePtr = NULL;
               ourData.itemLevel = itemLevel;
               ourData.modCount = 0;
               ourData.lineDesc = "";

               if (levelHelpTextPtr != NULL)
               {
                    ourData.helpText = *levelHelpTextPtr;
               }
               else
               {
                    ourData.helpText = "";
               }

               ourData.opPtr = NULL;
               ourData.filename = oldFn;

               Set( theKey, ourData );

               theMod.treeData = &ourData;
               theMod.itemKey = theKey;
               theMod.sofar = bytesSoFar;
               theMod.totalBytes= totalBytes;
               theMod.itemCount = opCount;

               UpdateAllViews( NULL, ItemAdded, &theMod );
          }

          parentKey = theKey;
     }

     ++opCount;

     BOOL          visible = TRUE;
     CString theKey = parentKey + ":" + ldrop->name;

     CString   tParentKey( parentKey );

     if (ldrop->flags & ISHING)
     {
          option  *hingOp = ldrop->hngptr;

          CnfAssert( hingOp != NULL );

          char     hop = ldrop->hngop;
           
          CnfAssert( (hop == HNGEQ) || (hop == HNGNE) || (hop == HNGEXC) );

          visible = chkvis( ldrop );

          tParentKey = parentKey + ":" + hingOp->name;

          AddHingee( tParentKey, theKey );
     }

     if ((ldrop->type == 'T') && (fp != NULL))
     {
          //   load text blocks
          //
          CString   temp;

          ourData.langBlocks.resize( nlingo, temp );

          altlng   *langs = ldrop->values;

          fpos_t    oldPos = ftell( fp );

          for ( int count = 0; count < salingo; ++count )
          {
               if (langs[ count ].value.ptr != NULL)
               {
                    loadtv( fp, &langs[ count ] );

                    int       univ = mlingo( ldrop->file, count );
                    ourData.langBlocks[ univ ] = txtbuf;
                    ldrop->origValues[ univ ] = ourData.langBlocks[ univ ];
               }
          }

          fseek( fp, oldPos, SEEK_SET );
     }

     if ((ldrop->type != 'V') && Lookup( theKey, ourData ) && (fp != NULL))
     {
          CString   dispStr;
          dispStr.Format( "The option %s was seen more than once\r\n"
                          "in the file %s.\r\n\r\n"
                          "Please make sure %s is a valid MSG file.",
                          ldrop->name, filename, filename
                        );  

          ::MessageBox( NULL, dispStr, "Duplicate Option Encountered", 
                        MB_OK | MB_ICONSTOP | MB_APPLMODAL
                      );

          theApp.ForceExit();
     }

     ourData.header = ldrop->name;
     ourData.header += ":  ";
     ourData.header += ldrop->lineDesc;

     if (ourData.header.Right( 1 ) == ":")
          ourData.header = ourData.header.Left( ourData.header.GetLength() - 1 );

     ourData.parentKey = tParentKey;
     ourData.treePtr = NULL;
     ourData.itemLevel = itemLevel;
     ourData.modCount = 0;
     ourData.lineDesc = ldrop->lineDesc;
     ourData.helpText = helpText;
     ourData.opPtr = ldrop;
     ourData.filename = oldFn;

     if (fp != NULL)
     {
          ourData.opPtr->references++;
     }

     theMod.treeData = &ourData;
     theMod.itemKey = theKey;
     theMod.sofar = bytesSoFar;
     theMod.totalBytes= totalBytes;
     theMod.itemCount = opCount;
     theMod.visible = visible;

     Set( theKey, ourData );

     //   if it's invisible or a level separator, don't add it to the tree
     //   
     if (visible && (ldrop->type != 'V'))
          UpdateAllViews( NULL, ItemAdded, &theMod );

     return;
}


BOOL                               //   TRUE if we found it
CnfDoc::Lookup(                    // lookup an option by key
const CString &key,                //   key to lookup
TreeData &treeData) const          //   option data (returned)
{
     CObject *lookVal;

     BOOL result = map.Lookup( key, lookVal );

     TdHolder *holder = static_cast< TdHolder * >( lookVal );

     if (result)
          treeData = holder->treeData;

     return( result );
}


BOOL                               //   TRUE if set successfully
CnfDoc::Set(                       // set an option's data
const CString &key,                //   opt key
const TreeData &treeData)          //   new tree data
{
     TdHolder *oldVal = NULL;

     map.Lookup( key, (CObject *&)oldVal );

     map.SetAt( key, new TdHolder( treeData ) );

     if (oldVal != NULL)
          delete oldVal;

     return( TRUE );
}



void
CnfDoc::UpdateAllViews(            // update all views (MFC override)
CView* pSender, 
LPARAM lHint, 
CObject* pHint)
{
     Modifier *theMod = static_cast< Modifier * >( pHint );

     switch( lHint )
     {
          case SelectionChanged:
          case VisibleChange:
               if (theMod->oldKey.GetLength() > 0)
               {
                    //   grab changed option data
                    //
                    TreeData treeData;
                    BOOL      lookRes = Lookup( theMod->oldKey, treeData );
                    CnfAssert( lookRes );

                    option *changedOp = treeData.opPtr;

                    if (changedOp != NULL)
                    {
                         int       hingeCount = treeData.hingees.size();

                         //   for each hingee
                         for ( int count = 0; count < hingeCount; ++count )
                         {
                              //   see if hingee is visible now

                              CString   hingeeKey = treeData.hingees[ count ];

                              TreeData ourData;

                              BOOL      lookRes = Lookup( hingeeKey, ourData );
                              CnfAssert( lookRes );

                              BOOL      wasVisible = (ourData.treePtr != NULL);
                              BOOL      isVisible = chkvis( ourData.opPtr );

                              //   if (was visible and now isn't)
                              if (wasVisible && ! isVisible)
                              {
                                   //   call UpdateAllViews with new hint
                                   //
                                   Modifier newMod;

                                   newMod.treeData = &ourData;
                                   newMod.oldKey = hingeeKey;

                                   UpdateAllViews( NULL, VisibleChange, &newMod );

                                   ourData.treePtr = NULL;
                                   Set( hingeeKey, ourData );
                              }
                              //   if (wasn't and now is)
                              else if ((! wasVisible) && isVisible)
                              {
                                  //    add to the tree
                                  //
                                  AddOption( ourData.filename,
                                             ourData.opPtr,
                                             ourData.helpText,
                                             0, 0,
                                             ourData.itemLevel
                                           );

                                   //   call UpdateAllViews with new hint
                                   //
                                   Modifier newMod;

                                   newMod.treeData = &ourData;
                                   newMod.oldKey = hingeeKey;

                                   UpdateAllViews( NULL, VisibleChange, &newMod );
                              }
                         }
                    }
               }
               break;

          case ItemModified:
               {
                    SetModifiedFlag( TRUE );

                    TreeData ourData;

                    if (Lookup( theMod->itemKey, ourData ))
                    {
                         option *op = ourData.opPtr;

                         if ((op != NULL) && (! (op->flags & ANYCHG)))
                         {
                              op->flags|=ANYCHG;
                              ++op->file->modCount;

                              if (! (op->file->flags & ANYCHG))
                              {
                                   op->file->flags|=ANYCHG;
                                   ++m_modFileCount;
                              }
                         }
                    }
               }
               break;

          case ItemUnModified:
               {
                    TreeData ourData;

                    if (Lookup( theMod->itemKey, ourData ))
                    {
                         option *op = ourData.opPtr;

                         if ((op != NULL) && (op->flags & ANYCHG))
                         {
                              op->flags&=~ANYCHG;

                              CnfAssert( op->file->modCount > 0 );

                              --op->file->modCount;

                              if (op->file->modCount <= 0)
                              {
                                   op->file->flags&=~ANYCHG;

                                   CnfAssert( m_modFileCount > 0 );
                                   --m_modFileCount;

                                   if (m_modFileCount <= 0)
                                   {
                                        SetModifiedFlag( FALSE );
                                   }
                              }
                         }
                    }
               }
               break;
     }

     CDocument::UpdateAllViews( pSender, lHint, pHint );
}

BOOL CnfDoc::SaveModified() 
{
     BOOL      result = TRUE;
     static BOOL justExit = FALSE;

     if (justExit)
     {
          result = TRUE;
     }
     else if (! CheckValid())
     {
          result = FALSE;
     }
     else if (IsModified() && ! FullyLoaded())
     {
          CantExit dlg;

          dlg.DoModal();

          result = FALSE;
     }
     else if (IsModified())
     {
          result = FALSE;

          ExitDialog exDlg;

          int       dres = exDlg.DoModal();

          if (dres == IDOK)
          {
               //   save the data
               OnFileSave();
               SetModifiedFlag( FALSE );
               result = TRUE;
          }
          else if (dres == IDC_EXITWITHOUTSAVE)
          {
               //   don't save the data
               SetModifiedFlag( FALSE );
               justExit = TRUE;
               result = TRUE;
          }
     }

     return( result );
}

BOOL CnfDoc::OnSaveDocument(LPCTSTR lpszPathName) 
{
     return CDocument::OnSaveDocument(lpszPathName);
}

void CnfDoc::OnFileSave() 
{
     if (! CheckValid())
     {
          return;
     }

     CnfAssert( theMainFrame != NULL );
     theMainFrame->SetMessageText( "Saving changes..." );

     CWaitCursor waitCursor;

     if (outmsg())
     {
          SetModifiedFlag( FALSE );
          m_modFileCount = 0;
          theApp.NoteChanges();
     }

     CString     theKey;
     CObject    *theData;

     POSITION  toDel = map.GetStartPosition();

     Modifier  theMod;

     while (toDel != NULL)
     {
          map.GetNextAssoc( toDel, theKey, theData );

          TdHolder *holder = static_cast< TdHolder * >( theData );
          TreeData realData = holder->treeData;

          if (realData.Modified())
          {
               if (realData.opPtr != NULL)
               {
                    if (! (realData.opPtr->flags & ANYCHG))
                    {
                         theMod.itemKey = theKey;
                         UpdateAllViews( NULL, ItemUnModified, &theMod );

                         option    &op = *realData.opPtr;

                         if (op.origValues == NULL)
                         {
                              op.origValue = op.curValue;
                         }
                         else
                         {
                              for ( int count = 0; count < nlingo; ++count )
                              {
                                   op.origValues[ count ] = op.values[ count ].value.ptr;
                              }
                         }
                    }
               }
          }
     }

     UpdateAllViews( NULL, FileSaved, NULL );
}


void CnfDoc::OnFileSaveAs() 
{
     // TODO: Add your command handler code here
     OnFileSave();
}


void 
CnfDoc::SetFullyLoaded(            // notify that we are fully loaded
BOOL loaded)
{
     m_fullyLoaded = loaded;
}


BOOL                               //   TRUE if loading is complete
CnfDoc::FullyLoaded() const        // check where loading is finished
{
     return( m_fullyLoaded );
}


BOOL                               //   TRUE if item is valid
CnfDoc::ValidateCurrentItem()      // check current opt value's validity
{
     BOOL      valid = TRUE;

     POSITION  nextPos = GetFirstViewPosition();
     OptionEditView    *view = NULL;

     while (nextPos != NULL)
     {
          CView *cview = GetNextView( nextPos );

          if ((cview != NULL) && cview->IsKindOf( RUNTIME_CLASS( OptionEditView ) ))
          {
               view = static_cast< OptionEditView * >( cview );
               break;
          }
     }

     if (view != NULL)
     {
          valid = view->ValidateCurrentItem();
     }

     return( valid );
}


void 
CnfDoc::StartSearch()              // begin a new search
{
     m_searching = FALSE;

     SearchDialog dlg;
     dlg.m_radioSelected = m_searchingForward ? 0 : 1;
     dlg.m_searchText = m_searchText;
     dlg.m_value = m_searchInValue;
     dlg.m_nameAndDesc = m_searchInNameAndDesc;
     dlg.m_helpText = m_searchInHelpText;

     int       dresult = dlg.DoModal();

     if (dresult == IDOK)
     {
          m_searchText = dlg.m_searchText;

          if (m_searchText.GetLength() > 0)
          {
               m_searching = TRUE;
               m_searchingForward = (dlg.m_radioSelected == 0);
               m_searchCount = 0;
               m_searchInValue = dlg.m_value;
               m_searchInNameAndDesc = dlg.m_nameAndDesc;
               m_searchInHelpText = dlg.m_helpText;

               //   DEBUG
               BmgInit( m_searchText );

               SearchNext();
          }
     }
}


void
CnfDoc::SearchNext()               // look for the next matching entry
{
     if (m_searching)
     {
          BOOL      found = FALSE;
          BOOL      done = FALSE;

          OptionTree *view = GetTreeView();

          if (view != NULL)
          {
               CWaitCursor wc;
     
               HTREEITEM cur = view->GetSelectedItem();

               if (cur == NULL)
               {
                    cur = TVI_ROOT;
               }

               while ((! found) && (! done))
               {
                    HTREEITEM item = NULL;

                    if (m_searchingForward)
                    {
                         item = view->NextItem( cur );
                    }
                    else
                    {
                         item = view->PrevItem( cur );
                    }

                    if (item == NULL)
                    {
                         done = TRUE;
                         m_searching = FALSE;
                    }
                    else
                    {
                         cur = item;
                         found = TreeItemContains( cur, m_searchText );
                    }
               }

               if (found)
               {
                    view->SelectItem( cur );
                    ++m_searchCount;
               }
          }
          else
          {
               m_searching = FALSE;
          }

          if (! found)
          {
               CString   msgText;
               CString   titleText;

               if (m_searchCount > 0)
               {
                    msgText = "No more matches found.";
                    titleText = "Search ended.";
               }
               else
               {
                    msgText = "No matches found for \"" + m_searchText + "\".";
                    titleText = "Search failed";
               }

               if (! FullyLoaded())
               {
                    msgText += "\r\n\r\n"
                               "Not all options have been loaded yet.\r\n"
                               "You may wish to try your search again\r\n"
                               "once loading is complete.";
               }

               ::MessageBox( NULL, msgText, titleText, MB_ICONEXCLAMATION | MB_OK );
          }
     }
     else
     {
          ::MessageBeep( MB_ICONASTERISK );
     }
}


OptionTree *                       //   opt tree view or NULL
CnfDoc::GetTreeView() const        // get the option tree view
{
     OptionTree *result = NULL;

     POSITION pos = GetFirstViewPosition();

     while (pos != NULL)
     {
          CView     *view = GetNextView( pos );

          CnfAssert( view != NULL );

          if (view->IsKindOf( RUNTIME_CLASS( OptionTree ) ))
          {
               result = static_cast< OptionTree * >( view );
          }
     }

     return( result );
}


BOOL                               //   TRUE if the item is visible
CnfDoc::IsVisible(                 // check whether an option is visible
const CString &key) const          //   option's key
{
     BOOL      visible = FALSE;
     
     TreeData  ourData;

     if (Lookup( key, ourData ))
     {
          if (ourData.opPtr == NULL)
          {
               visible = TRUE;
          }
          else
          {
               visible = chkvis( ourData.opPtr );
          }
     }

     return( visible );
}


void
CnfDoc::FinishExternal(            // notify that external editor has finished
int rc)                            //   result code
{
     POSITION  nextPos = GetFirstViewPosition();
     OptionEditView    *view = NULL;

     while (nextPos != NULL)
     {
          CView *cview = GetNextView( nextPos );

          if ((cview != NULL) && cview->IsKindOf( RUNTIME_CLASS( OptionEditView ) ))
          {
               view = static_cast< OptionEditView * >( cview );
               break;
          }
     }

     if (view != NULL)
     {
          view->FinishExternal( rc );
     }
}


void CnfDoc::AddHingee( const CString &hinge, const CString &hingee )
{
     TreeData  hingeData;

     if (hinge == hingee)
          return;

     BOOL    lookRes = Lookup( hinge, hingeData );

     CnfAssert( lookRes );

     int       nHinges = hingeData.hingees.size();

     CString temp( hingee );

     //   add this hingee IFF we don't already have it listed
     //
     if (find( hingeData.hingees.begin(),
               hingeData.hingees.end(),
               temp
             ) == hingeData.hingees.end()
        )
     {
          hingeData.hingees.resize( nHinges + 1, temp );

          CnfAssert( hingeData.hingees.size() == (nHinges + 1) );

          Set( hinge, hingeData );
     }

     return;
}


BOOL CnfDoc::CheckValid()
{
     BOOL      result = ValidateCurrentItem();

     return( result );
}


BOOL CnfDoc::TreeItemContains( HTREEITEM item, const CString &text ) const
{
     OptionTree *view = GetTreeView();

     CnfAssert( view != NULL );

     TreeData ourData;

     BOOL      getRes = view->GetTreeData( item, ourData );

     CnfAssert( getRes );

     BOOL      found = FALSE;
     
     if (m_searchInNameAndDesc)
     {
          // DEBUG
          // found = (SearchNoCase( ourData.header, text ) >= 0);
          found = BmgSearch( ourData.header );
     }

     if ((! found) && m_searchInNameAndDesc)
     {
          // DEBUG
          // found = (SearchNoCase( ourData.lineDesc, text ) >= 0);
          found = BmgSearch( ourData.lineDesc );
     }

     if ((! found) && m_searchInHelpText)
     {
          // DEBUG
          // found = (SearchNoCase( ourData.helpText, text ) >= 0);
          found = BmgSearch( ourData.helpText );
     }
     
     if ((! found) && m_searchInValue)
     {
          if (ourData.opPtr != NULL)
          {
               if (ourData.opPtr->type == 'T')
               {
                    for ( int count = 0; count < ourData.langBlocks.size(); ++count )
                    {
                         //   DEBUG
                         //   if (SearchNoCase( ourData.langBlocks[ count ], text ) >= 0)
                         if (BmgSearch( ourData.langBlocks[ count ] ))
                         {
                              found = TRUE;
                              break;
                         }
                    }
               }
               else
               {
                    // DEBUG
                    // found = (SearchNoCase( ourData.opPtr->curValue, text ) >= 0);
                    found = BmgSearch( ourData.opPtr->curValue );
               }
          }
     }

     return( found );
}

