#include "qjson.h"

inline QChar readOne(QTextStream &in) {
    QChar ch;
    in >> ch;
    return ch;
}
JValue JParser::readValue() {
    if(ch=='{') {
        JObj obj;
        while(true) {
            do skipSpace(); //ch有三种可能
            while(ch==',');
            if(ch=='}') return obj;
            QString key;
            if(ch == '"') key = readStr();
            else if(ch!='n' || in.read(3)!="ull") throw QString("Unexpected char ")+ch+" (code "+ch+"): was expecting double-quote to start field name or null";
            skipSpace();
            if(ch != ':') throw QString("Unexpected char ")+ch+" (code "+ch+"): was expecting a colon to separate field name and value";
            skipSpace();
            obj.insert(key, readValue());
            skipSpace(); //ch有两种可能
            if(ch=='}') return obj;
            if(ch!=',') throw QString("Unexpected char ")+ch+"' (code "+ch+"): was expecting } to end Object or comma to separate Object entries";
        }
    }
    if(ch=='[')  {
        JArray list;
        while(true) {
            do skipSpace(); //ch有三种可能
            while(ch==',');
            if(ch==']') return list;
            list->push_back(readValue());
            skipSpace(); //ch有两种可能
            if(ch==']') return list;
            if(ch!=',') throw QString("Unexpected char ")+ch+" (code "+ch+"): was expecting ] to end Array or comma to separate Array entries";
        }
    }
    if(ch=='"') return readStr();
    if((ch>='0' && ch<='9') || ch=='-') {
        QString buf;
        buf += ch;
        bool isInt = true;
        while(0 != (ch = readOne(in))) {
            if((ch>'*' && ch<':' && ch!=',' && ch!='/') || ch=='e' || ch=='E') {
                buf.append(ch);
                if(isInt && ch=='.') isInt = false;
            } else {
                bk = ch;
                break;
            }
        }
        bool ok;
        if(isInt) {
            auto num = buf.toLongLong(&ok);
            if(! ok) throw "Illegal number: "+buf;
            return num;
        } else {
            auto num = buf.toDouble(&ok);
            if(! ok) throw "Illegal number: "+buf;
            return num;
        }
    }
    if(ch=='n') {
        if(in.read(3)=="ull") return JValue();
        else throw "Unexpected char n: expected null";
    }
    if(ch=='t') {
        if(in.read(3)=="rue") return true;
        else throw "Unexpected char t: expected true";
    }
    if(ch=='f') {
        if(in.read(4)=="alse") return false;
        else throw "Unexpected char f: expected false";
    }
    throw QString("Unexpected char ")+ch+" (code "+ch+"): expected {}, [], \"string\", number, null, true or false";
}
QString JParser::readStr() {
    QString buf;
    while((ch = readOne(in)) != '"') {
        if(ch==0) throw "Unexpected end-of-input: was expecting closing quote for string";
        if(ch=='\\') {
            in>>ch;
            if(ch==0) throw "Unexpected end-of-input in char escape sequence";
            if(ch=='"' || ch=='\\' || ch=='/') buf.append(ch);
            else if(ch=='n') buf.append('\n');
            else if(ch=='r') buf.append('\r');
            else if(ch=='t') buf.append('\t');
            else if(ch=='f') buf.append('\f');
            else if(ch=='b') buf.append('\b');
            else if(ch=='u') {
                auto hex = in.read(4);
                if(hex.size()<4) throw "Unexpected end-of-input in char escape sequence";
                bool ok;
                buf.append(hex.toUShort(&ok, 16));
                if(! ok) throw "Illegal hex-digits in char escape sequence: \\u"+hex;
            } else throw QString("Unrecognized char-escape ")+ch+" (code "+ch+")";
        } else buf.append(ch);
    }
    return buf;
}
void JParser::skipSpace() {
    if(bk.unicode()) {
        bk = QChar::Null;
        if(! ch.isSpace()) return;
    }
    in.skipWhiteSpace();
    in >> ch;
    if(ch==0) throw "Unexpected end-of-input";
}

void JOut::write(const JValue &value) {
    if(value.type==JValue::Null) out << "null";
    else if(value.type==JValue::Str) writeStr(value.toStr());
    else if(value.type==JValue::Obj) writeMap(value.toObj());
    else if(value.type==JValue::Array) writeList(value.toArray());
    else out << value.toStr();
    out.flush();
}
void JOut::writeStr(const QString &str) {
    out << '"';
    QChar ch;
    for(int i=0; i<str.length(); i++) {
        ch = str[i];
        if(ch=='"'||ch=='\\') out<<'\\'<<ch;
        else if(ch < ' ') {
            if(ch=='\n') out<<"\\n";
            else if(ch=='\r') out<<"\\r";
            else if(ch=='\t') out<<"\\t";
            else if(ch=='\f') out<<"\\f";
            else if(ch=='\b') out<<"\\b";
            else {
                out<<"\\u00";
                auto aa = QString::number(ch.unicode(), 16);
                if(aa.size()==1) out<<'0';
                out<<aa;
            }
        } else out << ch;
    }
    out << '"';
}
void JOut::writeMap(const JObj &map) {
    out << '{';
    if(! map.empty()) {
        if(indent.size()) {
            out << '\n';
            cnt++;
            for(int c=0; c<cnt; c++) out << indent;
        }
        bool addSep = false;
        for(const auto &pair : map) {
            if(addSep) {
                out << ',';
                if(indent.size()) {
                    out << '\n';
                    for(int c=0; c<cnt; c++) out << indent;
                }
            }
            else addSep = true;
            out << '"' << pair.first << "\":";
            if(indent.size()) out << ' ';
            write(pair.second);
        }
        if(indent.size()) {
            out << '\n';
            cnt--;
            for(int c=0; c<cnt; c++) out << indent;
        }
    }
    out << '}';
}
void JOut::writeList(const JArray &vals) {
    out << '[';
    auto iter = vals.begin();
    auto end = vals.end();
    if(iter!=end) write(*iter++);
    while(iter!=end) {
        out << ',';
        if(indent.size()) out << ' ';
        write(*iter++);
    }
    out << ']';
}