package y;

import java.awt.*;
import java.io.*;
import java.util.ArrayList;
import java.util.Date;

/**
 * @author Rajiv Shivane
 * @since Dec 24, 2007
 */
public class Parser
{
    private String buddy;
    private String myId;
    private char[] key;
    private InputStream is;

    public static enum StandardColors
    {
        BLACK(0, 0, 0), BLUE(0, 0, 255), TEAL(0, 128, 128), SKY_BLUE_4(238, 243, 246), //EEF3F6
        GREEN(0, 128, 0), MAGENTA(255, 0, 128), PURPLE(128, 0, 128), ORANGE(255, 128, 0),
        RED(255, 0, 0), OLIVE(128, 128, 0);

        private Color color;

        StandardColors(int r, int g, int b)
        {
            color = new Color(r, g, b);
        }

        public Color getColor()
        {
            return color;
        }
    };

    public Parser(String buddyId, String myId, InputStream is)
    {
        this.buddy = buddyId;
        this.myId = myId;
        this.key = myId.toCharArray();
        this.is = is;
    }

    public Record readRecord() throws IOException
    {
        Date date = readDate();
        int unknown1 = readInt();
        if (unknown1 != 0 && unknown1 != 6) {
            Thread.dumpStack();
            System.exit(unknown1);
        }
//        System.out.println("\"" + unknown1 + "\"");
        int source = readInt();
        boolean isFromBuddy = source != 0;
        Record record = new Record(date, isFromBuddy ? buddy : myId, isFromBuddy);

        int mesgLen = readInt();
        InputStream mis = new MessageInputStream(is, key, mesgLen);
        int r;
        StringBuffer buff = new StringBuffer();
        while ((r = mis.read()) != -1) {
            if (r == 0x1b) {
                r = mis.read();
                if (r != 0x5b) {
                    throw badFormatError(r);
                } else {
                    if (buff.length() > 0) {
                        record.addToken(new Token(Token.TokenTypes.TEXT, buff.toString()));
                        buff.setLength(0);
                    }
                    record.addToken(readFontAttribute(mis));
                }
                continue;
            }
            buff.append((char) r);
        }
        if (buff.length() > 0) {
            record.addToken(new Token(Token.TokenTypes.TEXT, buff.toString()));
            buff.setLength(0);
        }

        int unknown2 = readInt();
        if (unknown2 != 0 && unknown2 != 6) {
            Thread.dumpStack();
            System.exit(unknown2);
        }
//        System.out.println("\'" + unknown2 + "\'");
        return record;
    }

    private Token readFontAttribute(InputStream mis)
            throws IOException
    {
        int r = mis.read();
        try {
            switch (r) {
                case '#'/*0x23*/:
                    return readCustomColor(mis);
                case '0'/*0x30*/:
                    return new Token(Token.TokenTypes.UNKNOWN, r);
                case '1'/*0x31*/:
                    return new Token(Token.TokenTypes.FONT, Token.FontFormats.BOLD);
                case '2'/*0x32*/:
                    return new Token(Token.TokenTypes.FONT, Token.FontFormats.ITALIC);
                case '3'/*0x33*/:
                    return readStandardColor(mis);
                case '4'/*0x34*/:
                    return new Token(Token.TokenTypes.FONT, Token.FontFormats.UNDERLINE);
                case 'l'/*0x6c*/:
                    //NOTE: Seems to come only from linux machines.
                    //      See: xxxsurya\20071226*.dat
                    //           cprxxxxxxreddy\20060320*.dat
                    //           navxxxxxnth_r\20060118*.dat
                    return new Token(Token.TokenTypes.BEGIN_LINK, r);
                case 'x'/*0x78*/:
                    r = mis.read();
                    switch (r) {
                        case '0'/*0x30*/:
                            return new Token(Token.TokenTypes.UNKNOWN, r);
                        case '1'/*0x31*/:
                            return new Token(Token.TokenTypes.FONT, Token.FontFormats.UNDO_BOLD);
                        case '2'/*0x32*/:
                            return new Token(Token.TokenTypes.FONT, Token.FontFormats.UNDO_ITALIC);
                        case '4'/*0x34*/:
                            return new Token(Token.TokenTypes.FONT, Token.FontFormats.UNDO_UNDERLINE);
                        case 'l'/*0x6c*/:
                            return new Token(Token.TokenTypes.END_LINK, r);
                        default:
                            throw badFormatError(r);
                    }
                default:
                    throw badFormatError(r);
            }
        } finally {
            if ((r = mis.read()) != 'm') {
                throw badFormatError(r);
            }
        }
    }

    private Token readStandardColor(InputStream mis)
            throws IOException
    {
        StandardColors[] standardColors = StandardColors.values();
        int r = mis.read();
        int idx = r - '0';
        if (idx >= 0 && idx < standardColors.length) {
            return new Token(Token.TokenTypes.STANDARD_COLOR, standardColors[idx]);
        } else {
            throw badFormatError(r);
        }
    }

    private Token readCustomColor(InputStream is) throws IOException
    {
        int r = ((c1(is) << 4) | c1(is));
        int g = ((c1(is) << 4) | c1(is));
        int b = ((c1(is) << 4) | c1(is));
        Color color;
        try {
            color = new Color(r, g, b);
        } catch (IllegalArgumentException e) {
            System.err.println("Bad color: [" + r + ", " + g + ", " + b + "]");
            color = Color.black;
        }
        return new Token(Token.TokenTypes.CUSTOM_COLOR, color);
    }

    private int c1(InputStream is)
            throws IOException
    {
        int v = is.read();
        v ^= '0';
        if (v > 0xF) {
            if (v - 'G' > 0xF) {
                //NOTE: Seems to come when we get messages from a linux machine. See: xxxsurya\20071210xxx.dat
                v -= 'g';
            } else {
                v -= 'G';
            }
        }
        return v;
    }

    private Date readDate() throws IOException
    {
        long time = readInt();
        return new Date(time * 1000);
    }

    private int readInt()
            throws IOException
    {
        int[] dt = new int[4];
        for (int i = dt.length - 1; i >= 0; i--) {
            dt[i] = is.read();
            if (dt[i] == -1) {
                throw new EOFException();
            }
        }
        int time = 0;
        for (int i = 0; i < dt.length; i++) {
            time <<= 8;
            time |= dt[i];
        }
        return time;
    }


    private IOException badFormatError(int r)
    {
        return new IOException("Bad format: " + Integer.toHexString(r));
    }

    private static class MessageInputStream
            extends FilterInputStream
    {
        private char[] key;
        private int mesgLen;
        private int read = 0;

        public MessageInputStream(InputStream source, char[] key, int mesgLen)
        {
            super(source);
            this.key = key;
            this.mesgLen = mesgLen;
        }

        public int read() throws IOException
        {
            if (read == mesgLen) {
                return -1;
            }
            int c = super.read();
            int r = c ^ key[read % key.length];
            read++;
//            System.out.println("("+Integer.toHexString(r)+"/"+Integer.toHexString(c)+")");
            return r;
        }
    }

    public static class Record
    {
        private Date date;
        private String author;
        private boolean isFromBuddy;

        private java.util.List<Token> tokens = new ArrayList<Token>();

        public Record(Date date, String author, boolean fromBuddy)
        {
            this.date = date;
            this.author = author;
            isFromBuddy = fromBuddy;
        }

        public void addToken(Token token)
        {
            tokens.add(token);
        }

        public java.util.List<Token> getTokens()
        {
            return tokens;
        }

        public Date getDate()
        {
            return date;
        }

        public String getAuthor()
        {
            return author;
        }

        public boolean isFromBuddy()
        {
            return isFromBuddy;
        }
    }

    public static class Token
    {
        public static enum TokenTypes
        {
            UNKNOWN, TEXT, FONT, STANDARD_COLOR, CUSTOM_COLOR, BEGIN_LINK, END_LINK
        }

        public static enum FontFormats
        {
            BOLD, ITALIC, UNDERLINE, UNDO_BOLD, UNDO_ITALIC, UNDO_UNDERLINE
        }


        private TokenTypes type;
        private Object value;

        public Token(TokenTypes type, Object value)
        {
            this.type = type;
            this.value = value;
        }

        public TokenTypes getType()
        {
            return type;
        }

        public Object getValue()
        {
            return value;
        }

        public String toString()
        {
            return "{" + value + "}";
        }
    }
}
