/********************************************************
 *  ██████╗  ██████╗████████╗██╗
 * ██╔════╝ ██╔════╝╚══██╔══╝██║
 * ██║  ███╗██║        ██║   ██║
 * ██║   ██║██║        ██║   ██║
 * ╚██████╔╝╚██████╗   ██║   ███████╗
 *  ╚═════╝  ╚═════╝   ╚═╝   ╚══════╝
 * Geophysical Computational Tools & Library (GCTL)
 *
 * Copyright (c) 2023  Yi Zhang (yizhang-geo@zju.edu.cn)
 *
 * GCTL is distributed under a dual licensing scheme. You can redistribute 
 * it and/or modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation, either version 2 
 * of the License, or (at your option) any later version. You should have 
 * received a copy of the GNU Lesser General Public License along with this 
 * program. If not, see <http://www.gnu.org/licenses/>.
 * 
 * If the terms and conditions of the LGPL v.2. would prevent you from using 
 * the GCTL, please consider the option to obtain a commercial license for a 
 * fee. These licenses are offered by the GCTL's original author. As a rule, 
 * licenses are provided "as-is", unlimited in time for a one time fee. Please 
 * send corresponding requests to: yizhang-geo@zju.edu.cn. Please do not forget 
 * to include some description of your company and the realm of its activities. 
 * Also add information on how to contact you by electronic and paper mail.
 ******************************************************/

#include "cliplot.h"

gctl::cliplot::cliplot(size_t width, size_t height, double xmin, double xmax, double ymin, double ymax)
{
    new_screen_ = false;
    digs_ = 4;
    xl_num_ = 5;
    yl_num_ = 5;
    wname_ = "X";
    hname_ = "Y";

    struct winsize w;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
    width += digs_ + 1;
    width_ = w.ws_col<width?w.ws_col:width;
    height_ =  w.ws_row<height?w.ws_row:height;
    w0_ = digs_ + 1;
    h0_ = height_ - 2;

    xmin_ = xmin;
    xmax_ = xmax;
    ymin_ = ymin;
    ymax_ = ymax;
    dx_ = (xmax_ - xmin_)/(width_ - w0_);
    dy_ = (ymax_ - ymin_)/height_;
    sym_.resize(width_*height_);
    att_.resize(width_*height_);
    clear();
    return;
}

gctl::cliplot::~cliplot(){}

void gctl::cliplot::clear()
{
    std::fill(sym_.begin(), sym_.end(), ' ');
    std::fill(att_.begin(), att_.end(), "");
    return;
}

/**
 * @brief 设置画布字符属性
 * 
 * @param w 字符列位置
 * @param h 字符行位置
 * @param sym 字符
 * @param att 字符属性
 */
void gctl::cliplot::set(size_t w, size_t h, char sym, const std::string &att)
{
    if (w >= width_ || h >= height_) return;
    sym_[h*width_ + w] = sym;
    att_[h*width_ + w] = att;
    return;
}

void gctl::cliplot::set_axis(int xt_num, int yt_num)
{
    xl_num_ = xt_num;
    yl_num_ = yt_num;
    return;
}

void gctl::cliplot::set_digs(int digs)
{
    int old_digs = digs_;
    digs_ = digs;

    width_ += digs_ - old_digs;
    w0_ += digs_ - old_digs;
    sym_.resize(width_*height_);
    att_.resize(width_*height_);
    clear();
    return;
}

void gctl::cliplot::set_new_screen(bool new_screen)
{
    new_screen_ = new_screen;
    return;
}

void gctl::cliplot::set_wname(const std::string &wname)
{
    wname_ = wname;
    return;
}

void gctl::cliplot::set_hname(const std::string &hname)
{
    hname_ = hname;
    return;
}

void gctl::cliplot::plot_line(double x1, double x2, double y1, double y2, char s, const std::string &t)
{
    if (x1 > x2)
    {
        std::swap(x1, x2);
        std::swap(y1, y2);
    }

    for (size_t w = w0_; w < width_; w++)
    {
        double x = xmin_ + (w - w0_)*(xmax_ - xmin_)/(width_ - 1 - w0_);
        if (x + 0.5*dx_ >= x1 && x - 0.5*dx_ <= x2)
        {
            double y;
            if (x2 - x1 < dx_) y = y1;
            else y = y1 + (y2 - y1)*(x - 0.25*dx_ - x1)/(x2 - x1);
            int h1 = round(h0_ - 1 - (y - ymin_)*(h0_ - 1)/(ymax_ - ymin_));

            if (x2 - x1 < dx_) y = y2;
            else y = y1 + (y2 - y1)*(x + 0.25*dx_ - x1)/(x2 - x1);
            int h2 = round(h0_ - 1 - (y - ymin_)*(h0_ - 1)/(ymax_ - ymin_));

            if (h1 > h2) std::swap(h1, h2);
            for (size_t h = h1; h <= h2; h++)
                set(w, h, s, t);
        }
    }
    return;
}

void gctl::cliplot::plot_data(const std::vector<double> &x, const std::vector<double> &y, char s, const std::string &t)
{
    if (x.size() != y.size()) return;

    for (size_t i = 0; i < x.size() - 1; i++)
    {
        plot_line(x[i], x[i+1], y[i], y[i+1], s, t);
    }
    return;
}

void gctl::cliplot::display(std::ostream &os)
{
    if (new_screen_)
    {
        os << GCTL_CLEARALL;
        GCTL_MOVEUP(os, height_);
    }

    plot_axis();

    for (size_t i = 0; i < height_; i++)
    {
        for (size_t j = 0; j < width_; j++)
        {
            os << att_[i*width_ + j] << sym_[i*width_ + j] << GCTL_RESET;
        }
        os << std::endl;
    }
    return;
}

std::string gctl::cliplot::axis_label(double num, int digs, int &odr)
{
    std::stringstream ss;
    if (floor(log10(abs(num))) >= digs)
    {
        ss << std::scientific << std::setprecision(digs) << num;
        odr = floor(log10(abs(num)));
        if (num < 0) odr *= -1;
    }
    else
    {
        ss << std::fixed << std::setprecision(digs) << num;
        odr = 0;
    }
    return ss.str();
}

void gctl::cliplot::plot_axis()
{
    for (size_t i = w0_; i < width_; i++)
        set(i, h0_, '-', GCTL_BOLD);

    std::vector<int> xpos(xl_num_);
    int xtick = (width_ - w0_)/(xl_num_ - 1);
    xpos[0] = w0_;
    xpos[xl_num_ - 1] = width_ - 1;
    for (size_t i = 1; i <= xl_num_ - 2; i++)
        xpos[i] = w0_ + i*xtick;

    int h_digs = digs_/2;
    int odr;
    for (size_t i = 0; i < xl_num_; i++)
    {
        set(xpos[i], h0_, '|', GCTL_BOLD);
        std::string xlabel = axis_label(xmin_ + (xpos[i] - w0_)*(xmax_ - xmin_)/(width_ - 1 - w0_), digs_, odr);

        for (int c = -h_digs; c <= h_digs; c++)
        {
            set(xpos[i] + c, h0_ + 1, xlabel[c + h_digs], GCTL_BOLD);
        }

        if (odr > 0)
        {
            set(xpos[i] + h_digs + 1, h0_ + 1, 'e', GCTL_BOLD);
            set(xpos[i] + h_digs + 3, h0_ + 1, std::to_string(odr)[0], GCTL_BOLD);
        }
        else if (odr < 0)
        {
            set(xpos[i] + h_digs + 1, h0_ + 1, 'e', GCTL_BOLD);
            set(xpos[i] + h_digs + 2, h0_ + 1, std::to_string(odr)[0], GCTL_BOLD);
            set(xpos[i] + h_digs + 3, h0_ + 1, std::to_string(odr)[1], GCTL_BOLD);
        }
    }

    for (size_t j = 0; j < h0_; j++)
    {
        set(w0_, j, '|', GCTL_BOLD);
    }

    std::vector<int> ypos(yl_num_);
    int ytick = h0_/(yl_num_ - 1);
    ypos[0] = 0;
    ypos[yl_num_ - 1] = h0_ - 1;
    for (size_t i = 1; i <= yl_num_ - 2; i++)
        ypos[i] = i*ytick;

    for (size_t i = 0; i < yl_num_; i++)
    {
        set(w0_, ypos[i], '-', GCTL_BOLD);
        std::string ylabel = axis_label(ymax_ - ypos[i]*(ymax_ - ymin_)/(h0_ - 1), digs_, odr);

        for (size_t c = 0; c < digs_; c++)
        {
            set(c, ypos[i], ylabel[c], GCTL_BOLD);
        }

        if (odr > 0)
        {
            set(digs_ + 3, ypos[i], 'e', GCTL_BOLD);
            set(digs_ + 4, ypos[i], std::to_string(odr)[0], GCTL_BOLD);
        }
        else if (odr < 0)
        {
            set(digs_ + 3, ypos[i], 'e', GCTL_BOLD);
            set(digs_ + 4, ypos[i], std::to_string(odr)[0], GCTL_BOLD);
            set(digs_ + 5, ypos[i], std::to_string(odr)[1], GCTL_BOLD);
        }
    }

    for (size_t i = 0; i < wname_.size(); i++)
    {
        set(width_ - wname_.size() + i, h0_ - 1, wname_[i], GCTL_BOLD);
    }

    for (size_t i = 0; i < hname_.size(); i++)
    {
        set(w0_ + 6 + i, 0, hname_[i], GCTL_BOLD);
    }
    return;
}