// Copyright (c) 2014-2021 Thomas Fussell
//
// 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
//
// @license: http://www.opensource.org/licenses/mit-license.php
// @author: see AUTHORS file

#include <detail/header_footer/header_footer_code.hpp>
//#include <detail/numeric_utils.hpp>

namespace xlnt {
namespace detail {

std::array<xlnt::optional<xlnt::rich_text>, 3> decode_header_footer(const std::string &hf_string, const number_serialiser &serialiser)
{
    std::array<xlnt::optional<xlnt::rich_text>, 3> result;

    if (hf_string.empty())
    {
        return result;
    }

    enum class hf_code
    {
        left_section, // &L
        center_section, // &C
        right_section, // &R
        current_page_number, // &P
        total_page_number, // &N
        font_size, // &#
        text_font_color, // &KRRGGBB or &KTTSNN
        text_strikethrough, // &S
        text_superscript, // &X
        text_subscript, // &Y
        date, // &D
        time, // &T
        picture_as_background, // &G
        text_single_underline, // &U
        text_double_underline, // &E
        workbook_file_path, // &Z
        workbook_file_name, // &F
        sheet_tab_name, // &A
        add_to_page_number, // &+
        subtract_from_page_number, // &-
        text_font_name, // &"font name,font type"
        bold_font_style, // &B
        italic_font_style, // &I
        outline_style, // &O
        shadow_style, // &H
        text // everything else
    };

    struct hf_token
    {
        hf_code code = hf_code::text;
        std::string value;
    };

    std::vector<hf_token> tokens;
    std::size_t position = 0;

    while (position < hf_string.size())
    {
        hf_token token;

        auto next_ampersand = hf_string.find('&', position + 1);
        token.value = hf_string.substr(position, next_ampersand - position);
        auto next_position = next_ampersand;

        if (hf_string[position] == '&')
        {
            token.value.clear();
            next_position = position + 2;
            auto first_code_char = hf_string[position + 1];

            if (first_code_char == '"')
            {
                auto end_quote_index = hf_string.find('"', position + 2);
                next_position = end_quote_index + 1;

                token.value = hf_string.substr(position + 2, end_quote_index - position - 2); // remove quotes
                token.code = hf_code::text_font_name;
            }
            else if (first_code_char == '&')
            {
                token.value = "&&"; // escaped ampersand
            }
            else if (first_code_char == 'L')
            {
                token.code = hf_code::left_section;
            }
            else if (first_code_char == 'C')
            {
                token.code = hf_code::center_section;
            }
            else if (first_code_char == 'R')
            {
                token.code = hf_code::right_section;
            }
            else if (first_code_char == 'P')
            {
                token.code = hf_code::current_page_number;
            }
            else if (first_code_char == 'N')
            {
                token.code = hf_code::total_page_number;
            }
            else if (std::string("0123456789").find(hf_string[position + 1]) != std::string::npos)
            {
                token.code = hf_code::font_size;
                next_position = hf_string.find_first_not_of("0123456789", position + 1);
                token.value = hf_string.substr(position + 1, next_position - position - 1);
            }
            else if (first_code_char == 'K')
            {
                if (hf_string[position + 4] == '+' || hf_string[position + 4] == '-')
                {
                    token.value = hf_string.substr(position + 2, 5);
                    next_position = position + 7;
                }
                else
                {
                    token.value = hf_string.substr(position + 2, 6);
                    next_position = position + 8;
                }

                token.code = hf_code::text_font_color;
            }
            else if (first_code_char == 'S')
            {
                token.code = hf_code::text_strikethrough;
            }
            else if (first_code_char == 'X')
            {
                token.code = hf_code::text_superscript;
            }
            else if (first_code_char == 'Y')
            {
                token.code = hf_code::text_subscript;
            }
            else if (first_code_char == 'D')
            {
                token.code = hf_code::date;
            }
            else if (first_code_char == 'T')
            {
                token.code = hf_code::time;
            }
            else if (first_code_char == 'G')
            {
                token.code = hf_code::picture_as_background;
            }
            else if (first_code_char == 'U')
            {
                token.code = hf_code::text_single_underline;
            }
            else if (first_code_char == 'E')
            {
                token.code = hf_code::text_double_underline;
            }
            else if (first_code_char == 'Z')
            {
                token.code = hf_code::workbook_file_path;
            }
            else if (first_code_char == 'F')
            {
                token.code = hf_code::workbook_file_name;
            }
            else if (first_code_char == 'A')
            {
                token.code = hf_code::sheet_tab_name;
            }
            else if (first_code_char == '+')
            {
                token.code = hf_code::add_to_page_number;
            }
            else if (first_code_char == '-')
            {
                token.code = hf_code::subtract_from_page_number;
            }
            else if (first_code_char == 'B')
            {
                token.code = hf_code::bold_font_style;
            }
            else if (first_code_char == 'I')
            {
                token.code = hf_code::italic_font_style;
            }
            else if (first_code_char == 'O')
            {
                token.code = hf_code::outline_style;
            }
            else if (first_code_char == 'H')
            {
                token.code = hf_code::shadow_style;
            }
        }

        position = next_position;
        tokens.push_back(token);
    }

    const auto parse_section = [&tokens, &result, &serialiser](hf_code code) {
        std::vector<hf_code> end_codes{hf_code::left_section, hf_code::center_section, hf_code::right_section};
        end_codes.erase(std::find(end_codes.begin(), end_codes.end(), code));

        std::size_t start_index = 0;

        while (start_index < tokens.size() && tokens[start_index].code != code)
        {
            ++start_index;
        }

        if (start_index == tokens.size())
        {
            return;
        }

        ++start_index; // skip the section code
        std::size_t end_index = start_index;

        while (end_index < tokens.size()
            && std::find(end_codes.begin(), end_codes.end(), tokens[end_index].code) == end_codes.end())
        {
            ++end_index;
        }

        xlnt::rich_text current_text;
        xlnt::rich_text_run current_run;

        // todo: all this nice parsing and the codes are just being turned back into text representations
        // It would be nice to create an interface for the library to read and write these codes

        for (auto i = start_index; i < end_index; ++i)
        {
            const auto &current_token = tokens[i];

            if (current_token.code == hf_code::text)
            {
                current_run.first = current_run.first + current_token.value;
                continue;
            }

            if (!current_run.first.empty())
            {
                current_text.add_run(current_run);
                current_run = xlnt::rich_text_run();
            }

            switch (current_token.code)
            {
            case hf_code::text: {
                break; // already handled above
            }

            case hf_code::left_section: {
                break; // used below
            }

            case hf_code::center_section: {
                break; // used below
            }

            case hf_code::right_section: {
                break; // used below
            }

            case hf_code::current_page_number: {
                current_run.first = current_run.first + "&P";
                break;
            }

            case hf_code::total_page_number: {
                current_run.first = current_run.first + "&N";
                break;
            }

            case hf_code::font_size: {
                if (!current_run.second.is_set())
                {
                    current_run.second = xlnt::font();
                }

                current_run.second.get().size(serialiser.deserialise(current_token.value));

                break;
            }

            case hf_code::text_font_color: {
                if (current_token.value.size() == 6)
                {
                    if (!current_run.second.is_set())
                    {
                        current_run.second = xlnt::font();
                    }

                    current_run.second.get().color(xlnt::rgb_color(current_token.value));
                }

                break;
            }

            case hf_code::text_strikethrough: {
                break;
            }

            case hf_code::text_superscript: {
                break;
            }

            case hf_code::text_subscript: {
                break;
            }

            case hf_code::date: {
                current_run.first = current_run.first + "&D";
                break;
            }

            case hf_code::time: {
                current_run.first = current_run.first + "&T";
                break;
            }

            case hf_code::picture_as_background: {
                current_run.first = current_run.first + "&G";
                break;
            }

            case hf_code::text_single_underline: {
                if (!current_run.second.is_set())
                {
                    current_run.second = xlnt::font();
                }
                current_run.second.get().underline(font::underline_style::single);
                break;
            }

            case hf_code::text_double_underline: {
                break;
            }

            case hf_code::workbook_file_path: {
                current_run.first = current_run.first + "&Z";
                break;
            }

            case hf_code::workbook_file_name: {
                current_run.first = current_run.first + "&F";
                break;
            }

            case hf_code::sheet_tab_name: {
                current_run.first = current_run.first + "&A";
                break;
            }

            case hf_code::add_to_page_number: {
                break;
            }

            case hf_code::subtract_from_page_number: {
                break;
            }

            case hf_code::text_font_name: {
                auto comma_index = current_token.value.find(',');
                auto font_name = current_token.value.substr(0, comma_index);

                if (!current_run.second.is_set())
                {
                    current_run.second = xlnt::font();
                }

                if (font_name != "-")
                {
                    current_run.second.get().name(font_name);
                }

                if (comma_index != std::string::npos)
                {
                    auto font_type = current_token.value.substr(comma_index + 1);

                    if (font_type == "Bold")
                    {
                        current_run.second.get().bold(true);
                    }
                    else if (font_type == "Italic")
                    {
                        // TODO
                    }
                    else if (font_type == "BoldItalic")
                    {
                        current_run.second.get().bold(true);
                    }
                }

                break;
            }

            case hf_code::bold_font_style: {
                if (!current_run.second.is_set())
                {
                    current_run.second = xlnt::font();
                }

                current_run.second.get().bold(true);

                break;
            }

            case hf_code::italic_font_style: {
                break;
            }

            case hf_code::outline_style: {
                break;
            }

            case hf_code::shadow_style: {
                break;
            }
            }
        }

        if (!current_run.first.empty())
        {
            current_text.add_run(current_run);
        }

        auto location_index =
            static_cast<std::size_t>(code == hf_code::left_section ? 0 : code == hf_code::center_section ? 1 : 2);

        if (!current_text.plain_text().empty())
        {
            result[location_index] = current_text;
        }
    };

    parse_section(hf_code::left_section);
    parse_section(hf_code::center_section);
    parse_section(hf_code::right_section);

    return result;
}

std::string encode_header_footer(const rich_text &t, header_footer::location where, const number_serialiser &serialiser)
{
    const auto location_code_map =
        std::unordered_map<header_footer::location,
            std::string, scoped_enum_hash<header_footer::location>>{
            {header_footer::location::left, "&L"},
            {header_footer::location::center, "&C"},
            {header_footer::location::right, "&R"},
        };

    auto encoded = location_code_map.at(where);

    for (const auto &run : t.runs())
    {
        if (run.first.empty()) continue;

        if (run.second.is_set())
        {
            if (run.second.get().has_name())
            {
                encoded.push_back('&');
                encoded.push_back('"');
                encoded.append(run.second.get().name());
                encoded.push_back(',');

                if (run.second.get().bold())
                {
                    encoded.append("Bold");
                }
                else
                {
                    encoded.append("Regular");
                }
                // todo: BoldItalic?

                encoded.push_back('"');
            }
            else if (run.second.get().bold())
            {
                encoded.append("&B");
            }

            if (run.second.get().has_size())
            {
                encoded.push_back('&');
                encoded.append(serialiser.serialise(run.second.get().size()));
            }
            if (run.second.get().underlined())
            {
                switch (run.second.get().underline())
                {
                case font::underline_style::single:
                case font::underline_style::single_accounting:
                    encoded.append("&U");
                    break;
                case font::underline_style::double_:
                case font::underline_style::double_accounting:
                    encoded.append("&E");
                    break;
                case font::underline_style::none:
                    break;
                }
            }
            if (run.second.get().has_color())
            {
                encoded.push_back('&');
                encoded.push_back('K');
                encoded.append(run.second.get().color().rgb().hex_string().substr(2));
            }
        }

        encoded.append(run.first);
    }

    return encoded;
};

} // namespace detail
} // namespace xlnt
