#include "confparser.hh"
#if USE_ICONV
 #include "ctcset.hh"
#endif

//=======================================================================
// Abstract input stream
//=======================================================================

class ConfParser::CharIStream
{
 public:
    CharIStream(FILE *f);

    bool good();

    char32_t peekChar() const;
    char32_t getChar();

    const std::u32string getLine();

    bool equal(char32_t c1, char c2) const;
    bool equal(char32_t c1, char32_t c2) const { return c1==c2; }
    bool isSpace(char32_t c) const;
    bool isDigit(char32_t c) const;
    char32_t toUpper(char32_t c) const;

    void skipWS();

    // Convert wstring to std::string
    std::string toString(const std::u32string& s) const { return WstrToAsc(s); }

    unsigned getLineNumber() const;

 private:
    wstringIn conv;
    FILE *fp;
    std::u32string cache;
    std::size_t cacheptr;
    char32_t getC();

    char32_t nextChar;
    unsigned line;
 private:
    const CharIStream& operator=(const CharIStream&);
    CharIStream(const CharIStream&);
};

ConfParser::CharIStream::CharIStream(FILE *f):
#if USE_ICONV
    conv(getcharset()),
#else
    conv(),
#endif
    fp(f),
    cache(), cacheptr(0),
    nextChar(getC()), line(1)
{
}

char32_t ConfParser::CharIStream::getC()
{
    for(;;)
    {
        if(cacheptr < cache.size())
        {
            char32_t result = cache[cacheptr++];
            return result;
        }

        cache.clear();
        cacheptr = 0;
        while(cache.empty())
        {
            char Buf[512];
            size_t n = fread(Buf, 1, sizeof Buf, fp);
            if(n == 0) break;
            cache += conv.puts(std::string(Buf, n));
        }
        // So now cache may be of arbitrary size.
        if(cache.empty()) return (char32_t)EOF;
    }
}

bool ConfParser::CharIStream::good()
{
    if(!cache.empty()) return true;
    if(nextChar != (char32_t)EOF) return true;
    return false;
}

char32_t ConfParser::CharIStream::peekChar() const
{
    return nextChar;
}

char32_t ConfParser::CharIStream::getChar()
{
    char32_t retval = nextChar;
    nextChar = getC();
    if(retval == U'\n') ++line;
    return retval;
}

const std::u32string ConfParser::CharIStream::getLine()
{
    std::u32string result;
    for(;;)
    {
        if(nextChar == U'\r') { getChar(); continue; }
        if(nextChar == U'\n') { getChar(); break; }
        result += getChar();
    }
    ++line;
    return result;
}

bool ConfParser::CharIStream::equal(char32_t c1, char c2) const
{
    return c1 == (unsigned char)c2;
}

bool ConfParser::CharIStream::isSpace(char32_t c) const
{
    return c == U' ' || c == U'\t' || c == U'\r' || c == U'\n';
}

bool ConfParser::CharIStream::isDigit(char32_t c) const
{
    return c >= U'0' && c <= U'9';
}

char32_t ConfParser::CharIStream::toUpper(char32_t c) const
{
    if(c < U'a') return c;
    if(c > U'z') return c;
    return c + U'A' - U'a';
}

void ConfParser::CharIStream::skipWS()
{
    while(isSpace(nextChar)) getChar();
}

unsigned ConfParser::CharIStream::getLineNumber() const
{
    return line;
}

//=======================================================================
// ConfParser
//=======================================================================

const ConfParser::Section&
ConfParser::operator[](const std::string& sectionName) const
{
    SecMap::const_iterator iter = sections.find(sectionName);
    if(iter == sections.end())
    {
        throw invalid_section(sectionName);
    }

    return iter->second;
}


const ConfParser::Field&
ConfParser::Section::operator[](const std::string& fieldName) const
{
    FieldMap::const_iterator iter = fields.find(fieldName);
    if(iter == fields.end())
    {
        throw invalid_field(SectName, fieldName);
    }
    return iter->second;
}

unsigned ConfParser::Field::IField() const
{
    if(!elements.empty()) return elements[0].IField;
    return 0;
}

double ConfParser::Field::DField() const
{
    if(!elements.empty()) return elements[0].DField;
    return 0;
}

const std::u32string& ConfParser::Field::SField() const
{
    static const std::u32string empty;
    if(!elements.empty()) return elements[0].SField;
    return empty;
}

void ConfParser::Field::append(const Field& f)
{
    elements.insert(elements.end(), f.elements.begin(), f.elements.end());
}



bool ConfParser::ParseField(CharIStream& is, Field& field, bool mergeStrings)
{
    while(true)
    {
        char32_t c = is.getChar();
        if(!is.good()) return false;
        if(is.equal(c, '\n')) return true;
        if(is.isSpace(c)) continue;

        if(is.equal(c, '"'))
        {
            Field::Element element;
            while(true)
            {
                c = is.getChar();
                if(!is.good()) return false;
                if(is.equal(c, '\\'))
                {
                    c = is.getChar();
                    if(is.equal(c, 'n')) c = U'\n';
                    else if(is.equal(c, 't')) c = U'\t';
                    else if(is.equal(c, 'r')) c = U'\r';
                }
                else
                    if(is.equal(c, '"')) break;
                element.SField += c;
            }
            if(mergeStrings && !field.elements.empty() &&
               !field.elements.back().SField.empty())
            {
                field.elements.back().SField += element.SField;
            }
            else
            {
                field.elements.push_back(element);
            }
        }
        else if(is.isDigit(c) || is.equal(c, '$'))
        {
            bool isHex = false;
            if(is.equal(c, '0') && is.equal(is.peekChar(), U'x'))
            {
                is.getChar(); c = is.getChar();
                isHex = true;
            }
            else if(is.equal(c, '$'))
            {
                c = is.getChar();
                isHex = true;
            }

            Field::Element element;
            element.IField = 0;
            element.DField = 0;
            while(true)
            {
                unsigned value = is.toUpper(c)-U'0';
                if(isHex && value > 9) value -= U'A'-U'0'-10;
                element.IField *= isHex ? 16 : 10;
                element.IField += value;

                c = is.toUpper(is.peekChar());
                if(is.isDigit(c) || (isHex && c>=U'A' && c<=U'F'))
                    c = is.getChar();
                else
                    break;
            }
            element.DField = element.IField;
            if(is.peekChar() == U'%')
            {
                element.DField /= 100.0;
                is.getChar();
            }
            field.elements.push_back(element);
        }
        else if(is.equal(c, 't') &&
                is.equal(is.getChar(), U'r') &&
                is.equal(is.getChar(), U'u') &&
                is.equal(is.getChar(), U'e'))
        {
            Field::Element element;
            element.IField = 1;
            element.DField = 1;
            field.elements.push_back(element);
        }
        else if(is.equal(c, 'f') &&
                is.equal(is.getChar(), U'a') &&
                is.equal(is.getChar(), U'l') &&
                is.equal(is.getChar(), U's') &&
                is.equal(is.getChar(), U'e'))
        {
            Field::Element element;
            element.IField = 0;
            element.DField = 0;
            field.elements.push_back(element);
        }
        else
        {
            fprintf(stderr, "Syntax error in config line %u, expected a value but got '%c'\n",
                is.getLineNumber(),
                (char)c);
            return false;
        }
    }
}


void ConfParser::ParseSection(CharIStream& is, const std::string& secName)
{
    Section section;

    while(true)
    {
        is.skipWS();
        if(!is.good()) break;

        std::u32string fieldNameString;

        char32_t c = is.peekChar();

        // Section name?
        if(is.equal(c, '[')) break;

        // Comment line:
        if(is.equal(c, '#'))
        {
            is.getChar();
            is.getLine();
            continue;
        }

        while(true)
        {
            c = is.peekChar();
            if(is.isSpace(c) || is.equal(c, '=')) break;
            fieldNameString += is.getChar();
            if(!is.good()) return;
        }

        std::string fieldName = is.toString(fieldNameString);

        if(fieldName.empty())
        {
            fprintf(stderr, "Syntax error in config line %u, expected field name but got '%c'\n",
                is.getLineNumber(), (char)c);
            continue;
        }

        Field field;

        is.skipWS();
        c = is.peekChar();
        if(is.equal(c, '='))
        {
            is.getChar();
            if(!ParseField(is, field, true)) continue;
            section.fields[fieldName] = field;
        }
        else
        {
            if(!ParseField(is, field, false)) continue;
            section.fields[fieldName].append(field);
        }
    }

    section.SectName = secName;
    sections[secName] = section;
}


void ConfParser::Parse(FILE *fp)
{
    CharIStream is(fp);
    while(true)
    {
        is.skipWS();
        char32_t c = is.getChar();

        if(!is.good()) break;

        // Comment line:
        if(is.equal(c, '#'))
        {
            is.getLine();
            continue;
        }

        // Expect section name:
        if(is.equal(c, '['))
        {
            std::u32string secName;
            while(true)
            {
                c = is.getChar();
                if(is.equal(c, ']')) break;
                secName += c;
            }
            ParseSection(is, is.toString(secName));
        }
        else
        {
            fprintf(stderr, "Syntax error in config line %u, expected a section marker\n",
                is.getLineNumber());
        }
    }
}

void ConfParser::Clear()
{
    sections.clear();
}
