/*
** FILE:   xm_DocumentModel.java
**
** (c) 1998 Steve Withall.
**
** HISTORY:
**    28Mar98  stevew  Created.
**    06Apr98  stevew  Added xe_ParseListener interface.
**    11Apr98  stevew  Added use of xt_ParseThread.
**    03Jul98  stevew  Moved from xt to xm.
*/
package xm;

import xm.xm_ParseListener;
import xm.xm_StatusBar;
import xm.xm_XmlEngine;

import xg.xg_Document;
import xg.xg_Node;

import eh.eh_Debug;

import com.sun.java.swing.text.AttributeSet;
import com.sun.java.swing.text.BadLocationException;
import com.sun.java.swing.text.DefaultStyledDocument;
import com.sun.java.swing.text.Element;
import com.sun.java.swing.text.StyleConstants;
import com.sun.java.swing.text.StyleContext;

import com.sun.java.swing.tree.DefaultTreeModel;
import com.sun.java.swing.JProgressBar;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;

import java.io.FileWriter;
import java.io.IOException;

//*****************************************************************************
/** A model for working with an XML document, capable of supporting multiple
 *  simultaneous views of a single document (source editor, tree, etc.).
 *
 *  Note that while this class is a 'document' in the Swing sense (inheriting
 *  from DefaultStyledDocument), it is *not* a document in the xg_Document sense
 *  (indeed, it *contains* one of those). Thus, to avoid getting confused over
 *  which of these two different types of document this is, it has been called
 *  something other than a 'document'. Also, calling this a *model* makes clear
 *  its intended place in an MVC design.
 */
public class xm_DocumentModel extends    DefaultStyledDocument
                              implements xm_ParseListener
{
    /** The XML document (as a parse tree) which is currently being worked on. */
    xg_Document       CurrentDocument   = null;

    /** The model of the current document used for display in a JTree. */
    DefaultTreeModel  DocumentTreeModel = null;

    /** The XML engine, responsible for XML processing. */
    xm_XmlEngine      TheXmlEngine      = null;

    /** Status bar for displaying parse progress (if not null). */
    xm_StatusBar      TheStatusBar      = null;

    /** Progress bar for displaying how far through the source the parse has
     *  reached. */
    JProgressBar      ParseProgressBar  = null;

    /** Flag indicating whether we are undertaking styled editing at the moment
     *  (true means we are). This is present only temporarily (hopefully!), due
     *  to severe problems being caused in other code - notably non-styled editing
     *  when the root of the parse tree is passed back from getDefaultRootElement(). */
    boolean           StyledEditModeFlag = false;

    //*****************************************************************************
    /** Constructor.
      *
      * @param  InputXmlEngine  The engine to be used for parsing
      */
    public xm_DocumentModel(xm_XmlEngine  InputXmlEngine)
    {
	    eh_Debug.add(5, "Construct xm_DocumentModel");
        CurrentDocument   = new xg_Document();
        CurrentDocument.setDocument(this);
	    DocumentTreeModel = new DefaultTreeModel(CurrentDocument);
        TheXmlEngine      = InputXmlEngine;
    }

    //*****************************************************************************
    /** Clear this document, ready to start afresh.
     */
    public void clear()
    {
	    eh_Debug.add(5, "xm_DocumentModel.Clear: Reset the document");
        CurrentDocument.reset();
        DocumentTreeModel.reload();

        try
        {
            remove(getStartPosition().getOffset(), getLength() );
        }
        catch (Exception  InputException)
        {
            eh_Debug.add(3, "Cannot clear document source: " + InputException);
        }
        return;
    }

    //*****************************************************************************
    /** Save this document to the named file.
     *
     *  @param      InputFilePathnameString  The pathname of the file to which the
     *                                        document is to be saved
     *  @exception  IOException              Error writing to FileWriter
     */
    public void save(String  InputFilePathnameString) throws IOException
    {
        if (InputFilePathnameString == null)
            eh_Debug.add(2, "xm_DocumentModel.save: Null save file name");
        else if (CurrentDocument == null)
            eh_Debug.add(2, "xm_DocumentModel.save: No document to save");
        else
        {
           eh_Debug.add(3, "xm_DocumentModel.save: Save to file '" + InputFilePathnameString + "'");
           FileWriter  SaveFileWriter = new FileWriter(InputFilePathnameString);
           CurrentDocument.save(SaveFileWriter);
           SaveFileWriter.close();
        }
    }

    //*****************************************************************************
    /** Save the source of this document to the named file.
     *
     *  @param      InputFilePathnameString  The pathname of the file to which the
     *                                        document is to be saved
     *  @exception  IOException              Error writing to FileWriter
     */
    public void saveFromSource(String  InputFilePathnameString) throws IOException
    {
        if (InputFilePathnameString == null)
            eh_Debug.add(2, "xm_DocumentModel.saveFromSource: Null save file name");
        else
        {
            String  SourceString = getSourceString();
            if (SourceString == null)
                eh_Debug.add(2, "xm_DocumentModel.saveFromSource: No source to save");
            else
            {
                eh_Debug.add(3, "xm_DocumentModel.saveFromSource: Save source to file '"
                                        + InputFilePathnameString + "'");
                FileWriter  SaveFileWriter = new FileWriter(InputFilePathnameString);
                SaveFileWriter.write(SourceString);
                SaveFileWriter.close();
            }
        }
    }

    //*****************************************************************************
    /** Set the XML processor to be used to process this document.
     *
     *  @param  InputXmlEngine  The XML processor
     */
    public void setXmlEngine(xm_XmlEngine  InputXmlEngine)
    {
        TheXmlEngine = InputXmlEngine;
    }

    //*****************************************************************************
    /** Set the status bar for displaying parse progress.
     *
     *  @param  InputStatusBar  The status bar for displaying parse progress
     */
    public void setStatusBar(xm_StatusBar  InputStatusBar)
    {
        TheStatusBar = InputStatusBar;
        if (TheStatusBar == null)
            ParseProgressBar = null;
        else
            ParseProgressBar = TheStatusBar.getProgressBar();
    }

    //*****************************************************************************
    /** Set the styled edit mode flag.
     *
     *  @param  InputStyledEditModeFlag  true means we are in styled edit mode;
                                          false means we're not
     */
    public void setStyledEditModeFlag(boolean  InputStyledEditModeFlag)
    {
        StyledEditModeFlag = InputStyledEditModeFlag;
    }

    //*****************************************************************************
    /** Get the last XML document which was parsed.
     *
     *  @return  The last XML document parsed
     */
    public xg_Document getCurrentDocument()
    {
        return CurrentDocument;
    }

    //*****************************************************************************
    /** Get the current source in the form of a string.
     *
     *  @return  A string representation of the current source
     */
    public String getSourceString()
    {
        String  SourceString = null;
        try
        {
            SourceString = getText(getStartPosition().getOffset(), getLength() );
        }
        catch (BadLocationException  InputException)
        {
	        eh_Debug.add(2, "xm_DocumentModel.getSourceString: Document not initialised: "
	                               + InputException);
        }
        return SourceString;
    }

    //*****************************************************************************
    /** Get the tree model of the current document.
     *
     *  @return  The tree model of the current document
     */
    public DefaultTreeModel getDocumentTreeModel()
    {
        return DocumentTreeModel;
    }

    //*****************************************************************************
    /** Get the XML processor.
     *
     *  @return  The XML processor
     */
    public xm_XmlEngine getXmlEngine()
    {
        return TheXmlEngine;
    }

    //*****************************************************************************
    /** Get the status bar.
     *
     *  @return  The status bar
     */
    public xm_StatusBar getStatusBar()
    {
        return TheStatusBar;
    }

    //*****************************************************************************
    /** Get the styled edit mode flag.
     *
     *  @return  true means we are in styled edit mode; false means we're not
     */
    public boolean getStyledEditModeFlag()
    {
        return StyledEditModeFlag;
    }

    //*****************************************************************************
    //*Methods*implementing*the*Document*interface.********************************
    //*****************************************************************************
    /** Get the default root element.
     *
     *  @return  The default root element
     */
    public Element getDefaultRootElement()
    {
        Element  DefaultRootElement = null;
        if (    StyledEditModeFlag == false
             || CurrentDocument == null
             || CurrentDocument.getChildCount() == 0)
        {
            eh_Debug.add(6, "xm_DocumentModel.getDefaultRootElement: Return swing Document");
            DefaultRootElement = super.getDefaultRootElement();
        }
        else
        {
            eh_Debug.add(6, "xm_DocumentModel.getDefaultRootElement: Return xg Document");
//            DefaultRootElement = CurrentDocument.getChild(0);  //TEMP
            DefaultRootElement = CurrentDocument;
//        DefaultRootElement = super.getDefaultRootElement();
//        eh_Debug.add(6, "xm_DocumentModel.getDefaultRootElement: DefaultRootElement is '"
//                                + DefaultRootElement + "'");
        }

        return DefaultRootElement;
    }

    //*****************************************************************************
    /** Get all the root elements which are defined.
     *
     *  @return  An array of all root elements
     */
    public Element[] getRootElements()
    {
        Element[]  RootElements;
        if (    CurrentDocument == null
             || CurrentDocument.getChildCount() == 0)
        {
            eh_Debug.add(6, "xm_DocumentModel.getRootElements: Return 1 root element (CurrentDocument is null)");
            RootElements = new Element[1];
        }
        else
        {
            eh_Debug.add(6, "xm_DocumentModel.getRootElements: Return 2 root elements (CurrentDocument is not null)");
            RootElements = new Element[2];
            RootElements[1] = CurrentDocument;
        }

        RootElements[0] = super.getDefaultRootElement();

        return RootElements;
    }

    //*****************************************************************************
    //*Methods*overriding*DefaultStyledDocument*methods.***************************
    //*****************************************************************************
    /** Get the color to be used for the foreground, based on the value of the
     *  'color' attribute in the InputAttributeSet.
     *
     *  @param   InputAttributeSet  The attribute set
     *  @return                     The foreground color
     */
    public Color getForeground(AttributeSet  InputAttributeSet)
    {
//        eh_Debug.add(6, "xm_DocumentModel.getForeground:");
        Color   ForegroundColor = Color.black;
        String  ColorName       = (String)InputAttributeSet.getAttribute("color");
        if (ColorName != null)
        {
            //TBD Add support for HTML 4.0 color names.
            //TBD Move all this code to a support class.

            try
            {
                ForegroundColor = Color.decode(ColorName);
            }
            catch (NumberFormatException  InputException)
            {
                eh_Debug.add(6, "xm_DocumentModel.getForeground: Color '"
                                       + ColorName + "' is not a valid color code");
            }
        }
        return ForegroundColor;
//	    return StyleConstants.getForeground(InputAttributeSet);
    }

    //*****************************************************************************
    /** Get the background colour from an attribute set.
     *
     *  @param   InputAttributeSet the attribute set
     *  @return  the colour
     */
//    public Color getBackground(AttributeSet InputAttributeSet)
//    {
//        eh_Debug.add(6, "xm_DocumentModel.getBackground:");
//	    throw new Error("not implemented");
//    }

    //*****************************************************************************
    /** Get the font from an attribute set.
     *
     *  @param   InputAttributeSet  The attribute set
     *  @return                     The resulting font
     */
    public Font getFont(AttributeSet  InputAttributeSet)
    {
        eh_Debug.add(6, "xm_DocumentModel.getFont:");

        // Determine the font family.
        String  FontFamilyName = (String)InputAttributeSet.getAttribute("font");
        if (FontFamilyName == null)
            FontFamilyName = "Monospaced";

        // Determine the style of font: bold/italic/normal.
        int  FontStyle = Font.PLAIN;
        String  BoldValue = (String)InputAttributeSet.getAttribute("bold");
        if (    BoldValue != null
             && BoldValue.equals("true") )
            FontStyle |= Font.BOLD;

        String  ItalicValue = (String)InputAttributeSet.getAttribute("italic");
        if (    ItalicValue != null
             && ItalicValue.equals("true") )
            FontStyle |= Font.ITALIC;

        // Determine the font size.
        int     FontSize = 12;
        String  FontSizeValue = (String)InputAttributeSet.getAttribute("size");
        if (FontSizeValue != null)
        {
            try
            {
                Integer  FontSizeInteger = Integer.valueOf(FontSizeValue);
                FontSize = FontSizeInteger.intValue();
            }
            catch (NumberFormatException  InputException)
            {
                eh_Debug.add(6, "xm_DocumentModel.getFont: Size '"
                                       + FontSizeValue + "' is not a valid integer");
            }
        }

        Font  NewFont = new Font(FontFamilyName, FontStyle, FontSize);
//        int size = StyleConstants.getFontSize(attr);
//	    StyleContext styles = (StyleContext)getAttributeContext();
        return NewFont;
//    	return styles.getFont(InputAttributeSet);
    }

    //*****************************************************************************
    // Methods implementing the xm_ParseListener interface.
    //*****************************************************************************
    /** The start of a node has been parsed.
     *
     *  @param  InputNode         The node whose start has just been parsed
     *  @param  InputStartOffset  The offset from the start of the source at
     *                             which this node starts
     */
    public void startNode(xg_Node  InputNewNode, int  InputStartOffset)
    {
        eh_Debug.add(7, "xm_DocumentModel.startNode: at position "
                               + InputStartOffset);

        // Update the progress bar.
        if (ParseProgressBar != null)
            ParseProgressBar.setValue(InputStartOffset);

        // Create a position in the source document for the start of this node.
        try
        {
            int  StartOffset = InputStartOffset
                                  - InputNewNode.getPrecedingWhitespaceLength();
            InputNewNode.setStartPosition(createPosition(StartOffset) );
        }
    	catch (BadLocationException InputException)
    	{
            eh_Debug.add(2, "Document start position " + InputStartOffset
                                + "is bad. Exception = " + InputException);
    	}
    }

    //*****************************************************************************
    /** The end of a node has been parsed.
     *
     *  @param  InputNewNode    The node which has just been parsed
     *  @param  InputEndOffset  The offset from the start of the source at which
     *                           this node ends
     */
    public void endNode(xg_Node  InputNewNode, int  InputEndOffset)
    {
        eh_Debug.add(7, "xm_DocumentModel.endNode: at offset " + InputEndOffset);

        // Create a position in the source document for the end of this node.
        try
        {
            InputNewNode.setEndPosition(createPosition(InputEndOffset) );
        }
    	catch (BadLocationException InputException)
    	{
            eh_Debug.add(2, "Document end position " + InputEndOffset
                                + " is bad. Exception = " + InputException);
    	}

        // Cause a tree insert event to be fired.
//        DocumentTreeModel.reload();  //TEMP
/*        xg_Node  ParentNode = InputNewNode.getParentNode();
        if (ParentNode == null)
            eh_Debug.add(2, "xm_DocumentModel.startNode: ParentNode is null");
        else
        {
        eh_Debug.add(8, "xm_DocumentModel.startNode: ParentNode '" + ParentNode
                               + "' has " + ParentNode.getChildCount() + " children");

            int[]  NewIndexes = new int[1];
            NewIndexes[0]     = ParentNode.getChildCount();
            DocumentTreeModel.nodesWereInserted(ParentNode, NewIndexes);

//        DocumentTreeModel.insertNodeInto(InputNewNode,
//                                         ParentNode,
//                                         ParentNode.getChildCount());
        }
*/
//        eh_Debug.add(8, "xm_DocumentModel.endNode: Return");
    }

    //*****************************************************************************
    /** The parse has completed successfully.
     */
    public void endParseSuccessful()
    {
        eh_Debug.add(8, "xm_DocumentModel.endParseSuccessful:");
        DocumentTreeModel.reload();
    }

    //*****************************************************************************
    /** The parse has completed, but not successfully.
     */
    public void endParseUnsuccessful()
    {
        eh_Debug.add(8, "xm_DocumentModel.endParseUnsuccessful:");
        DocumentTreeModel.reload();
    }
}

//*****************************************************************************
