/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/testsuite/junit/store/org/apache/slide/store/tamino/testtools/logger/Logger.java,v 1.1 2004/03/25 16:18:15 juergen Exp $
 * $Revision: 1.1 $
 * $Date: 2004/03/25 16:18:15 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.slide.store.tamino.testtools.logger;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.util.Base64;
import org.apache.xerces.parsers.DOMParser;
import org.jdom.DocType;
import org.jdom.input.DOMBuilder;
import org.jdom.output.XMLOutputter;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

public class Logger extends DefaultHandler
{
    //General use OutputStream
    private OutputStream fos = null;
    //Memory store
    private java.util.List memoryStore = new java.util.ArrayList();
    private int totalLength = 0;
    //DTD
    //private DTD dtd = null;
    //The Document used to store the XML before outputing to file
    private org.w3c.dom.Document document = null;
    private org.w3c.dom.Element lastElement = null;
    private String dtdLocation = null;
    private boolean debug = false;
    
    private String fileName = "";
    
    private List errors;
    private String problem = "";
    private int success = 0;
    
    private boolean output = true;
    
    /**
     * Default Constructor
     * @throws    FileNotFoundException
     */
    public Logger() throws FileNotFoundException
    {
        //Don't pass either the file name or the location of the logger.xml file
        setupLogger("","logger.xml");
    }
    
    /**
     * Constructor, passing the location of the logger.XML file
     * @throws    FileNotFoundException
     */
    public Logger(String loggerXML) throws FileNotFoundException
    {
        //Pass just loggerXML to the setup method
//      System.out.println("LOGGER.XML "+loggerXML);
        setupLogger("",loggerXML);
    }
    
    /**
     * Constructor, passing the fileName of the Logger File the output will be placed
     * and the location of the Logger.XML file.
     * @throws    FileNotFoundException
     */
    public Logger (String fileName, String loggerXML) throws FileNotFoundException
    {
        //Pass the fileName and loggerXML to the setup method
        setupLogger(fileName,loggerXML);
    }
    
    /**
     * Start the Logger logging to the OutputStream of the file name specified
     * @throws    FileNotFoundException
     */
    public void startLogger(String fileName) throws FileNotFoundException
    {
        //If FileOuputStream (fos) = null then create a new file
        if (this.fos == null)
        {
            this.fos = new FileOutputStream(fileName);
            System.out.println("LOGGER - INFO - File "+fileName+" created");
            this.fileName = fileName;
        }
        else
        {
            System.err.println("LOGGER - WARNING - File ("+fileName+") is already in use");
        }
    }
    
    /**
     * Stop the Logger logging to the OutputStream
     */
    public void stopLogger() throws Exception
    {
        byte[] all = mergeArrayList(this.memoryStore,this.totalLength);
        //outputBuffer(all,all.length,System.out);
        if (dtdLocation == null)
        {
            //DTD has not been created, output the byte[] direct to the output stream
            System.out.println("LOGGER - INFO - Sending document directly to file");
            outputBuffer(all,all.length,this.fos);
        }
        else
        {
            if (this.output)
            {
                //Format the byte[] to XML
                System.out.println("LOGGER - INFO - Formating document to XML");
                formatOutput(all);
                System.out.println("LOGGER - INFO - Sending XML to file");
                //Print the DOCUMENT
                printDOM();
                //validateXML(this.fileName);
            }
            else
            {
                System.out.println("LOGGER - INFO - Sending document directly to file");
                outputBuffer(all,all.length,this.fos);
            }
        }
        try
        {
            if (!(this.fos == null))
            {
                this.fos.close();
                System.out.println("LOGGER - INFO - File closed");
                this.fos = null;
            }
        }
        catch (IOException ioe)
        {
            System.err.println("LOGGER - ERROR - Error closing file");
            ioe.printStackTrace();
        }
    }
    
    /**
     * Setup the logger
     * @throws    FileNotFoundException
     */
    private void setupLogger(String fileName, String loggerXML) throws FileNotFoundException
    {
        System.out.println(loggerXML);
        //If loggerXML length = 0 then logger.xml is at the default location
        if (loggerXML.length() == 0)
        {
            parseXMLFile("logger.xml");
        }
        else
        {
            parseXMLFile(loggerXML);
        }
        //If fileName length > 0 then a file name has been supplied, create the FileOutputStream
        if (fileName.length() > 0)
        {
            startLogger(fileName);
        }
    }
    
    /**
     * Output Buffer to the file
     */
    public synchronized void outputBuffer(byte[] buffer, int size)
    {
        //System.out.println(new String(buffer));
        if (this.fos != null)
        {
            //Add the byte buffer to the memory store
            this.memoryStore.add(new Integer(size));
            this.memoryStore.add(buffer);
            this.totalLength += size;
        }
    }
    
    
    /**
     * Output to the OutputStream
     */
    private synchronized void outputBuffer(byte[] buffer, int size, OutputStream stream)
    {
        //Check to see if the OutputStream has been set, if not return
        if (stream == null)
            return;
        try
        {
            stream.write(buffer,0,size);
            stream.write(0x0d);
            stream.write(0x0a);
        }
        catch(IOException ioe)
        {
            System.out.println("LOGGER - ERROR - Error writing to OutputStream");
            ioe.printStackTrace();
        }
    }
    
    /**
     * Parse the Logger.xml file
     */
    private void parseXMLFile(String loggerXML)
    {
        try
        {
            DOMParser dom = new DOMParser();
            dom.parse(new InputSource(new FileInputStream(loggerXML)));
            parseLoggerFile(dom.getDocument().getChildNodes());
        }
        catch(IOException ioe)
        {
            ioe.printStackTrace();
        }
        catch(SAXException saxe)
        {
            saxe.printStackTrace();
        }
    }
    
    /**
     * Merge the ArrayList into one large byte[]
     */
    private byte[] mergeArrayList(java.util.List arrayList, int length)
    {
        //Create a byte array for the totalLength
        byte[] all = new byte[length];
        byte[] newBuffer = null;
        int ofs = 0;
        int bufferSize = 0;
        for (int i = 0; i < arrayList.size(); i+= 2)
        {
            bufferSize = ((Integer)arrayList.get(i)).intValue();
            newBuffer = (byte[])arrayList.get(i+1);
            System.arraycopy(newBuffer,0,all,ofs,bufferSize);
            ofs += bufferSize;
        }
        
        return all;
    }
    
    private void parseLoggerFile(NodeList list)
    {
        for (int i = 0; i < list.getLength(); i++)
        {
            switch (list.item(i).getNodeType())
            {
                case Node.ELEMENT_NODE    : if (list.item(i).getNodeName().equalsIgnoreCase("dtddoctype"))
                    {
                        //setupDTD(this.dtdLocation,list.item(i).getChildNodes().item(0).getNodeValue());
                        break;
                    }
                    else if (list.item(i).getNodeName().equalsIgnoreCase("dtdlocation"))
                    {
                        setDTDLocation(list.item(i).getChildNodes().item(0).getNodeValue());
                        setupDTD(this.dtdLocation);
                        //System.out.println(list.item(i).getChildNodes().item(0).getNodeValue());
                        break;
                    }
            } //End switch statement
            
            if (list.item(i).hasChildNodes())
            {
                parseLoggerFile(list.item(i).getChildNodes());
            }
        } //END for loop
    }
    
    private void setDTDLocation(String location)
    {
        this.dtdLocation = location;
    }
    
    private void setupDTD(String location)
    {
        String dtdString = "<!DOCTYPE test [\n";
        try
        {
            System.out.println("DTD LOCATION : "+location);
            FileInputStream reader = new FileInputStream(location);
            byte[] buffer = new byte[2048];
            int bytes_read = 0;
            String temp = "";
            while( (bytes_read = reader.read(buffer)) != -1    )
            {
                temp = dtdString.concat((new String(buffer)).trim());
                dtdString = temp;
            }
        }
        catch (IOException ioe)
        {
            ioe.printStackTrace();
            return;
        }
        dtdString = dtdString.concat("]>");
    }
    
    //******************** OUTPUT FORMATTING METHODS *****************************
    
    /**
     * Create the Document used to hold the formated output
     * @reutrn    org.w3c.dom.Document
     */
    private org.w3c.dom.Document createDocument()
    {
        try
        {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            return builder.newDocument();
        }
        catch (ParserConfigurationException pce)
        {
            pce.printStackTrace();
            return null;
        }
    }
    /**
     * Create a ElementName in the Document
     */
    private void createElementSection(String elementName)
    {
        
        try
        {
            if (elementName.equalsIgnoreCase("step"))
            {
                backTrack();
                backTrack();
            }
            org.w3c.dom.Element thischild = this.document.createElement(elementName);
            this.lastElement.appendChild(thischild);
            //  if (!(dtd.isLastElement(elementName)) )
            this.lastElement = thischild;
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        
    }
    
    /**
     * Create a text section to the elementName
     */
    private void createTextSection(String elementName, String text)
    {
        org.w3c.dom.Element child = document.createElement(elementName);
        this.lastElement.appendChild(child);
        child.appendChild(document.createTextNode(text));
    }
    
    /**
     * Create a CDATA section to the elementName
     */
    private void createCDATA(String elementName, String cdata, boolean binaryData)
    {
        if (!binaryData)
        {
            cdata = parseCDATA(cdata);
        }
        org.w3c.dom.Element child = document.createElement(elementName);
        child.setAttribute("encode", String.valueOf(binaryData) );
        this.lastElement.appendChild(child);
        child.appendChild(document.createCDATASection(cdata));
        
    }
    
    /**
     * Back track lastElement
     */
    private void backTrack()
    {
        try
        {
            this.lastElement = (org.w3c.dom.Element) this.lastElement.getParentNode();
        }
        catch(Exception e) //Catch incase try to go back to far
        {
            return;
        }
    }
    
    /**
     * Format the byte[] in memoryStore into XML
     */
    private synchronized void formatOutput(byte[] buffer)
    {
        //outputBuffer(buffer,buffer.length,System.out);
        //Create the document
        //System.out.println(new String(buffer));
        this.document = createDocument();
        if (this.document == null)
        {
            System.out.println("LOGGER - ERROR - Unable to create Document to output");
            return;
        }
        //Create a org.w3c.dom.Element to store the last element used
        this.lastElement = null;
        
        //Create the root element
        org.w3c.dom.Element root = document.createElement("test");
        
        document.appendChild(root);
        this.lastElement = root;
        
        char lastChar = ' ';
        char currentChar = ' ';
        int start = 0;
        int end = 0;
        StringBuffer stringBuffer = new StringBuffer();
        String escape = "";
        
        try
        {
            for (int i = 0; i < buffer.length; i++)
            {
                currentChar = (char) buffer[i];
                //System.out.print(currentChar);
                if ( (currentChar == '@') )
                {
                    if ( (lastChar == '@') )
                    {
                        end = i;
                        //Can ignore upto the first step
                        if (start > 0)
                        {
                            byte[] all = createBodyArray(buffer, start, ((end-1) - start) );
                            parseHTTP(all);
                            //System.out.println(new String(all) );
                        }
                        //Create the escape character
                        while ( !(currentChar == '\n') )
                        {
                            //Add the character to the buffer
                            stringBuffer.append(currentChar);
                            //Increment i
                            i++;
                            //Set the new current char
                            currentChar = (char) buffer[i];
                        }
                        start = i;
                        escape = stringBuffer.toString().substring(1, (stringBuffer.toString().length() - 2) );
                        if (escape.equalsIgnoreCase("response") )
                        {
                            backTrack();
                        }
                        stringBuffer = new StringBuffer();
                        createElementSection(escape);
                    }
                }
                
                
                //Set the last char
                lastChar = currentChar;
            } //END for loop
            byte[] all = createBodyArray(buffer, start, ((buffer.length) - start) );
            parseHTTP(all);
            //System.out.println(new String(all) );
            
            //System.out.println(start+" "+buffer.length);
        } //END try
        catch (Exception e)
        {
            e.printStackTrace();
        }
        
    }
    
    private synchronized byte[] createBodyArray(byte[] buffer, int start, int size)
    {
        byte[] all = new byte[size];
        for (int i = 1; i < size; i++)
        {
            all[i] = buffer[start+i];
            //System.out.print((char)all[i]);
        }
        return all;
    }
    
    /**
     * Create an byte[] of of the supplied byte[] starting at <code>start</code> and finishing at the <code>
     * buffer</code> length
     **/
    private synchronized byte[] createBodyArray(byte[] buffer, int start)
    {
        byte[] all = new byte[buffer.length - start];
        char c;
        int size = buffer.length - start;
        //System.out.println(size);
        for (int i = 0; i < size; i ++)
        {
            c = (char) buffer[start + i];
            //System.out.print(c);
            all[i] = buffer[start + i];
        }
        return all;
    }
    
    /**
     * Get the headers and body section from the byte[]
     **/
    private synchronized void parseHTTP(byte[] buffer)
    {
        char c = ' ';
        byte b;
        
        boolean command = true;
        boolean body = false;
        int bodyStart = 0;
        StringBuffer stringBuffer = new StringBuffer();
        try
        {
            for (int i = 0; i < buffer.length; i++)
            {
                b = buffer[i];
                c = (char) buffer[i];
                if (! ( ( b == (byte)10) || (b == (byte)0)  ) )
                {
                    stringBuffer.append(c);
                    if ( b == (byte)13)
                    {
                        if (stringBuffer.toString().trim().length() == 0)
                        {
                            if (bodyStart == 0)
                            {
                                bodyStart = i;
                                body = true;
                                //System.out.println(bodyStart);
                            }
                        }
                        else if ((command) && (!body) )
                        {
                            //System.out.println("COMMAND\t"+stringBuffer.toString().trim());
                            createTextSection("command",stringBuffer.toString().trim());
                            command = false;
                        }
                        else if ( (!command) && (!body)  )
                        {
                            //System.out.println("HEADER\t"+stringBuffer.toString().trim());
                            createTextSection("header",stringBuffer.toString().trim());
                        }
                        
                        stringBuffer = new StringBuffer();
                    } //END if (b == 13)
                    
                } //END if (b)
                
            } //END for loop
            if (bodyStart > 0)
            {
                
                //Need to go backwards to make sure there is no crap at the end of the buffer
                int end = buffer.length;
                int difference = 0;
                for (int i = buffer.length - 1; i > 0; i--)
                {
                    b = buffer[i];
                    if ( ! ( ((byte)b == 10) || ((byte)b == 0) || ((byte)b == 13) ))
                    {
                        end = i;
                        break;
                    }
                }
                //Work out the different size
                difference = (buffer.length - end);
                byte[] bodyArray = createBodyArray(buffer, bodyStart, (buffer.length - difference));
                boolean encode = containsBinaryData(bodyArray);
                String encodeString = "";
                if (encode)
                {
                    encodeString = new String(Base64.encode(bodyArray));
                }
                else
                {
                    if ( (new String(bodyArray).trim().length() == 0  ))
                    {
                        encodeString = "";
                    }
                    else
                    {
                        encodeString = new String( bodyArray);
                    }
                    
                }
                if (encodeString.length() > 0)
                {
                    createCDATA("body",encodeString.trim(),encode);
                }
            }
        }
        catch (Exception e)
        {
            byte[] bodyArray = createBodyArray(buffer, bodyStart);
            boolean encode = containsBinaryData(bodyArray);
            //System.out.println("start:"+bodyStart+" end:"+end+" end difference:"+ (buffer.length - end ));
            //System.out.println(new String(bodyArray));
            String encodeString = "";
            if (encode)
            {
                encodeString = new String(Base64.encode(bodyArray));
            }
            else
            {
                if ( (new String(bodyArray).trim().length() == 0  ))
                {
                    encodeString = "";
                }
                else
                {
                    encodeString = new String( bodyArray);
                }
            }
            if (encodeString.length() > 0)
            {
                createCDATA("body",encodeString,encode);
            }
        }
    }
    
    /**
     * Check to see if the supplied <code>byte[]</code> contains binary data, ie non characters
     *
     * @pre         buffer != null
     **/
    public synchronized boolean containsBinaryData(byte[] buffer)
    {
        for (int i = 0; i < buffer.length; i++)
        {
            if (! ( Character.isDefined((char)buffer[i]) ) )
                return true;
        }
        return false;
    }
    
    /**
     * Print the DOCUMENT in the form of XML
     */
    private void printDOM()
    {
        try
        {
            DOMBuilder builder = new DOMBuilder ();
            org.jdom.Document doc = builder.build(this.document);
            DocType docType = new DocType("test",this.dtdLocation);
            doc.setDocType(docType);
            XMLOutputter xmlOutput = new XMLOutputter(" ",true);
            xmlOutput.output(doc,this.fos);
        }
        catch (IOException ioe)
        {
            ioe.printStackTrace();
        }
    }
    
    private synchronized String parseCDATA(String bodyString)
    {
//      System.out.println("BODY"+bodyString);
        String temp = "";
        String full = "";
        int current = 0;
        int last = 0;
        do
        {
            //current = bodyString.indexOf("]]>",last);
            current = bodyString.indexOf(">",last);
            if ((last > bodyString.length()) || (current < 0))
                break;
            temp = full.concat(bodyString.substring(last,current)+"&gt;");
            full = temp;
            last = current+1;
        } while(current > 0);
        //System.out.println(full);
        if (full.length() > 0)
        {
            bodyString = full;
        }
        return bodyString;
    }
    
//  /**
//   * Validate the XML File produced. Throw an Exception if the XML is not valid
//   * @pre         fileName != null
//   * @param       String      the name of the file to be checked
//   **/
//  public void validateXML(String fileName) throws Exception
//  {
//      try
//      {
//          errors = new ArrayList();
//          SAXParserFactory factory;
//          SAXParser parser;
//          factory = SAXParserFactory.newInstance();
//          factory.setValidating(true);
//          factory.setNamespaceAware(false);
//          parser = factory.newSAXParser();
//          HandlerBase base = new HandlerBase();
//          System.out.println("Validating "+fileName);
//          FileInputStream fis = new FileInputStream(fileName);
//          problem = null;
//          success = 0;
//          //parser.parse(fis,new HandlerBase());
//          parser.parse("file:///"+this.fileName,new HandlerBase());
//      }
//      catch (SAXParseException e)
//      {
//          e.printStackTrace();
//          add(e);
//          throw new Exception(e.getMessage());
//      }
//      if (printAndResetErrors(fileName))
//      {
//
//      }
//
//  }
    public void add(SAXParseException e) {
        errors.add(e);
    }
    
    public boolean printAndResetErrors(String name) {
        int i;
        int max;
        SAXParseException exception;
        
        max = errors.size();
        if (max == 0) {
            return false;
        }
        for (i = 0; i < max; i++) {
            exception = (SAXParseException) errors.get(i);
            System.err.println(name + ":" +
                                   + exception.getLineNumber() + ":"
                                   + exception.getColumnNumber() + ": "
                                   + exception.getMessage());
        }
        errors.clear();
        return true;
    }
    
    public void error(SAXParseException exception) {
        errors.add(exception);
    }
    
    public void fatalError(SAXParseException exception) {
        errors.add(exception);
    }
    
    public void warning(SAXParseException exception) {
        errors.add(exception);
    }
    
    public void setOutput(boolean state)
    {
        this.output = state;
        if (output)
        {
            System.out.println("LOGGER - INFO - Document will be formated to XML");
        }
        else
        {
            System.out.println("LOGGER - INFO - Document will be sent directly to file");
        }
    }
}

