/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "factory_sheet.hpp"
#include "orcus/spreadsheet/sheet.hpp"
#include "orcus/spreadsheet/document.hpp"
#include "orcus/spreadsheet/view.hpp"
#include "orcus/measurement.hpp"
#include "orcus/string_pool.hpp"

#include "formula_global.hpp"

#include <ixion/formula_name_resolver.hpp>
#include <ixion/model_context.hpp>
#include <ixion/formula.hpp>

namespace orcus { namespace spreadsheet {

import_sheet_named_exp::import_sheet_named_exp(document& doc, sheet_t sheet_index) :
    m_doc(doc), m_sheet_index(sheet_index), m_base(sheet_index, 0, 0) {}

import_sheet_named_exp::~import_sheet_named_exp() {}

void import_sheet_named_exp::define(
    std::string_view name, std::string_view expression, formula_ref_context_t ref_cxt)
{
    string_pool& sp = m_doc.get_string_pool();
    m_name = sp.intern(name).first;

    const ixion::formula_name_resolver* resolver = m_doc.get_formula_name_resolver(ref_cxt);
    assert(resolver);

    ixion::model_context& cxt = m_doc.get_model_context();
    m_tokens = ixion::parse_formula_string(cxt, m_base, *resolver, expression);
}

void import_sheet_named_exp::set_base_position(const src_address_t& pos)
{
    m_base.sheet = pos.sheet;
    m_base.row = pos.row;
    m_base.column = pos.column;
}

void import_sheet_named_exp::set_named_expression(std::string_view name, std::string_view expression)
{
    define(name, expression, formula_ref_context_t::global);
}

void import_sheet_named_exp::set_named_range(std::string_view name, std::string_view range)
{
    define(name, range, formula_ref_context_t::named_range);
}

void import_sheet_named_exp::commit()
{
    ixion::model_context& cxt = m_doc.get_model_context();
    cxt.set_named_expression(m_sheet_index, std::string{m_name}, m_base, std::move(m_tokens));

    m_name = std::string_view{};
    m_base.sheet = 0;
    m_base.row = 0;
    m_base.column = 0;
}

import_data_table::import_data_table(sheet& sh) : m_sheet(sh) {}
import_data_table::~import_data_table() {}

void import_data_table::reset()
{
}

void import_data_table::set_type(data_table_type_t /*type*/)
{
}

void import_data_table::set_range(const range_t& /*range*/)
{
}

void import_data_table::set_first_reference(std::string_view /*ref*/, bool /*deleted*/)
{
}

void import_data_table::set_second_reference(std::string_view /*ref*/, bool /*deleted*/)
{
}

void import_data_table::commit()
{
}

import_array_formula::import_array_formula(document& doc, sheet& sheet) :
    m_doc(doc), m_sheet(sheet), m_missing_formula_result(), m_error_policy(formula_error_policy_t::fail)
{
    m_range.first.column = -1;
    m_range.first.row = -1;
    m_range.last = m_range.first;
}

import_array_formula::~import_array_formula()
{
}

void import_array_formula::set_range(const range_t& range)
{
    m_range = range;

    // Initialize the result matrix with the missing result value.
    switch (m_missing_formula_result.get_type())
    {
        case ixion::formula_result::result_type::value:
        {
            ixion::matrix _mtx(
                m_range.last.row - m_range.first.row + 1,
                m_range.last.column - m_range.first.column + 1,
                m_missing_formula_result.get_value());
            m_result_mtx.swap(_mtx);
            break;
        }
        case ixion::formula_result::result_type::error:
        {
            ixion::matrix _mtx(
                m_range.last.row - m_range.first.row + 1,
                m_range.last.column - m_range.first.column + 1,
                m_missing_formula_result.get_error());
            m_result_mtx.swap(_mtx);
            break;
        }
        case ixion::formula_result::result_type::string:
        {
            ixion::matrix _mtx(
                m_range.last.row - m_range.first.row + 1,
                m_range.last.column - m_range.first.column + 1,
                m_missing_formula_result.get_string());
            m_result_mtx.swap(_mtx);
            break;
        }
        default:
        {
            ixion::matrix _mtx(
                m_range.last.row - m_range.first.row + 1,
                m_range.last.column - m_range.first.column + 1);
            m_result_mtx.swap(_mtx);
        }
    }
}

void import_array_formula::set_formula(formula_grammar_t /*grammar*/, std::string_view formula)
{
    const ixion::formula_name_resolver* resolver =
        m_doc.get_formula_name_resolver(spreadsheet::formula_ref_context_t::global);
    if (!resolver)
        return;

    // Tokenize the formula string and store it.
    ixion::model_context& cxt = m_doc.get_model_context();
    ixion::abs_address_t pos(m_sheet.get_index(), m_range.first.row, m_range.first.column);

    try
    {
        m_tokens = ixion::parse_formula_string(cxt, pos, *resolver, formula);
    }
    catch (const std::exception& e)
    {
        if (m_error_policy == formula_error_policy_t::fail)
            throw;

        std::string_view error_s = e.what();
        m_tokens = ixion::create_formula_error_tokens(cxt, formula, error_s);
    }
}

void import_array_formula::set_result_value(row_t row, col_t col, double value)
{
    m_result_mtx.set(row, col, value);
}

void import_array_formula::set_result_string(row_t /*row*/, col_t /*col*/, std::string_view /*value*/)
{
    // TODO : handle this
}

void import_array_formula::set_result_empty(row_t /*row*/, col_t /*col*/)
{
    // TODO : handle this
}

void import_array_formula::set_result_bool(row_t row, col_t col, bool value)
{
    m_result_mtx.set(row, col, value);
}

void import_array_formula::commit()
{
    ixion::formula_result cached_results(std::move(m_result_mtx));
    m_sheet.set_grouped_formula(m_range, std::move(m_tokens), std::move(cached_results));
}

void import_array_formula::set_missing_formula_result(ixion::formula_result result)
{
    m_missing_formula_result = std::move(result);
}

void import_array_formula::set_formula_error_policy(formula_error_policy_t policy)
{
    m_error_policy = policy;
}

void import_array_formula::reset()
{
    m_tokens.clear();
    m_result_mtx = ixion::matrix();
    m_range.first.row = -1;
    m_range.first.column = -1;
    m_range.last.row = -1;
    m_range.last.column = -1;
}

import_formula::import_formula(document& doc, sheet& sheet, shared_formula_pool& pool) :
    m_doc(doc),
    m_sheet(sheet),
    m_shared_formula_pool(pool),
    m_row(-1),
    m_col(-1),
    m_shared_index(0),
    m_shared(false),
    m_error_policy(formula_error_policy_t::fail) {}

import_formula::~import_formula() {}

void import_formula::set_position(row_t row, col_t col)
{
    m_row = row;
    m_col = col;
}

void import_formula::set_formula(formula_grammar_t /*grammar*/, std::string_view formula)
{
    if (m_row < 0 || m_col < 0)
        return;

    const ixion::formula_name_resolver* resolver =
        m_doc.get_formula_name_resolver(spreadsheet::formula_ref_context_t::global);
    if (!resolver)
        return;

    // Tokenize the formula string and store it.
    ixion::model_context& cxt = m_doc.get_model_context();
    ixion::abs_address_t pos(m_sheet.get_index(), m_row, m_col);

    ixion::formula_tokens_t tokens;
    try
    {
        tokens = ixion::parse_formula_string(cxt, pos, *resolver, formula);
    }
    catch (const std::exception& e)
    {
        if (m_error_policy == formula_error_policy_t::fail)
            throw;

        std::string_view error_s = e.what();
        tokens = ixion::create_formula_error_tokens(cxt, formula, error_s);
    }

    m_tokens_store = ixion::formula_tokens_store::create();
    m_tokens_store->get() = std::move(tokens);
}

void import_formula::set_shared_formula_index(size_t index)
{
    m_shared = true;
    m_shared_index = index;
}

void import_formula::set_result_value(double value)
{
    m_result = ixion::formula_result(value);
}

void import_formula::set_result_string(std::string_view value)
{
    m_result = ixion::formula_result(std::string{value});
}

void import_formula::set_result_empty() {}
void import_formula::set_result_bool(bool /*value*/) {}

void import_formula::commit()
{
    if (m_row < 0 || m_col < 0)
        return;

    if (m_shared)
    {
        if (m_tokens_store)
        {
            if (m_result)
                m_sheet.set_formula(m_row, m_col, m_tokens_store, *m_result);
            else
                m_sheet.set_formula(m_row, m_col, m_tokens_store);

            m_shared_formula_pool.add(m_shared_index, m_tokens_store);
        }
        else
        {
            ixion::formula_tokens_store_ptr_t ts = m_shared_formula_pool.get(m_shared_index);
            if (!ts)
                return;

            if (m_result)
                m_sheet.set_formula(m_row, m_col, ts, *m_result);
            else
                m_sheet.set_formula(m_row, m_col, ts);
        }
        return;
    }

    if (m_result)
        m_sheet.set_formula(m_row, m_col, m_tokens_store, *m_result);
    else
        m_sheet.set_formula(m_row, m_col, m_tokens_store);
}

void import_formula::set_missing_formula_result(ixion::formula_result result)
{
    m_result = std::move(result);
}

void import_formula::set_formula_error_policy(formula_error_policy_t policy)
{
    m_error_policy = policy;
}

void import_formula::reset()
{
    m_tokens_store.reset();
    m_result.reset();
    m_row = -1;
    m_col = -1;
    m_shared_index = 0;
    m_shared = false;
}

import_sheet::import_sheet(document& doc, sheet& sh, sheet_view* view) :
    m_doc(doc),
    m_sheet(sh),
    m_formula(doc, sh, m_shared_formula_pool),
    m_array_formula(doc, sh),
    m_named_exp(doc, sh.get_index()),
    m_sheet_properties(doc, sh),
    m_data_table(sh),
    m_auto_filter(doc.get_string_pool()),
    m_table(doc, sh),
    m_charset(character_set_t::unspecified),
    m_fill_missing_formula_results(false)
{
    if (view)
        m_sheet_view = std::make_unique<import_sheet_view>(*view, sh.get_index());
}

import_sheet::~import_sheet() {}

iface::import_sheet_view* import_sheet::get_sheet_view()
{
    return m_sheet_view.get();
}

iface::import_auto_filter* import_sheet::start_auto_filter(const range_t& range)
{
    auto& dest = m_sheet;

    import_auto_filter::commit_func_type func = [range, &dest](auto_filter_t&& filter)
    {
        dest.set_auto_filter(std::make_unique<auto_filter_t>(std::move(filter)));
    };

    m_auto_filter.reset(std::move(func), to_abs_range(range, dest.get_index()));
    return &m_auto_filter;
}

iface::import_conditional_format* import_sheet::get_conditional_format()
{
    return nullptr;
}

iface::import_data_table* import_sheet::get_data_table()
{
    return &m_data_table;
}

iface::import_named_expression* import_sheet::get_named_expression()
{
    return &m_named_exp;
}

iface::import_sheet_properties* import_sheet::get_sheet_properties()
{
    return &m_sheet_properties;
}

iface::import_table* import_sheet::start_table()
{
    m_table.reset();
    return &m_table;
}

iface::import_formula* import_sheet::get_formula()
{
    m_formula.reset();

    if (m_fill_missing_formula_results)
    {
        m_formula.set_missing_formula_result(
            ixion::formula_result(ixion::formula_error_t::no_result_error));
    }

    return &m_formula;
}

iface::import_array_formula* import_sheet::get_array_formula()
{
    m_array_formula.reset();

    if (m_fill_missing_formula_results)
    {
        m_array_formula.set_missing_formula_result(
            ixion::formula_result(ixion::formula_error_t::no_result_error));
    }

    return &m_array_formula;
}

void import_sheet::set_auto(row_t row, col_t col, std::string_view s)
{
    m_sheet.set_auto(row, col, s);
}

void import_sheet::set_bool(row_t row, col_t col, bool value)
{
    m_sheet.set_bool(row, col, value);
}

void import_sheet::set_date_time(row_t row, col_t col, int year, int month, int day, int hour, int minute, double second)
{
    m_sheet.set_date_time(row, col, year, month, day, hour, minute, second);
}

void import_sheet::set_format(row_t row, col_t col, size_t xf_index)
{
    m_sheet.set_format(row, col, xf_index);
}

void import_sheet::set_format(
    row_t row_start, col_t col_start, row_t row_end, col_t col_end, size_t xf_index)
{
    m_sheet.set_format(row_start, col_start, row_end, col_end, xf_index);
}

void import_sheet::set_column_format(col_t col, col_t col_span, std::size_t xf_index)
{
    m_sheet.set_column_format(col, col_span, xf_index);
}

void import_sheet::set_row_format(row_t row, std::size_t xf_index)
{
    m_sheet.set_row_format(row, xf_index);
}

void import_sheet::set_string(row_t row, col_t col, string_id_t sindex)
{
    m_sheet.set_string(row, col, sindex);
}

void import_sheet::set_value(row_t row, col_t col, double value)
{
    m_sheet.set_value(row, col, value);
}

void import_sheet::fill_down_cells(row_t src_row, col_t src_col, row_t range_size)
{
    m_sheet.fill_down_cells(src_row, src_col, range_size);
}

range_size_t import_sheet::get_sheet_size() const
{
    return m_doc.get_sheet_size();
}

void import_sheet::set_character_set(character_set_t charset)
{
    m_charset = charset;
}

void import_sheet::set_fill_missing_formula_results(bool b)
{
    m_fill_missing_formula_results = b;
}

void import_sheet::set_formula_error_policy(formula_error_policy_t policy)
{
    m_formula.set_formula_error_policy(policy);
    m_array_formula.set_formula_error_policy(policy);
}

import_sheet_view::import_sheet_view(sheet_view& view, sheet_t si) :
    m_view(view), m_sheet_index(si) {}

import_sheet_view::~import_sheet_view() {}

void import_sheet_view::set_split_pane(
        double hor_split, double ver_split,
        const orcus::spreadsheet::address_t& top_left_cell,
        orcus::spreadsheet::sheet_pane_t active_pane)
{
    m_view.set_split_pane(hor_split, ver_split, top_left_cell);
    m_view.set_active_pane(active_pane);
}

void import_sheet_view::set_frozen_pane(
        orcus::spreadsheet::col_t visible_columns,
        orcus::spreadsheet::row_t visible_rows,
        const orcus::spreadsheet::address_t& top_left_cell,
        orcus::spreadsheet::sheet_pane_t active_pane)
{
    m_view.set_frozen_pane(visible_columns, visible_rows, top_left_cell);
    m_view.set_active_pane(active_pane);
}

void import_sheet_view::set_selected_range(sheet_pane_t pane, range_t range)
{
    m_view.set_selection(pane, range);
}

void import_sheet_view::set_sheet_active()
{
    m_view.get_document_view().set_active_sheet(m_sheet_index);
}

import_sheet_properties::import_sheet_properties(document& doc, sheet& sh) :
    m_doc(doc), m_sheet(sh) {}

import_sheet_properties::~import_sheet_properties() {}

void import_sheet_properties::set_column_width(col_t col, col_t col_span, double width, orcus::length_unit_t unit)
{
    col_width_t w = orcus::convert(width, unit, length_unit_t::twip);
    m_sheet.set_col_width(col, col_span, w);
}

void import_sheet_properties::set_column_hidden(col_t col, col_t col_span, bool hidden)
{
    m_sheet.set_col_hidden(col, col_span, hidden);
}

void import_sheet_properties::set_row_height(row_t row, row_t row_span, double height, orcus::length_unit_t unit)
{
    row_height_t h = orcus::convert(height, unit, length_unit_t::twip);
    m_sheet.set_row_height(row, row_span, h);
}

void import_sheet_properties::set_row_hidden(row_t row, row_t row_span, bool hidden)
{
    m_sheet.set_row_hidden(row, row_span, hidden);
}

void import_sheet_properties::set_merge_cell_range(const range_t& range)
{
    m_sheet.set_merge_cell_range(range);
}

export_sheet::export_sheet(const document& doc, const sheet& sh) : m_doc(doc), m_sheet(sh) {}
export_sheet::~export_sheet() = default;

void export_sheet::write_string(std::ostream& os, row_t row, col_t col) const
{
    const ixion::model_context& cxt = m_doc.get_model_context();
    ixion::abs_address_t pos(m_sheet.get_index(), row, col);

    switch (cxt.get_celltype(pos))
    {
        case ixion::cell_t::string:
        {
            size_t str_id = cxt.get_string_identifier(pos);
            const std::string* p = cxt.get_string(str_id);
            if (p)
                os << *p;
        }
        break;
        case ixion::cell_t::numeric:
            os << cxt.get_numeric_value(pos);
        break;
        default:
            ;
    }
}

}}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

