package opsx.server.util;

import java.io.*;
import java.util.StringTokenizer;

/**
 * @author abrown
 *         Copyright OpsTechnology Nov 12
 * @author 2003
 */
public class XmlPrettyPrinter implements LogPrinter {

//**************************************        PUBLIC STATIC FIELDS         *************************************

    public static final int EOF                  = 65535;
    public static final int INDENT_SIZE          = 4;
    public static final int LINE_WIDTH           = 120;
    public static final int PUSHBACK_BUFFER_SIZE = 500;


//**************************************        PUBLIC METHODS              *************************************


    public String prettyPrint(String input) throws IOException {
        return prettyPrint(new ByteArrayInputStream(input.getBytes()));
    }

    public String prettyPrint(InputStream inputStream) throws IOException  {
        long before = System.currentTimeMillis();
        ByteArrayOutputStream holdingStream = new ByteArrayOutputStream();
        OutputStreamWriter    writer        = new OutputStreamWriter(holdingStream);
        PushbackReader        reader        = new PushbackReader(new InputStreamReader(inputStream), PUSHBACK_BUFFER_SIZE);

        formatXml(reader, writer);

        reader.close();
        long time = System.currentTimeMillis() - before;

        return "timetaken (" + time + ")\n" + new String(holdingStream.toByteArray());
    }

//**************************************        PRIVATE METHODS              *************************************

    private void breakDownOpeningTag(Writer writer, char[] openingTag, int indent) throws IOException {
        StringTokenizer tokenizer = new StringTokenizer(new String(openingTag));
        if (tokenizer.countTokens() == 1) {
            writer.write(openingTag);
            return ;
        }
        String tagName        = tokenizer.nextToken();
        writer.write(tagName);
        writer.write(' ');
        indent += tagName.length() + 1;
        String firstAttribute = tokenizer.nextToken();
        int    numOfQuotes    = getNumOfQuotes(firstAttribute);
        writer.write(firstAttribute);
        while (tokenizer.hasMoreTokens()) {
            String nextToken = tokenizer.nextToken();

            if (numOfQuotes % 2 == 0 && !"/>".equals(nextToken)) {
                writer.write('\n');
                writeIndent(indent, writer);
            }else
                writer.write(' ');
            writer.write(nextToken);
            numOfQuotes += getNumOfQuotes(nextToken);
        }
    }


    private boolean doesLineHaveRoom(int charCount, PushbackReader input) throws IOException {
        char[] block = readBlock(input);
        pushBackBlock(input, block);
        return charCount + block.length < LINE_WIDTH;
    }


    private void formatXml(PushbackReader input, OutputStreamWriter writer)
    throws IOException {

        int     indent                           = 0;
        char[]  nextBlock;
        boolean isNewLineNecessary               = false;
        int     lineLengthAfterOpeningTagWritten;

        while ((nextBlock = readBlock(input)) != null) {

            lineLengthAfterOpeningTagWritten = indent;

            if (isWhitespace(nextBlock)) {
                continue;
            }
            if (isEmptyTag(nextBlock)) {
                writer.write('\n');
                writeIndent(indent, writer);
                breakDownOpeningTag(writer, nextBlock, indent);
                isNewLineNecessary = true;
            }else if (isOpeningTag(nextBlock)) {
                writer.write('\n');
                writeIndent(indent, writer);
                if (isOpeningTagTooLong(nextBlock.length + indent)) {
                    breakDownOpeningTag(writer, nextBlock, indent);
                }else
                    writer.write(nextBlock);
                lineLengthAfterOpeningTagWritten += nextBlock.length;
                indent                           += INDENT_SIZE;
                isNewLineNecessary               = false;
            }else if (isBody(nextBlock)) {
                if (doesLineHaveRoom(lineLengthAfterOpeningTagWritten + nextBlock.length, input))
                    writer.write(nextBlock);
                else {
                    writer.write('\n');
                    writeIndent(indent, writer);
                    writer.write(nextBlock);
                    isNewLineNecessary = true;
                }

            }else if (isClosingTag(nextBlock)) {
                indent = indent >= INDENT_SIZE ?
                        indent -= INDENT_SIZE :
                                indent;
                if (isNewLineNecessary) {
                    writer.write('\n');
                    writeIndent(indent, writer);
                }
                isNewLineNecessary = true;
                writer.write(nextBlock);
            }
            writer.flush();
        }
        writer.write(EOF);
    }


    private int getNumOfQuotes(String s) {
        String newString = s.replaceAll("\"", "");
        return s.length() - newString.length();
    }


    private boolean isBody(char[] block) {
        return "<]".indexOf(block[0]) == -1;
    }


    private boolean isClosingTag(char[] block) {
        if (block.length == 1 && block[0] == '>')
            return true;
        if (block.length > 1 && block[0] == ']' && block[block.length - 1] == '>')
            return true;
        return block.length > 0 && (block[0] == '<' && block[1] == '/');
    }


    private boolean isEmptyTag(char[] block) {
        if (block.length < 4)
            return false;
        return block[block.length - 1] == '>' &&
               block[block.length - 2] == '/';
    }


    private boolean isOpeningTag(char[] block) {
        if (!isClosingTag(block) &&
            !isEmptyTag(block))
            return block.length > 0 && block[0] == '<';
        return false;
    }


    private boolean isOpeningTagTooLong(int length) {
        return length > LINE_WIDTH;
    }


    private boolean isWhitespace(char[] buffer) {
        if (buffer.length == 0)
            return true;
        for (int i = 0; i < buffer.length; i++) {
            if (!isWhitespace(buffer[i]))
                return false;
        }
        return true;
    }


    private boolean isWhitespace(char c) {
        return c == '\t' ||
               c == '\n' ||
               c == '\r' ||
               c == ' ';
    }


    private void pushBackBlock(PushbackReader input, char[] block) throws IOException {
        input.unread(block);
    }


    private char[] readBlock(PushbackReader input) throws IOException {
        boolean isBody = false;

        char nextChar;
        do {
            nextChar = (char) input.read();
        } while (isWhitespace(nextChar) && nextChar != EOF);

        if (nextChar == EOF)
            return null;

        if (nextChar != '<' && nextChar != ']') {
            isBody = true;
        }

        String searchString = isBody ? "<" : "<>";

        CharArrayWriter caw = new CharArrayWriter(50);
        caw.write(nextChar);
        while ((nextChar = (char) input.read()) != EOF && searchString.indexOf(nextChar) == -1) {
            caw.write(nextChar);
        }
        if (((int) nextChar) == EOF || nextChar == '<') {
            char[] tmpBuff = new char[1];
            tmpBuff[0] = nextChar;
            input.unread(tmpBuff);
        }else {
            caw.write(nextChar);
        }
        String s = new String(caw.toCharArray());
        return s.trim().toCharArray();
    }


    private void writeIndent(int indent, Writer writer) throws IOException {
        for (int i = 0; i < indent; i++)
            writer.write(' ');
    }

//*************************************        MAIN METHOD           ***********************************

    public static void main(String[] args) {
        String inputFile  = args[0];
        String outputFile;

        if (args.length > 1) {
            outputFile = args[1];
        }else
            outputFile = inputFile;

        XmlPrettyPrinter printer;
        try {
            printer = new XmlPrettyPrinter();
            InputStream inputStream = new BufferedInputStream(new FileInputStream(inputFile));
            String result = printer.prettyPrint(inputStream);
            System.out.println(result);
            OutputStreamWriter finalWriter = new OutputStreamWriter(new FileOutputStream(new File(outputFile)));
            finalWriter.write(new String(result.getBytes()));
            finalWriter.close();
        } catch (FileNotFoundException fnfe) {
            System.out.println("filename " + inputFile + " can't be found.  Please state your filename");
            System.exit(1);
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

