// Copyright (c) 2022 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "pch.h"
#include "json.hpp"
#include <string_view>
#include <cctype>
#include "PiPedalException.hpp"

using namespace pipedal;



uint32_t json_writer::continuation_byte(std::string_view::iterator &p, std::string_view::const_iterator end)
{
    if (p == end)
        throw_encoding_error();
    uint8_t c = *p++;
    if ((c & UTF8_CONTINUATION_MASK) != UTF8_CONTINUATION_BITS)
        throw_encoding_error();
    return c & 0x3F;
}
static char hex(int v)
{
    if (v < 10)
        return (char)('0' + v);
    return (char)('A' + v - 10);
}

void json_writer::throw_encoding_error()
{
    throw std::invalid_argument("Invalid UTF-8 character sequence");
}
void json_writer::write_utf16_char(uint16_t uc)
{
    os << "\\u"
       << hex((int)((uc >> 12) & 0x0F))
       << hex((int)((uc >> 8) & 0x0F))
       << hex((int)((uc >> 4) & 0x0F))
       << hex((int)((uc)&0x0F));
}

void json_writer::write(string_view v,bool enforceValidUtf8Encoding)
{
    // convert to utf-32.
    // convert utf-32 to normalized utf-16.
    // write non-7-bit and unsafe characters as \uHHHH.

    auto p = v.begin();
    os << '"';
    while (p != v.end())
    {
        uint32_t uc;
        uint8_t c = (uint8_t)*p++;
        if ((c & UTF8_ONE_BYTE_MASK) == UTF8_ONE_BYTE_BITS)
        {
            uc = c;
        }
        else
        {
            uint32_t c2 = continuation_byte(p, v.end());

            if ((c & UTF8_TWO_BYTES_MASK) == UTF8_TWO_BYTES_BITS)
            {
                uint32_t c1 = c & (uint32_t)(~UTF8_TWO_BYTES_MASK);
                if (c1 <= 1 && enforceValidUtf8Encoding)
                {
                    // overlong encoding.
                    throw_encoding_error();
                }
                uc = (c1 << 6) | c2;
            }
            else
            {
                uint32_t c3 = continuation_byte(p, v.end());

                if ((c & UTF8_THREE_BYTES_MASK) == UTF8_THREE_BYTES_BITS)
                {
                    uint32_t c1 = c & (uint32_t)~UTF8_THREE_BYTES_MASK;
                    if (c1 == 0 && c2 < 0x20 && enforceValidUtf8Encoding)
                    {
                        // overlong encoding.
                        throw_encoding_error();
                    }

                    uc = (c1) << 12 | (c2 << 6) | c3;
                }
                else
                {
                    uint32_t c4 = continuation_byte(p, v.end());
                    if ((c & UTF8_FOUR_BYTES_MASK) == UTF8_FOUR_BYTES_BITS)
                    {
                        uint32_t c1 = c & (uint32_t)~UTF8_FOUR_BYTES_MASK;
                        if (c1 == 0 && c2 < 0x10 && enforceValidUtf8Encoding)
                        {
                            // overlong encoding.
                            throw_encoding_error();
                        }
                        uc  = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4;
                    } else {
                        // outside legal UCS range. 
                        throw_encoding_error();
                    }
                }
            }
        }
        if ((uc >= UTF16_SURROGATE_1_BASE && uc <= UTF16_SURROGATE_1_BASE + UTF16_SURROGATE_MASK) || (uc >= UTF16_SURROGATE_2_BASE && uc <= UTF16_SURROGATE_2_BASE + UTF16_SURROGATE_MASK))
        {
            // MUST not encode UTF16 surrogates in UTF8.
            throw_encoding_error();
        }

        if (uc == '"' || uc == '\\')
        {
            os << (char)'\\';
            os << (char)uc;
        }
        else if (uc >= 0x20 && uc < 0x80)
        {
            os << (char)uc;
        } 
        else if (uc == '\r')
        {
            os << "\\r";
        }
        else if (uc == '\n')
        {
            os << "\\n";
        }
        else if (uc == '\t')
        {
            os << "\\t";
        }
        else if (uc < 0x10000ul)
        {
            write_utf16_char((uint16_t)uc);
        }
        else
        {
            // write UTF-16 surrogate pair.
            uc -= 0x10000;
            
            uint16_t s1 = (uint16_t)(UTF16_SURROGATE_1_BASE + ((uc >> 10) & 0x3FFu));
            uint16_t s2 = (uint16_t)(UTF16_SURROGATE_2_BASE + (uc & 0x03FFu));
            // surrogate pair.
            write_utf16_char(s1);
            write_utf16_char(s2);
        }
    }
    os << '"';
}

void json_writer::indent()
{
    if (!compressed)
    {
        for (int i = 0; i < indent_level; ++i)
        {
            os << " ";
        }
    }
}

void json_writer::start_object()
{
    os << "{" << CRLF;
    indent_level += TAB_SIZE;
}
void json_writer::end_object()
{
    indent_level -= TAB_SIZE;
    os << CRLF;
    indent();
    os << "}";
}

void json_writer::start_array()
{
    indent();
    os << "[" << CRLF;
    indent_level += TAB_SIZE;
}
void json_writer::end_array()
{
    indent_level -= TAB_SIZE;
    indent();
    os << "]" << CRLF;
}


void json_reader::skip_whitespace()
{
    char c;
    while (true)
    {
        int ic = is_.peek();
        if (ic == -1)
            break;
        if (is_whitespace((char)ic))
        {
            c = get();
        }
        else if (ic == '/')
        {
            get();
            int c2 = is_.peek();
            if (c2 == '/') {
                // skip to end of line.
                get();
                while (true) {
                    c2 = is_.peek();
                    if (c2 == '\r' || c2 == '\n')
                    {
                        get(); // and continue.
                        break;
                    }
                    if (c2 == -1) 
                    {
                        break;
                    }
                    get();
                }
            } else if (c2 == '*') {
                get();
                int level = 1;
                while (true)
                {
                    c = get();
                    if (c == '*' && is_.peek() == '/')
                    {
                        get();
                        if (--level == 0)
                        {
                            break;
                        }
                    }
                    if (c == '/' && is_.peek() == '*')
                    {
                        get();
                        ++level;
                    }
                }
            } else {

                throw_format_error();
            }
        } else
        {

            break;
        }
    }
}

std::string json_reader::read_string()
{
    // To completely normalize UTF-32 values we must covert to UTF-16, resolve surrogate pairs, and then convert UTF-32 to UTF-8.


    skip_whitespace();
    char c;
    char startingCharacter;
    startingCharacter = get();
    if (startingCharacter != '\'' && startingCharacter != '\"')
    {
        throw_format_error();
    }
    std::stringstream s;

    while (true)
    {
        c = get();
        if (c == startingCharacter)
        {
            if (is_.peek() == startingCharacter) //  "" -> "
            {
                get();
                s.put(c);
            } else {
                break;
            }
        }
        if (c != '\\')
        {
            s << (char)c;
        }
        else
        {
            c = get();
            switch (c)
            {
            case '"':
            case '\\':
            default:
                s << c;
                break;
            case 'r':
                s << '\r';
                break;
            case 'b':
                s << '\b';
                break;
            case 'f':
                s << '\f';
                break;
            case 'n':
                s << '\n';
                break;
            case 't':
                s << '\t';
                break;
            case 'u':
            {
                uint32_t uc = read_u_escape();
                if (uc >= UTF16_SURROGATE_1_BASE && uc <= UTF16_SURROGATE_1_BASE + UTF16_SURROGATE_MASK)
                {
                    // MUST be a UTF16_SURROGATE 2 to be legal.
                    c = get();
                    if (c != '\\')
                        throw_format_error("Invalid UTF16 surrogate pair");
                    c = get();
                    if (c != 'u')
                        throw_format_error("Invalid UTF16 surrogate pair");
                    uint16_t uc2 = read_u_escape();
                    if (uc2 < UTF16_SURROGATE_2_BASE || uc2 > UTF16_SURROGATE_2_BASE + UTF16_SURROGATE_MASK)
                    {
                        throw_format_error("Invalid UTF16 surrogate pair");
                    }
                    uc = ((uc & UTF16_SURROGATE_MASK) << 10) + (uc2 & UTF16_SURROGATE_MASK) + 0x10000U;
                }
                HtmlHelper::utf32_to_utf8_stream(s, uc);
            }
            break;
            }
        }
    }
    return s.str();
}
uint16_t json_reader::read_hex()
{
    char c;
    c = get();
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    throw_format_error("Invalid \\u escape character");
    return 0;
}

uint16_t json_reader::read_u_escape()
{
    uint16_t c1 = read_hex();
    uint16_t c2 = read_hex();
    uint16_t c3 = read_hex();
    uint16_t c4 = read_hex();

    return (c1 << 12) + (c2 << 8) + (c3 << 4) + c4;
}

bool json_reader::is_complete()
{
    skip_whitespace();
    return is_.peek() == -1;
}

void json_reader::consumeToken(const char*expectedToken, const char*errorMessage)
{
    skip_whitespace();
    
    const char*p = expectedToken;
    while (*p != '\0')
    {
        char expectedChar = *p++;
        int c = is_.get();
        if (expectedChar != c) {
            this->throw_format_error(errorMessage);
        }
    }
}

void json_reader::consume(char expected)
{
    skip_whitespace();
    char c;
    c = get();
    if (c != expected)
    {
        std::stringstream s;
        s << "Expecting '" << expected << "'";
        throw_format_error(s.str().c_str());
    }
}

void json_reader::skip_property()
{
    skip_whitespace();
    int c = is_.peek();
    switch (c)
    {
    case -1:
        throw_format_error("Premature end of file.");
        break;
    case '[':
        skip_array();
        break;
    case '{':
        skip_object();
        break;
    case '\"':
        skip_string();
        break;
    case 't':
    case 'f':
        read_boolean();
        break;
    case 'n':
        read_null();
        break;
    default:
        skip_number();
        break;
    }
}
void json_reader::skip_string()
{
    consume('"');
    while (true)
    {
        int c;
        c = get();
        if (c == -1) throw_format_error("Premature end of file.");

        if (c == '\"')
        {
            if (peek() == '\"')
            {
                get();
            } else {
                break;
            }
        }
        if (c == '\\')
        {
            c = get(); // all of standard escapes,  enough to get past \u
        }
    }
}

void json_reader::skip_number()
{
    skip_whitespace();
    int c;
    if (is_.peek() == '-')
    {
        get();
    }
    if (!std::isdigit(is_.peek()))
    {
        throw_format_error("Expecting a number.");
    }
    while (std::isdigit(is_.peek()))
    {
        get();
    }
    if (is_.peek() == '.')
    {
        get();
    }
    while (std::isdigit(is_.peek()))
    {
        get();
    }
    c = is_.peek();
    if (c == 'e' || c == 'E')
    {
        get();
        c = is_.peek();
        if (c == '+' || c == 'i')
        {
            get();
        }
        while (std::isdigit(is_.peek()))
        {
            get();
        }

    }
}
void json_reader::skip_array()
{
    int c;
    consume('[');

    while (true)
    {
        c = peek();
        if (c == -1) throw_format_error("Premature end of file.");

        if (c == ']')
        {
            c = get();
            break;
        }
        skip_property();
        skip_whitespace();
        if (is_.peek() == ',')
        {
            c = get();
        }
    }
}

void json_reader::skip_object()
{
    int c;
    consume('{');
    while (true)
    {
        c = peek();
        if (c == -1) throw_format_error("Premature end of file.");
        if (c == '}')
        {
            c = get();
            break;
        }
        skip_string(); // name.
        consume(':');
        skip_object();
        if (peek() == ',')
        {
            consume(',');
        }
    }
}

void json_reader::read_object_start()
{
    skip_whitespace();

    char c;
    c = get();
    if (c != '{')
        throw_format_error();
}

std::string json_reader::readToken()
{
    skip_whitespace();

    std::stringstream s;
    while (true)
    {
        int ic = is_.peek();
        if (ic == -1)
            break;
        char c = (char)ic;
        if (!std::isalpha(c))
            break;
        is_.get();
        s << c;
    }
    return s.str();

}

void json_reader::read_null()
{
    std::string s = readToken();
    if (s != "null")
    {
        throw_format_error("Format error. Expecting 'null'.");

    }
}
bool json_reader::read_boolean()
{
    std::string ss = readToken();
    if (ss == "true")
        return true;
    if (ss == "false")
        return false;

    throw_format_error("Format error. Expectiong 'true' or 'false'");
    return false;
}

void json_reader::throw_format_error(const char*error)
{
    std::stringstream s;
    s << error;
    s << ", near: '";
    skip_whitespace();
    if (is_.peek() == -1) {
        s << "<eof>";
    } else {
        for (int i = 0; i < 40; ++i)
        {
            int c = get();
            if (c == -1) break;
            if (c == '\r') {
                s << "\\r";
            } else if (c == '\n')
            {
                s << "\\n";
            } else {
                s << (char)c;
            }
        }
    }
    s << "'.";
    std::string message = s.str();
    throw PiPedalException(message);

}
