/**
 * Copyright (c) 2020, Timothy Stack
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * * Neither the name of Timothy Stack nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include "base/humanize.hh"
#include "base/humanize.network.hh"
#include "base/string_util.hh"
#include "mapbox/variant.hpp"

#include "lnav.hh"
#include "files_sub_source.hh"

namespace files_model {
files_list_selection from_selection(vis_line_t sel_vis)
{
    auto &fc = lnav_data.ld_active_files;
    int sel = (int) sel_vis;

    if (sel < fc.fc_name_to_errors.size()) {
        auto iter = fc.fc_name_to_errors.begin();

        std::advance(iter, sel);
        return error_selection::build(sel, iter);
    }

    sel -= fc.fc_name_to_errors.size();

    if (sel < fc.fc_other_files.size()) {
        auto iter = fc.fc_other_files.begin();

        std::advance(iter, sel);
        return other_selection::build(sel, iter);
    }

    sel -= fc.fc_other_files.size();

    if (sel < fc.fc_files.size()) {
        auto iter = fc.fc_files.begin();

        std::advance(iter, sel);
        return file_selection::build(sel, iter);
    }

    return no_selection{};
}
}

files_sub_source::files_sub_source()
{

}

bool files_sub_source::list_input_handle_key(listview_curses &lv, int ch)
{
    switch (ch) {
        case KEY_ENTER:
        case '\r': {
            auto sel = files_model::from_selection(lv.get_selection());

            sel.match(
                [](files_model::no_selection) {},
                [](files_model::error_selection) {},
                [](files_model::other_selection) {},
                [&](files_model::file_selection& fs) {
                    auto& lss = lnav_data.ld_log_source;
                    auto lf = *fs.sb_iter;

                    lss.find_data(lf) | [](auto ld) {
                        ld->set_visibility(true);
                        lnav_data.ld_log_source.text_filters_changed();
                    };

                    if (lf->get_format() != nullptr) {
                        auto& log_view = lnav_data.ld_views[LNV_LOG];
                        lss.row_for_time(lf->front().get_timeval()) | [](auto row) {
                            lnav_data.ld_views[LNV_LOG].set_top(row);
                        };
                        ensure_view(&log_view);
                    } else {
                        auto& tv = lnav_data.ld_views[LNV_TEXT];
                        auto& tss = lnav_data.ld_text_source;

                        tss.to_front(lf);
                        tv.reload_data();
                        ensure_view(&tv);
                    }

                    lv.reload_data();
                    lnav_data.ld_mode = LNM_PAGING;
                }
            );

            return true;
        }

        case ' ': {
            auto sel = files_model::from_selection(lv.get_selection());

            sel.match(
                [](files_model::no_selection) {},
                [](files_model::error_selection) {},
                [](files_model::other_selection) {},
                [&](files_model::file_selection& fs) {
                    auto& lss = lnav_data.ld_log_source;
                    auto lf = *fs.sb_iter;

                    lss.find_data(lf) | [](auto ld) {
                        ld->set_visibility(!ld->ld_visible);
                    };

                    auto top_view = *lnav_data.ld_view_stack.top();
                    auto tss = top_view->get_sub_source();

                    if (tss != nullptr) {
                        tss->text_filters_changed();
                        top_view->reload_data();
                    }

                    lv.reload_data();
                }
            );
            return true;
        }
        case 'n': {
            execute_command(lnav_data.ld_exec_context, "next-mark search");
            return true;
        }
        case 'N': {
            execute_command(lnav_data.ld_exec_context, "prev-mark search");
            return true;
        }
        case '/': {
            execute_command(lnav_data.ld_exec_context,
                            "prompt search-files");
            return true;
        }
        case 'X': {
            auto sel = files_model::from_selection(lv.get_selection());

            sel.match(
                [](files_model::no_selection) {},
                [&](files_model::error_selection& es) {
                    auto &fc = lnav_data.ld_active_files;

                    fc.fc_file_names.erase(es.sb_iter->first);

                    auto name_iter = fc.fc_file_names.begin();
                    while (name_iter != fc.fc_file_names.end()) {
                        if (name_iter->first == es.sb_iter->first) {
                            name_iter = fc.fc_file_names.erase(name_iter);
                            continue;
                        }

                        auto rp_opt = humanize::network::path::from_str(name_iter->first);

                        if (rp_opt) {
                            auto rp = *rp_opt;

                            if (fmt::format("{}", rp.home()) == es.sb_iter->first) {
                                fc.fc_other_files.erase(name_iter->first);
                                name_iter = fc.fc_file_names.erase(name_iter);
                                continue;
                            }
                        }
                        ++name_iter;
                    }

                    fc.fc_name_to_errors.erase(es.sb_iter);
                    fc.fc_invalidate_merge = true;
                    lv.reload_data();
                },
                [](files_model::other_selection) {},
                [](files_model::file_selection) {}
            );
            return true;
        }
    }
    return false;
}

void files_sub_source::list_input_handle_scroll_out(listview_curses &lv)
{
    lnav_data.ld_mode = LNM_PAGING;
    lnav_data.ld_filter_view.reload_data();
}

size_t files_sub_source::text_line_count()
{
    const auto &fc = lnav_data.ld_active_files;

    return fc.fc_name_to_errors.size() +
           fc.fc_other_files.size() +
           fc.fc_files.size();
}

size_t files_sub_source::text_line_width(textview_curses &curses)
{
    return 512;
}

void files_sub_source::text_value_for_line(textview_curses &tc, int line,
                                           std::string &value_out,
                                           text_sub_source::line_flags_t flags)
{
    const auto dim = tc.get_dimensions();
    const auto &fc = lnav_data.ld_active_files;
    auto filename_width =
        std::min(fc.fc_largest_path_length,
                 std::max((size_t) 40, (size_t) dim.second - 30));

    if (line < fc.fc_name_to_errors.size()) {
        auto iter = fc.fc_name_to_errors.begin();
        std::advance(iter, line);
        auto path = ghc::filesystem::path(iter->first);
        auto fn = path.filename().string();

        truncate_to(fn, filename_width);
        value_out = fmt::format(
            FMT_STRING("    {:<{}}   {}"),
            fn, filename_width, iter->second.fei_description);
        return;
    }

    line -= fc.fc_name_to_errors.size();

    if (line < fc.fc_other_files.size()) {
        auto iter = fc.fc_other_files.begin();
        std::advance(iter, line);
        auto path = ghc::filesystem::path(iter->first);
        auto fn = path.string();

        truncate_to(fn, filename_width);
        value_out = fmt::format(
            FMT_STRING("    {:<{}}   {:14}  {}"),
            fn, filename_width, iter->second.ofd_format,
            iter->second.ofd_description);
        return;
    }

    line -= fc.fc_other_files.size();

    const auto &lf = fc.fc_files[line];
    auto fn = lf->get_unique_path();
    char start_time[64] = "", end_time[64] = "";
    std::vector<std::string> file_notes;

    if (lf->get_format() != nullptr) {
        sql_strftime(start_time, sizeof(start_time), lf->front().get_timeval());
        sql_strftime(end_time, sizeof(end_time), lf->back().get_timeval());
    }
    truncate_to(fn, filename_width);
    for (const auto& pair : lf->get_notes()) {
        file_notes.push_back(pair.second);
    }
    value_out = fmt::format(
        FMT_STRING("    {:<{}}   {:>8} {} \u2014 {}  {}"),
        fn,
        filename_width,
        humanize::file_size(lf->get_index_size()),
        start_time,
        end_time,
        fmt::join(file_notes, "; "));
    this->fss_last_line_len =
        filename_width + 23 + strlen(start_time) + strlen(end_time);
}

void files_sub_source::text_attrs_for_line(textview_curses &tc, int line,
                                           string_attrs_t &value_out)
{
    bool selected = lnav_data.ld_mode == LNM_FILES && line == tc.get_selection();
    const auto &fc = lnav_data.ld_active_files;
    auto &vcolors = view_colors::singleton();
    const auto dim = tc.get_dimensions();
    auto filename_width =
        std::min(fc.fc_largest_path_length,
                 std::max((size_t) 40, (size_t) dim.second - 30));

    if (selected) {
        value_out.emplace_back(line_range{0, 1}, &view_curses::VC_GRAPHIC, ACS_RARROW);
    }

    if (line < fc.fc_name_to_errors.size()) {
        if (selected) {
            value_out.emplace_back(line_range{0, -1},
                                   &view_curses::VC_ROLE,
                                   view_colors::VCR_DISABLED_FOCUSED);
        }

        value_out.emplace_back(line_range{4 + (int) filename_width, -1},
                               &view_curses::VC_ROLE_FG,
                               view_colors::VCR_ERROR);
        return;
    }
    line -= fc.fc_name_to_errors.size();

    if (line < fc.fc_other_files.size()) {
        if (selected) {
            value_out.emplace_back(line_range{0, -1},
                                   &view_curses::VC_ROLE,
                                   view_colors::VCR_DISABLED_FOCUSED);
        }
        if (line == fc.fc_other_files.size() - 1) {
            value_out.emplace_back(line_range{0, -1},
                                   &view_curses::VC_STYLE,
                                   A_UNDERLINE);
        }
        return;
    }

    line -= fc.fc_other_files.size();

    if (selected) {
        value_out.emplace_back(line_range{0, -1},
                               &view_curses::VC_ROLE,
                               view_colors::VCR_FOCUSED);
    }

    auto& lss = lnav_data.ld_log_source;
    auto &lf = fc.fc_files[line];
    auto ld_opt = lss.find_data(lf);

    chtype visible = ACS_DIAMOND;
    if (ld_opt && !ld_opt.value()->ld_visible) {
        visible = ' ';
    }
    value_out.emplace_back(line_range{2, 3}, &view_curses::VC_GRAPHIC, visible);
    if (visible == ACS_DIAMOND) {
        value_out.emplace_back(line_range{2, 3}, &view_curses::VC_FOREGROUND,
                               vcolors.ansi_to_theme_color(COLOR_GREEN));
    }

    auto lr = line_range{
        (int) filename_width + 3 + 4,
        (int) filename_width + 3 + 10,
    };
    value_out.emplace_back(lr, &view_curses::VC_STYLE, A_BOLD);

    lr.lr_start = this->fss_last_line_len;
    lr.lr_end = -1;
    value_out.emplace_back(lr, &view_curses::VC_FOREGROUND,
                           vcolors.ansi_to_theme_color(COLOR_YELLOW));
}

size_t files_sub_source::text_size_for_line(textview_curses &tc, int line,
                                            text_sub_source::line_flags_t raw)
{
    return 0;
}

static
auto spinner_index()
{
    auto now = ui_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(
        now.time_since_epoch()).count() / 100;
}

bool
files_overlay_source::list_value_for_overlay(const listview_curses &lv, int y,
                                             int bottom, vis_line_t line,
                                             attr_line_t &value_out)
{
    if (y == 0) {
        static const char PROG[] = "-\\|/";
        constexpr size_t PROG_SIZE = sizeof(PROG) - 1;

        auto &fc = lnav_data.ld_active_files;
        auto &fc_prog = fc.fc_progress;
        safe::WriteAccess<safe_scan_progress> sp(*fc_prog);

        if (!sp->sp_extractions.empty()) {
            const auto& prog = sp->sp_extractions.front();

            value_out.with_ansi_string(fmt::format(
                "{} Extracting "
                ANSI_COLOR(COLOR_CYAN) "{}" ANSI_NORM
                "... {:>8}/{}",
                PROG[spinner_index() % PROG_SIZE],
                prog.ep_path.filename().string(),
                humanize::file_size(prog.ep_out_size),
                humanize::file_size(prog.ep_total_size)));
            return true;
        }
        if (!sp->sp_tailers.empty()) {
            auto first_iter = sp->sp_tailers.begin();

            value_out.with_ansi_string(fmt::format(
                "{} Connecting to "
                ANSI_COLOR(COLOR_CYAN) "{}" ANSI_NORM
                ": {}",
                PROG[spinner_index() % PROG_SIZE],
                first_iter->first,
                first_iter->second.tp_message));
            return true;
        }
    }
    return false;
}
