/********************************************************************
 *
 * Copyright (C) 1996 Blue Marsh Softworks -- All Rights Reserved.
 *
 * PROJECT:      COMP 413 Software Engineering Project
 * MODULE:       Document Methods
 * FILE:         document.goc
 *
 * AUTHOR:       Nathan Fiedler
 *
 * REVISION HISTORY:
 *      Name     Date      Description
 *      ----     ----      -----------
 *       NF      03/07/96  Initial version
 *
 * DESCRIPTION:
 *      These are the function definitions and method definitions
 *      for the document object.
 *
 *******************************************************************/

/********************************************************************
 *                 Headers
 *******************************************************************/
    @include <stdapp.goh>
    #include "graph.h"             /* Ken's header file */
    @include "app.goh"             /* Our classes and constants. */
    @include "global.goh"          /* Must be after app.goh. Includes
                                    * globals, objects, prototypes. */
    #include <string.h>            /* For strlen */
    #include <ctype.h>             /* For toupper */

/********************************************************************
 *                 Function Definitions
 *******************************************************************/

/********************************************************************
 *                 Code for GDocumentClass
 *******************************************************************/

/********************************************************************
 *       MSG_GEN_DOCUMENT_INITIALIZE_DOCUMENT_FILE
 ********************************************************************
 * SYNOPSIS:     This creates an empty document.
 * PARAMETERS:   none
 * RETURNS:      Boolean - TRUE if error occurred
 * STRATEGY:     Initialize some variables and instance data,
 *               then initialize the main graph. End by calling
 *               the superclass.
 * ERRORS:       Nothing to check.
 * REVISION HISTORY:
 *      Name     Date      Description
 *      ----     ----      -----------
 *       NF      03/07/96  Initial version
 *******************************************************************/
@extern method GDocumentClass, MSG_GEN_DOCUMENT_INITIALIZE_DOCUMENT_FILE {

     /*
      * Initialize some global variables.
      * Initialize the adjacency matrix.
      */
  fileHan_g = NullHandle;
  winHan_g = NullHandle;
  verticesSelected_g.VNL_count = 0;
  AMInitializeGraph();

  return( @callsuper() );
} /* MSG_GEN_DOCUMENT_INITIALIZE_DOCUMENT_FILE */

/********************************************************************
 *       MSG_GEN_DOCUMENT_ATTACH_UI_TO_DOCUMENT
 ********************************************************************
 * SYNOPSIS:     This message is sent to the GenDocument object
 *               when a particular document is about to be displayed.
 *               It allows the document to attach any UI objects, or
 *               objects that should be displayed, to the GenDocument
 *               so that the first visual update will draw everything.
 * PARAMETERS:   none
 * RETURNS:      nothing
 * STRATEGY:     Call the superclass, then tell primary to update
 *               its scrollers. See what the extension is on the
 *               file name and call the appropriate routine based
 *               on that.
 * ERRORS:       Nothing to check.
 * REVISION HISTORY:
 *      Name     Date      Description
 *      ----     ----      -----------
 *       NF      03/07/96  Initial version
 *******************************************************************/
@extern method GDocumentClass, MSG_GEN_DOCUMENT_ATTACH_UI_TO_DOCUMENT {
     /* Pointer to visual instance data. */
  VisInstance * vself;

     /*
      * Read in the graph from the file by calling
      * one of Ken's functions. Use the extension to
      * know which function to call.
      */
  pself = ObjDerefGen( oself );
  fileHan_g = pself->GDI_fileHandle;
  pself->startingNode = VERTEX_INVALID_NUM;
  pself->endingNode = VERTEX_INVALID_NUM;
  verticesSelected_g.VNL_count = 0;

  @callsuper();

     /*
      * Set GenView document bounds.
      */
  pself = ObjDerefGen( oself );
  @send pself->GCI_genView::MSG_GEN_VIEW_SET_DOC_BOUNDS( 1000, 1000, 0, 0 );

     /*
      * Set the VI_bounds of the document.
      */
  vself = ObjDerefVis( oself );
  vself->VI_bounds.R_left = 0;
  vself->VI_bounds.R_top = 0;
  vself->VI_bounds.R_right = 1000;
  vself->VI_bounds.R_bottom = 1000;

     /*
      * Read in the file.
      */
  FilePos( pself->GDI_fileHandle, 0L, FILE_POS_START );
  if ( toupper(
           pself->GDI_fileName[
               strlen( pself->GDI_fileName )-1] ) == 'M' ) {
    readAdjMatrix( pself->GDI_fileHandle );
  }
  else {
    readAdjList( pself->GDI_fileHandle );
  }

     /*
      * Update scrollers for the OmniGo.
      */
  @send GPrimary::MSG_VIS_UPDATE_WIN_GROUP( VUM_DELAYED_VIA_UI_QUEUE );
} /* MSG_GEN_DOCUMENT_ATTACH_UI_TO_DOCUMENT */

/********************************************************************
 *       MSG_GEN_DOCUMENT_DETACH_UI_FROM_DOCUMENT
 ********************************************************************
 * SYNOPSIS:     This message is sent to the GenDocument object when
 *               the document is about to be closed.  It allows the
 *               GenDocument to detach all the UI objects it attached
 *               earlier before closing.
 * PARAMETERS:   none
 * RETURNS:      nothing
 * STRATEGY:     Clear a couple of variables and then call the
 *               superclass for default behavior.
 * ERRORS:       Nothing to check.
 * REVISION HISTORY:
 *      Name     Date      Description
 *      ----     ----      -----------
 *       NF      03/07/96  Initial version
 *******************************************************************/
@extern method GDocumentClass, MSG_GEN_DOCUMENT_DETACH_UI_FROM_DOCUMENT {

  pself = ObjDerefGen( oself );
  pself->startingNode = VERTEX_INVALID_NUM;
  pself->endingNode = VERTEX_INVALID_NUM;
  verticesSelected_g.VNL_count = 0;

  @callsuper();
} /* MSG_GEN_DOCUMENT_DETACH_UI_FROM_DOCUMENT */

/********************************************************************
 *                 MSG_GEN_DOCUMENT_OPEN
 ********************************************************************
 * SYNOPSIS:     This message is sent to the GenDocument object when
 *               the document is being opened.  It allows the
 *               GenDocument to load the document from disk.
 * PARAMETERS:   ( word *                 fileOrError,
 *                 DocumentCommonParams * params )
 * RETURNS:      Boolean - TRUE if error occurred
 * STRATEGY:     Make sure nothing went wrong with open.
 *******************************************************************/
@extern method GDocumentClass, MSG_GEN_DOCUMENT_OPEN {

     /*
      * Check if error occurred during open.
      * If so, cancel the open. Return error status.
      * (Don't try to do MSG_GEN_DESTROY, that gives an error.)
      */
  if ( @callsuper() ) {
    @send self::MSG_GEN_REMOVE( VUM_NOW, 0 );
    return( TRUE );
  }
  else {
    return( FALSE );
  }
} /* MSG_GEN_DOCUMENT_OPEN */

/********************************************************************
 *                 MSG_GEN_DOCUMENT_PHYSICAL_REVERT
 ********************************************************************
 * SYNOPSIS:     Return to the last-saved version of the file.
 * PARAMETERS:   word *error
 * RETURNS:      Boolean - TRUE if error occurred
 * STRATEGY:     Call superclass.
 * ERRORS:       Nothing to check.
 * REVISION HISTORY:
 *      Name     Date      Description
 *      ----     ----      -----------
 *       NF      03/07/96  Initial version
 *******************************************************************/
@extern method GDocumentClass, MSG_GEN_DOCUMENT_PHYSICAL_REVERT {

     /*
      * Read in the graph from the file by calling
      * one of Ken's functions. Use the extension to
      * know which function to call.
      */
  pself = ObjDerefGen( oself );
  FilePos( pself->GDI_fileHandle, 0L, FILE_POS_START );
  if ( toupper(
           pself->GDI_fileName[
               strlen( pself->GDI_fileName )-1] ) == 'M' ) {
    readAdjMatrix( pself->GDI_fileHandle );
  }
  else {
    readAdjList( pself->GDI_fileHandle );
  }
  pself->startingNode = VERTEX_INVALID_NUM;
  pself->endingNode = VERTEX_INVALID_NUM;
  verticesSelected_g.VNL_count = 0;

  return( @callsuper() );
} /* MSG_GEN_DOCUMENT_PHYSICAL_REVERT */

/********************************************************************
 *       MSG_GEN_DOCUMENT_PHYSICAL_SAVE,
 *       MSG_GEN_DOCUMENT_PHYSICAL_UPDATE
 ********************************************************************
 * SYNOPSIS:     Save text to file
 * PARAMETERS:   word * error
 * RETURNS:      Boolean - TRUE if error occurred
 * STRATEGY:     Call superclass.
 * ERRORS:       Nothing to check.
 * REVISION HISTORY:
 *      Name     Date      Description
 *      ----     ----      -----------
 *       NF      03/07/96  Initial version
 *******************************************************************/
@extern method GDocumentClass, MSG_GEN_DOCUMENT_PHYSICAL_SAVE,
                          MSG_GEN_DOCUMENT_PHYSICAL_UPDATE {

     /*
      * Write out the graph to the file by calling
      * one of Ken's functions. Use the extension to
      * know which function to call.
      */
  pself = ObjDerefGen( oself );
  FilePos( pself->GDI_fileHandle, 0L, FILE_POS_START );
  if ( toupper(
           pself->GDI_fileName[
               strlen( pself->GDI_fileName )-1] ) == 'M' ) {
    writeAdjMatrix( pself->GDI_fileHandle );
  }
  else {
    writeAdjList( pself->GDI_fileHandle );
  }
  FileTruncate( pself->GDI_fileHandle,
                FilePos( pself->GDI_fileHandle, 0, FILE_POS_RELATIVE ),
                FALSE );

  return( @callsuper() );
} /* MSG_GEN_DOCUMENT_PHYSICAL_SAVE,
   * MSG_GEN_DOCUMENT_PHYSICAL_UPDATE */

/********************************************************************
 *       MSG_GEN_DOCUMENT_PHYSICAL_SAVE_AS_FILE_HANDLE
 ********************************************************************
 * SYNOPSIS:     Save text to a newly created file.
 * PARAMETERS:   word * error
 *               FileHandle file
 * RETURNS:      Boolean - TRUE if error occurred
 * STRATEGY:     Call superclass.
 * ERRORS:       Nothing to check.
 * REVISION HISTORY:
 *      Name     Date      Description
 *      ----     ----      -----------
 *       NF      03/07/96  Initial version
 *******************************************************************/
@extern method GDocumentClass,
          MSG_GEN_DOCUMENT_PHYSICAL_SAVE_AS_FILE_HANDLE {

     /*
      * Write out the graph to the file by calling
      * one of Ken's functions. Use the extension to
      * know which function to call.
      */
  pself = ObjDerefGen( oself );
  fileHan_g = pself->GDI_fileHandle;
  FilePos( pself->GDI_fileHandle, 0L, FILE_POS_START );
  if ( toupper(
           pself->GDI_fileName[
               strlen( pself->GDI_fileName )-1] ) == 'M' ) {
    writeAdjMatrix( pself->GDI_fileHandle );
  }
  else {
    writeAdjList( pself->GDI_fileHandle );
  }
  FileTruncate( pself->GDI_fileHandle,
                FilePos( pself->GDI_fileHandle, 0, FILE_POS_RELATIVE ),
                FALSE );

  return( @callsuper() );
} /* MSG_GEN_DOCUMENT_PHYSICAL_SAVE_AS_FILE_HANDLE */

/********************************************************************
 *       MSG_META_EXPOSED
 ********************************************************************
 * SYNOPSIS:     Redraw the contents of the view.
 * PARAMETERS:	 WindowHandle win - Window to draw to
 * RETURNS:      nothing
 * STRATEGY:	 This message is sent by the windowing system when a
 *	    	 portion of the GenView has become invalid, either
 *	    	 because a window that was obscuring it has been moved,
 *	    	 or because someone called GrInvalRect.
 *	    	 We redraw the entire document, after telling the
 *		 graphics system we're drawing to the invalid portion
 *		 of the window.
 * ERRORS:       Nothing to check.
 * REVISION HISTORY:
 *      Name     Date      Description
 *      ----     ----      -----------
 *       NF      03/07/96  Initial version
 *******************************************************************/
@extern method GDocumentClass, MSG_META_EXPOSED {

     /*
      * Create the gstate, then save it to a global so the
      * drawLine, drawDirectedLine, and drawNode can use it.
      * Call Ken's graph drawing function.
      * End the gstate update. Destroy the gstate.
      */
  winHan_g = win;
  CreateGState();
  GrBeginUpdate( gstate_g );
  AMDrawGraph();
  GrEndUpdate( gstate_g );
  DestroyGState();
} /* MSG_META_EXPOSED */

/********************************************************************
 *                 MSG_META_START_SELECT
 ********************************************************************
 * SYNOPSIS:     Select an edge or add a new vertex.
 * PARAMETERS:	 MouseReturnParams * retVal
 *               sword               xPosition
 *               sword               yPosition
 *               word                inputState
 * RETURNS:      nothing
 * STRATEGY:     If not selecting an edge, use the mouse position
 *               to create a new vertex.
 * ERRORS:       Nothing to check.
 * REVISION HISTORY:
 *      Name     Date      Description
 *      ----     ----      -----------
 *       NF      03/07/96  Initial version
 *******************************************************************/
@extern method GDocumentClass, MSG_META_START_SELECT {

  pself = ObjDerefGen( oself );
  pself->startingNode = AMLocateVertex( xPosition, yPosition );
  retVal->flags = MRF_PROCESSED;
} /* MSG_META_START_SELECT */

/********************************************************************
 *                 MSG_META_END_SELECT
 ********************************************************************
 * SYNOPSIS:     Select a vertex, an edge or add a new vertex, or
 *               add a new edge.
 * PARAMETERS:	 MouseReturnParams * retVal
 *               sword               xPosition
 *               sword               yPosition
 *               word                inputState
 * RETURNS:      nothing
 * STRATEGY:     Check if user clicked on a vertex on the ending
 *               of the selection. If so, check if the user clicked
 *               on a vertex at the start of the selection. If so,
 *               highlight the edge inbetween. Else, toggle the
 *               selected state of the vertex that ended the
 *               selection and redraw it. If the user did not end
 *               the selection on a vertex, see if s/he started it
 *               on a vertex, and if not add a new vertex at the
 *               ending mouse position and draw it.
 * ERRORS:       Nothing to check.
 * REVISION HISTORY:
 *      Name     Date      Description
 *      ----     ----      -----------
 *       NF      03/07/96  Initial version
 *******************************************************************/
@extern method GDocumentClass, MSG_META_END_SELECT {
     /* Used to add new point to graph. */
  Point_t newPt;
     /* State of the selected vertext. */
  VertexState vstate;
     /* Edge number between startingNode and endingNode. */
  EdgeNumber edge;
     /* State of the edge. */
  EdgeState estate;

  pself = ObjDerefGen( oself );
  pself->endingNode = AMLocateVertex( xPosition, yPosition );
  if ( pself->endingNode < VERTEX_INVALID_NUM ) {
    if ( ( pself->startingNode < VERTEX_INVALID_NUM ) &&
         ( pself->endingNode != pself->startingNode ) ) {
         /*
          * Check for an undirected edge from starting to ending.
          * Then if there's still no edge, add one.
          * If there's an edge, toggle its selection state.
          * Redraw edge no matter what.
          */
      edge = AMGetEdgeNumber( pself->startingNode,
                              pself->endingNode );
      if ( edge == EDGE_INVALID_NUM ) {
        /*
         * Add new directed edge.
         * Get its edge number so we can draw it.
         */
        AMAddEdge( pself->startingNode, pself->endingNode, TRUE );
        edge = AMGetEdgeNumber( pself->startingNode,
                                pself->endingNode );
        @send GDocumentGroup::MSG_GEN_DOCUMENT_GROUP_MARK_DIRTY_BY_FILE(
                  pself->GDI_fileHandle );
      }
      else {
           /*
            * If there exists an edge between startingNode and
            * endingNode, then toggle its selection and redraw it.
            */
        estate = AMGetEdgeNumState( edge );
        if ( estate & ES_SELECTED ) {
          AMSetEdgeNumState( edge, estate & ~ES_SELECTED );
        }
        else {
          AMSetEdgeNumState( edge, estate | ES_SELECTED );
        }
      }
      CreateGState();
      AMDrawEdgeNum( edge, C_BLACK );
      DestroyGState();
    }
    else {
         /*
          * A vertex was clicked on. Toggle its selection state.
          * Save the first and second selected nodes for the
          * search operations. Draw vertex, no matter what happened.
          * Show mouse event was handled.
          */
      vstate = AMGetVertexState( pself->endingNode );
      if ( vstate & VS_SELECTED ) {
           /* Selected - unselect it. */
        AMSetVertexState( pself->endingNode, vstate & ~VS_SELECTED );
        VertexNumListRemoveNumber( &verticesSelected_g, pself->endingNode );
      }
      else {
           /* Not selected - select it. */
        AMSetVertexState( pself->endingNode, vstate | VS_SELECTED );
        VertexNumListAddNumber( &verticesSelected_g, pself->endingNode );
      }
      CreateGState();
      AMDrawVertexNum( pself->endingNode, C_BLACK );
      DestroyGState();
    }
  }
  else {
    if ( pself->startingNode == VERTEX_INVALID_NUM ) {
         /*
          * Vertex was not clicked on.
          * Create point using mouse position.
          * Add new point to graph.
          */
      newPt.P_x = xPosition;
      newPt.P_y = yPosition;
      pself->endingNode = AMAddVertex( newPt );
         /*
          * If there's an error, set vertex to zero so redraw
          * below will not cause any visual problems.
          * Display message about maximum number of vertices.
          */
      if ( pself->endingNode > VERTEX_MAX_COUNT ) {
        pself->endingNode = 0;
        UserStandardDialog( (char*)0, (char*)0, (char*)0, (char*)0,
                            "Cannot add more vertices.",
                            ( ( CDT_WARNING <<
                                CDBF_DIALOG_TYPE_OFFSET ) |
                              ( GIT_NOTIFICATION <<
                                CDBF_INTERACTION_TYPE_OFFSET ) ) );
      }
      else {
           /*
            * Draw vertex, no matter what happened.
            * Show mouse event was handled.
            * Mark the document dirty.
            */
        CreateGState();
        AMDrawVertexNum( pself->endingNode, C_BLACK );
        DestroyGState();
        @send GDocumentGroup::MSG_GEN_DOCUMENT_GROUP_MARK_DIRTY_BY_FILE(
                  pself->GDI_fileHandle );
      }
    }
  }
  retVal->flags = MRF_PROCESSED;
} /* MSG_META_END_SELECT */

/********************************************************************
 *                 MSG_PRINT_START_PRINTING
 ********************************************************************
 * SYNOPSIS:     Print the graph to the current printer.
 * CALLED BY:    GPrintControl
 * PARAMETERS:   optr         printCtrlOD
 *               GStateHandle gstate
 * RETURNS:      nothing
 * STRATEGY:     Get printer margins, then send MSG_VIS_DRAW to
 *               self with the DF_PRINT flag, then signal
 *               printing completed.
 * ERRORS:       Nothing to check.
 * REVISION HISTORY:
 *      Name     Date      Description
 *      ----     ----      -----------
 *       NF      04/23/96  Initial version
 *******************************************************************/
@extern method GDocumentClass, MSG_PRINT_START_PRINTING {

     /*
      * Set the global gstate for drawing functions.
      * Set the document size to reflect graph size.
      * Set number of pages to print.
      * Tell ourself to redraw to the printer.
      * Single end of page.
      * Wrap up the printing (tell PC we're done).
      */
  gstate_g = gstate;
  @call printCtrlOD::MSG_PRINT_CONTROL_SET_DOC_SIZE( 1000, 1000 );
  @call printCtrlOD::MSG_PRINT_CONTROL_SET_TOTAL_PAGE_RANGE( 1, 1 );
  @call self::MSG_VIS_DRAW( DF_PRINT, gstate_g );
  GrNewPage( gstate_g, PEC_FORM_FEED );
  @send printCtrlOD::MSG_PRINT_CONTROL_PRINTING_COMPLETED();
  gstate_g = NullHandle;
} /* MSG_PRINT_START_PRINTING */

