#include "Tab.hpp"
#include "Project.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Format/qds_3mf.hpp"

#include <wx/app.h>
#include <wx/button.h>
#include <wx/scrolwin.h>
#include <wx/sizer.h>

#include <wx/bmpcbox.h>
#include <wx/bmpbuttn.h>
#include <wx/treectrl.h>
#include <wx/imaglist.h>
#include <wx/settings.h>
#include <wx/filedlg.h>
#include <wx/wupdlock.h>
#include <wx/dataview.h>
#include <wx/tokenzr.h>
#include <wx/arrstr.h>
#include <wx/tglbtn.h>

#include <boost/log/trivial.hpp>

#include "wxExtensions.hpp"
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
#include "MainFrame.hpp"
#include <slic3r/GUI/Widgets/WebView.hpp>

namespace Slic3r { namespace GUI {

wxDEFINE_EVENT(EVT_PROJECT_RELOAD, wxCommandEvent);

const std::vector<std::string> license_list = {
    "BSD License",
    "Apache License",
    "GPL License",
    "LGPL License",
    "MIT License",
    "CC License"
};

ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style)
{
    m_project_home_url = wxString::Format("file://%s/web/model/index.html", from_u8(resources_dir()));
    wxString strlang = wxGetApp().current_language_code_safe();
    if (strlang != "")
        m_project_home_url = wxString::Format("file://%s/web/model/index.html?lang=%s", from_u8(resources_dir()), strlang);

    wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);

    m_browser = WebView::CreateWebView(this, m_project_home_url);
    if (m_browser == nullptr) {
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("load web view of project page failed");
        return;
    }
    //m_browser->Hide();
    main_sizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
    m_browser->Bind(wxEVT_WEBVIEW_NAVIGATED, &ProjectPanel::on_navigated, this);
    m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ProjectPanel::OnScriptMessage, this, m_browser->GetId());
    Bind(wxEVT_WEBVIEW_NAVIGATING, &ProjectPanel::onWebNavigating, this, m_browser->GetId());

    Bind(EVT_PROJECT_RELOAD, &ProjectPanel::on_reload, this);

    SetSizer(main_sizer);
    Layout();
    Fit();
}

ProjectPanel::~ProjectPanel() {}


void ProjectPanel::onWebNavigating(wxWebViewEvent& evt)
{
    wxString tmpUrl = evt.GetURL();
    //wxString NowUrl = m_browser->GetCurrentURL();

    if (boost::starts_with(tmpUrl, "http://") || boost::starts_with(tmpUrl, "https://")) {
        m_browser->Stop();
        evt.Veto();
        wxLaunchDefaultApplication(tmpUrl);
    }
}

void ProjectPanel::on_reload(wxCommandEvent& evt)
{
    boost::thread reload = boost::thread([this] {
        std::string update_type;
        std::string license;
        std::string model_name;
        std::string model_author;
        std::string cover_file;
        std::string description;
        std::map<std::string, std::vector<json>> files;

        std::string p_name;
        std::string p_author;
        std::string p_description;
        std::string p_cover_file;
        std::string model_id;

        Model model = wxGetApp().plater()->model();

        license = model.model_info->license;
        model_name = model.model_info->model_name;
        cover_file = model.model_info->cover_file;
        description = model.model_info->description;
        update_type = model.model_info->origin;



        if (model.design_info && !model.design_info->DesignerId.empty()) {

            if (m_model_id_map.count(model.design_info->DesignerId) > 0) {
                model_id = m_model_id_map[model.design_info->DesignerId];
            }
            else {
                model_id = get_model_id(model.design_info->DesignerId);
                m_model_id_map[model.design_info->DesignerId] = model_id;
            }
        }
        

        try {
            if (!model.model_info->copyright.empty()) {
                json copy_right = json::parse(model.model_info->copyright);

                if (copy_right.is_array()) {
                    for (auto it = copy_right.begin(); it != copy_right.end(); it++) {
                        if ((*it).contains("author")) {
                            model_author = (*it)["author"].get<std::string>();
                        }
                    }
                }
            }
        }
        catch (...) {
            ;
        }

        if (model_author.empty() && model.design_info != nullptr)
            model_author = model.design_info->Designer;

        if (model.profile_info != nullptr) {
            p_name = model.profile_info->ProfileTile;
            p_description = model.profile_info->ProfileDescription;
            p_cover_file = model.profile_info->ProfileCover;
            p_author = model.profile_info->ProfileUserName;
        }

        //file info
        std::string file_path = encode_path(wxGetApp().plater()->model().get_auxiliary_file_temp_path().c_str());
        if (!file_path.empty()) {
            files = Reload(file_path);
        }
        else {
            clear_model_info();
            return;
        }

        json j;
        j["model"]["license"] = license;
        j["model"]["name"] = wxGetApp().url_encode(model_name);
        j["model"]["author"] = wxGetApp().url_encode(model_author);;
        j["model"]["cover_img"] = wxGetApp().url_encode(cover_file);
        j["model"]["description"] = wxGetApp().url_encode(description);
        j["model"]["preview_img"] = files["Model Pictures"];
        j["model"]["upload_type"] = update_type;
        j["model"]["model_id"] = model_id;

        j["file"]["BOM"] = files["Bill of Materials"];
        j["file"]["Assembly"] = files["Assembly Guide"];
        j["file"]["Other"] = files["Others"];

        j["profile"]["name"] = wxGetApp().url_encode(p_name);
        j["profile"]["author"] = wxGetApp().url_encode(p_author);
        j["profile"]["description"] = wxGetApp().url_encode(p_description);
        j["profile"]["cover_img"] = wxGetApp().url_encode(p_cover_file);
        j["profile"]["preview_img"] = files["Profile Pictures"];

        json m_Res = json::object();
        m_Res["command"] = "show_3mf_info";
        m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id++);
        m_Res["model"] = j;

        wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));

        if (m_web_init_completed) {
            wxGetApp().CallAfter([this, strJS] {
                RunScript(strJS.ToStdString());
                });
        }
    });
}

std::string ProjectPanel::get_model_id(std::string desgin_id)
{
    std::string model_id;
    auto host = wxGetApp().get_http_url(wxGetApp().app_config->get_country_code(), "v1/design-service/model/" + desgin_id);
    Http http = Http::get(host);
    http.header("accept", "application/json")
        //.header("Authorization")
        .on_complete([this, &model_id](std::string body, unsigned status) {
        try {
            json j = json::parse(body);
            if (j.contains("id")) {
                int mid = j["id"].get<int>();
                if (mid > 0) {
                    model_id = std::to_string(mid);
                }
            }
        }
        catch (...) {
            ;
        }
            })
        .on_error([this](std::string body, std::string error, unsigned status) {
            })
        .perform_sync();

    return model_id;
}

void ProjectPanel::msw_rescale() 
{
}

void ProjectPanel::on_size(wxSizeEvent &event)
{
    event.Skip();
}

void ProjectPanel::on_navigated(wxWebViewEvent& event)
{
    event.Skip();
}

void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
{
    try {
        wxString strInput = evt.GetString();
        json     j = json::parse(strInput);

        wxString strCmd = j["command"];

        if (strCmd == "open_3mf_accessory") {
            wxString accessory_path =  j["accessory_path"];

            if (!accessory_path.empty()) {
                std::string decode_path = wxGetApp().url_decode(accessory_path.ToStdString());
                fs::path path(decode_path);

                if (fs::exists(path)) {
                    wxLaunchDefaultApplication(path.wstring(), 0);
                }
            }
        }
        else if (strCmd == "request_3mf_info") {
            m_web_init_completed = true;
        }
        else if (strCmd == "modelmall_model_open") {
            if (j.contains("data")) {
                json data = j["data"];

                if (data.contains("id")) {
                    wxString model_id =  j["data"]["id"];

                    if (!model_id.empty()) {
                        std::string h = wxGetApp().get_model_http_url(wxGetApp().app_config->get_country_code());
                        auto l = wxGetApp().current_language_code_safe();
                        if (auto n = l.find('_'); n != std::string::npos)
                            l = l.substr(0, n);
                        auto url = (boost::format("%1%%2%/models/%3%") % h % l % model_id).str();
                        wxLaunchDefaultBrowser(url);
                    }
                }
            }
            
            
        }
        else if (strCmd == "debug_info") {
            //wxString msg =  j["msg"];
            //OutputDebugString(wxString::Format("Model_Web: msg = %s \r\n", msg));
            //BOOST_LOG_TRIVIAL(info) << wxString::Format("Model_Web: msg = %s", msg);
        }

    }
    catch (std::exception& e) {
        // wxMessageBox(e.what(), "json Exception", MB_OK);
    }
}

void ProjectPanel::update_model_data()
{
    Model model = wxGetApp().plater()->model();
    clear_model_info();

    //basics info
    if (model.model_info == nullptr)
        return;
    
    auto event = wxCommandEvent(EVT_PROJECT_RELOAD);
    event.SetEventObject(this);
    wxPostEvent(this, event);
}

void ProjectPanel::clear_model_info()
{
    json m_Res = json::object();
    m_Res["command"] = "clear_3mf_info";
    m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id++);

    wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));

    wxGetApp().CallAfter([this, strJS] {
        RunScript(strJS.ToStdString());
    });
}

std::map<std::string, std::vector<json>> ProjectPanel::Reload(wxString aux_path)
{
    std::vector<fs::path>                           dir_cache;
    fs::directory_iterator                          iter_end;
    wxString                                        m_root_dir;
    std::map<std::string, std::vector<json>> m_paths_list;

    const static std::array<wxString, 5> s_default_folders = {
        ("Model Pictures"),
        ("Bill of Materials"),
        ("Assembly Guide"),
        ("Others"),
        //(".thumbnails"),
        ("Profile Pictures"),
    };

    for (auto folder : s_default_folders)
        m_paths_list[folder.ToStdString()] = std::vector<json>{};


    fs::path new_aux_path(aux_path.ToStdWstring());

    try {
        fs::remove_all(fs::path(m_root_dir.ToStdWstring()));
    }
    catch (...) {
        BOOST_LOG_TRIVIAL(error) << "Failed  removing the auxiliary directory" << m_root_dir.c_str();
    }

    m_root_dir = aux_path;
    // Check new path. If not exist, create a new one.
    if (!fs::exists(new_aux_path)) {
        fs::create_directory(new_aux_path);
        // Create default folders if they are not loaded
        for (auto folder : s_default_folders) {
            wxString folder_path = aux_path + "/" + folder;
            if (fs::exists(folder_path.ToStdWstring())) continue;
            fs::create_directory(folder_path.ToStdWstring());
        }
        return m_paths_list;
    }

    // Load from new path
    for (fs::directory_iterator iter(new_aux_path); iter != iter_end; iter++) {
        wxString path = iter->path().generic_wstring();
        dir_cache.push_back(iter->path());
    }


    for (auto dir : dir_cache) {
        for (fs::directory_iterator iter(dir); iter != iter_end; iter++) {
            if (fs::is_directory(iter->path())) continue;

            json pfile_obj;

            std::string file_path = iter->path().string();
            fs::path file_path_obj = fs::path(iter->path().string());

            for (auto folder : s_default_folders) {
                auto idx = file_path.find(folder.ToStdString());
                if (idx != std::string::npos) {
                    
                    wxStructStat strucStat;
                    wxString file_name = encode_path(file_path.c_str());
                    wxStat(file_name, &strucStat);
                    wxFileOffset filelen = strucStat.st_size;
     
                    pfile_obj["filename"] = wxGetApp().url_encode(file_path_obj.filename().string().c_str());
                    pfile_obj["size"] = formatBytes((unsigned long)filelen);

                    std::string file_extension = file_path_obj.extension().string();
                    boost::algorithm::to_lower(file_extension);

                    //image
                    if (file_extension == ".jpg"    ||
                        file_extension == ".jpeg"   ||
                        file_extension == ".pjpeg"  ||
                        file_extension == ".png"    ||
                        file_extension == ".jfif"   ||
                        file_extension == ".pjp"    ||
                        file_extension == ".webp"   ||
                        file_extension == ".bmp")
                    {

                        wxString base64_str = to_base64(file_path);
                        pfile_obj["filepath"] = base64_str.ToStdString();
                        m_paths_list[folder.ToStdString()].push_back(pfile_obj);
                        break;
                    }
                    else {
                        pfile_obj["filepath"] = wxGetApp().url_encode(file_path);
                        m_paths_list[folder.ToStdString()].push_back(pfile_obj);
                        break;
                    }
                }
            }
        }
    }

    return m_paths_list;
}

std::string ProjectPanel::formatBytes(unsigned long bytes)
{
    double dValidData = round(double(bytes) / (1024 * 1024) * 1000) / 1000;
    return wxString::Format("%.2fMB", dValidData).ToStdString();
}

wxString ProjectPanel::to_base64(std::string file_path) 
{

    std::ifstream imageFile(encode_path(file_path.c_str()), std::ios::binary);
    if (!imageFile) {
        return wxEmptyString;
    }

    std::ostringstream imageStream;
    imageStream << imageFile.rdbuf();

    std::string binaryImageData = imageStream.str();

    std::string extension;
    size_t last_dot = file_path.find_last_of(".");
   
    if (last_dot != std::string::npos) {
        extension = file_path.substr(last_dot + 1);
    }

    wxString bease64_head = wxString::Format("data:image/%s;base64,", extension);


    std::wstringstream wss;
    wss << bease64_head;
    wss << wxBase64Encode(binaryImageData.data(), binaryImageData.size());

    wxString base64_str = wss.str();
    return  base64_str;
}

void ProjectPanel::RunScript(std::string content)
{
    WebView::RunScript(m_browser, content);
}

bool ProjectPanel::Show(bool show) 
{
    if (show) update_model_data();
    return wxPanel::Show(show); 
}

}} // namespace Slic3r::GUI
