package com.taurus.core.util.json;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class JSONParser {

    private String text;
    private int    index = 0;
    private char   ch;

    private Token  token;
    private String stringValue;
    private long   longValue;
    private double doubleValue;

    public JSONParser(String text){
        this.text = text;
        ch = text.charAt(0);
        nextToken();
    }

    public Object parse() {
        if (token == Token.LBRACE) {
            return parseMap();
        }

        if (token == Token.INT) {
            Object value;
            if (this.longValue >= Integer.MIN_VALUE && this.longValue <= Integer.MAX_VALUE) {
                value = (int) this.longValue;
            } else {
                value = this.longValue;
            }

            nextToken();
            return value;
        }

        if (token == Token.DOUBLE) {
            Object value = this.doubleValue;
            nextToken();
            return value;
        }

        if (token == Token.STRING) {
            Object value = this.stringValue;
            nextToken();
            return value;
        }

        if (token == Token.LBRACKET) {
            return parseArray();
        }

        if (token == Token.TRUE) {
            nextToken();
            return true;
        }

        if (token == Token.FALSE) {
            nextToken();
            return false;
        }

        if (token == Token.NULL) {
            nextToken();
            return null;
        }

        throw new IllegalArgumentException("illegal token : " + token);
    }

    public List<Object> parseArray() {
        accept(Token.LBRACKET);
        ArrayList<Object> list = new ArrayList<Object>();

        for (;;) {
            if (token == Token.RBRACKET) {
                break;
            }

            if (token == Token.COMMA) {
                nextToken();
                continue;
            }

            Object item = parse();
            list.add(item);
        }

        accept(Token.RBRACKET);
        return list;
    }

    public Map<String, Object> parseMap() {
        accept(Token.LBRACE);
        Map<String, Object> map = new LinkedHashMap<String, Object>();

        for (;;) {
            if (token == Token.RBRACE) {
                break;
            }

            if (token == Token.COMMA) {
                nextToken();
                continue;
            }

            String key;
            {
                if (token != Token.STRING) {
                    throw new IllegalArgumentException("illegal json, " + token + " : " + text);
                }
                key = this.stringValue;
                nextToken();
            }

            accept(Token.COLON);

            Object value = parse();

            map.put(key, value);
        }

        accept(Token.RBRACE);
        return map;
    }

    void accept(Token token) {
        if (this.token == token) {
            nextToken();
            return;
        }

        throw new IllegalArgumentException("illegal token : " + this.token + ", expect " + token);
    }
    private final static boolean[] whitespaceFlags = new boolean[256];
    static {
        for (int i = 0; i <= 32; ++i) {
            whitespaceFlags[i] = true;
        }
        
        whitespaceFlags[0x1A] = false;
        for (int i = 0x7F; i <= 0xA0; ++i) {
            whitespaceFlags[i] = true;
        }
   
        whitespaceFlags[160] = true; // 特别处理
    }
    public static boolean isWhitespace(char c) {
        return (c <= whitespaceFlags.length && whitespaceFlags[c]) //
               || c == '　'; // Chinese space
    }
    
    final void nextToken() {
        if (index == Integer.MIN_VALUE) {
            token = Token.EOF;
            return;
        }

        for (;;) {
            if (isWhitespace(ch)) {
                nextChar();
                continue;
            }

            if (index >= text.length()) {
                token = Token.EOF;
                return;
            }

            break;
        }

        switch (ch) {
            case '{':
                token = Token.LBRACE;
                nextChar();
                break;
            case '}':
                token = Token.RBRACE;
                nextChar();
                break;
            case '[':
                token = Token.LBRACKET;
                nextChar();
                break;
            case ']':
                token = Token.RBRACKET;
                nextChar();
                break;
            case ',':
                token = Token.COMMA;
                nextChar();
                break;
            case ':':
                token = Token.COLON;
                nextChar();
                break;
            case '"':
                scanString();
                break;
            default:
                if (isDigit(ch) || ch == '-') {
                    scanDigit();
                    return;
                }

                if (text.startsWith("null", index)) {
                    token = Token.NULL;
                    index += 3;
                    nextChar();
                    return;
                }

                if (text.startsWith("true", index)) {
                    token = Token.TRUE;
                    index += 3;
                    nextChar();
                    return;
                }

                if (text.startsWith("false", index)) {
                    token = Token.FALSE;
                    index += 4;
                    nextChar();
                    return;
                }
                throw new IllegalArgumentException("illegal json char : " + ch);
        }
    }

    private void scanDigit() {
        boolean isNegate = false;
        if (ch == '-') {
            isNegate = true;
            nextChar();
        }

        int dotCount = 0;
        StringBuilder digitBuf = new StringBuilder();
        for (;;) {
            digitBuf.append(ch);
            nextChar();

            if (ch == '.') {
                dotCount++;
                digitBuf.append('.');
                nextChar();
                continue;
            }

            if (!isDigit(ch)) {
                break;
            }
        }

        if (dotCount == 0) {
            long longValue = Long.parseLong(digitBuf.toString());
            if (isNegate) {
                longValue = -longValue;
            }
            this.longValue = longValue;
            token = Token.INT;
        } else {
            double doubleValue = Double.parseDouble(digitBuf.toString());
            if (isNegate) {
                doubleValue = -doubleValue;
            }
            this.doubleValue = doubleValue;
            token = Token.DOUBLE;
        }
    }

    private void scanString() {
        nextChar();
        StringBuilder strBuf = new StringBuilder();
        for (;;) {
            if (index >= text.length()) {
                throw new IllegalArgumentException("illegal string : " + strBuf);
            }
            if (ch == '"') {
                nextChar();
                break;
            }

            if (ch == '\\') {
                nextChar();
                if (ch == '"' || ch == '\\' || ch == '/') {
                    strBuf.append(ch);
                } else if (ch == 'n') {
                    strBuf.append('\n');
                } else if (ch == 'r') {
                    strBuf.append('\r');
                } else if (ch == 'b') {
                    strBuf.append('\b');
                } else if (ch == 'f') {
                    strBuf.append('\f');
                } else if (ch == 't') {
                    strBuf.append('\t');
                } else if (ch == 'u') {
                    nextChar();
                    char c1 = ch;
                    nextChar();
                    char c2 = ch;
                    nextChar();
                    char c3 = ch;
                    nextChar();
                    char c4 = ch;
                    int val = Integer.parseInt(new String(new char[] { c1, c2, c3, c4 }), 16);
                    strBuf.append((char) val);
                } else {
                    throw new IllegalArgumentException("illegal string : " + strBuf);
                }
            } else {
                strBuf.append(ch);
            }
            nextChar();
        }
        stringValue = strBuf.toString();
        token = Token.STRING;
    }

    static boolean isDigit(char ch) {
        return ch >= '0' && ch <= '9';
    }

    void nextChar() {
        ++index;
        if (index >= text.length()) {
            index = Integer.MIN_VALUE;
            return;
        }

        ch = text.charAt(index);
    }

    enum Token {
        INT, //
        DOUBLE, //
        STRING, //
        BOOLEAN, //
        TRUE, //
        FALSE, //
        NULL, //
        EOF, //

        LBRACE("{"), //
        RBRACE("}"), //
        LBRACKET("["), //
        RBRACKET("]"), //
        COMMA(","), //
        COLON(":"),

        ;

        public final String name;

        Token(){
            this(null);
        }

        Token(String name){
            this.name = name;
        }
    }
}
