#include "MarkdownTip.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "MainFrame.hpp"
#include "Widgets/WebView.hpp"

#include "libslic3r/Utils.hpp"
#include "I18N.hpp"

namespace fs = boost::filesystem;

namespace Slic3r { namespace GUI {

// CMGUO

static std::string url_encode(const std::string& value) {
    std::ostringstream escaped;
    escaped.fill('0');
    escaped << std::hex;
    for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
        std::string::value_type c = (*i);

        // Keep alphanumeric and other accepted characters intact
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            escaped << c;
            continue;
        }

        // Any other characters are percent-encoded
        escaped << std::uppercase;
        escaped << '%' << std::setw(2) << int((unsigned char)c);
        escaped << std::nouppercase;
    }
    return escaped.str();
}
/*
 * Edge browser not support WebViewHandler
 * 
class MyWebViewHandler : public wxWebViewArchiveHandler
{
public:
    MyWebViewHandler() : wxWebViewArchiveHandler("tooltip") {}
    wxFSFile* GetFile(const wxString& uri) override {
        // file:///resources/tooltip/test.md
        wxFSFile* direct = wxWebViewArchiveHandler::GetFile(uri);
        if (direct)
            return direct;
        // file:///data/tooltips.zip;protocol=zip/test.md
        int n = uri.Find("resources/tooltip");
        if (n == wxString::npos)
            return direct;
        set_var_dir(data_dir());
        auto url = var("tooltips.zip");
        std::replace(url.begin(), url.end(), '\\', '/');
        auto uri2 = "file:///" + wxString(url) + ";protocol=zip" + uri.substr(n + 17);
        return wxWebViewArchiveHandler::GetFile(uri2);
    } 
};
*/

/*
TODO:
1. Fix height correctly now h * 1.25 + 50
2. Async RunScript avoid long call stack risc
3. Fetch markdown content in javascript (*)
4. Use scheme handler to support zip archive & make code tidy
*/

MarkdownTip::MarkdownTip()
    : wxPopupTransientWindow(wxGetApp().mainframe, wxBORDER_NONE)
{
    wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);

    _tipView = CreateTipView(this);
    topsizer->Add(_tipView, wxSizerFlags().Expand().Proportion(1));

    SetSizer(topsizer);
    SetSize({400, 300});

    LoadStyle();

    _timer = new wxTimer;
    _timer->Bind(wxEVT_TIMER, &MarkdownTip::OnTimer, this);
}

MarkdownTip::~MarkdownTip() { delete _timer; }

void MarkdownTip::LoadStyle()
{
    _language = GUI::into_u8(GUI::wxGetApp().current_language_code());
    fs::path ph(data_dir());
    ph /= "resources/tooltip/common/styled.html";
    _data_dir = true;
    if (!fs::exists(ph)) {
        ph = resources_dir();
        ph /= "tooltip/styled.html";
        _data_dir = false;
    }
    auto url = ph.string();
    std::replace(url.begin(), url.end(), '\\', '/');
    url = "file:///" + url;
    _tipView->LoadURL(from_u8(url));
    _lastTip.clear();
}

bool MarkdownTip::ShowTip(wxPoint pos, std::string const &tip, std::string const &tooltip)
{
    if (tip.empty()) {
        if (_tipView->GetParent() != this)
            return false;
        if (pos.x) {
            _hide = true;
            BOOST_LOG_TRIVIAL(info) << "MarkdownTip::ShowTip: hide soon on empty tip.";
            this->Hide();
        }
        else if (!_hide) {
            _hide = true;
            BOOST_LOG_TRIVIAL(info) << "MarkdownTip::ShowTip: start hide timer (300)...";
            _timer->StartOnce(300);
        }
        return false;
    }
    bool tipChanged = _lastTip != tip;
    if (tipChanged) {
        auto content = LoadTip(tip, tooltip);
        if (content.empty()) {
            _hide = true;
            this->Hide();
            BOOST_LOG_TRIVIAL(info) << "MarkdownTip::ShowTip: hide soon on empty content.";
            return false;
        }
        auto script = "window.showMarkdown('" + url_encode(content) + "', true);";
        if (!_pendingScript.empty()) {
            _pendingScript = script;
        }
        else {
            RunScript(script);
        }
        _lastTip = tip;
        if (_tipView->GetParent() == this)
            this->Hide();
    }
    if (_tipView->GetParent() == this) {
        wxSize size = wxDisplay(this).GetClientArea().GetSize();
        _requestPos = pos;
        if (pos.y + this->GetSize().y > size.y)
            pos.y = size.y - this->GetSize().y;
        this->SetPosition(pos);
        if (tipChanged || _hide) {
            _hide = false;
            BOOST_LOG_TRIVIAL(info) << "MarkdownTip::ShowTip: start show timer (500)...";
            _timer->StartOnce(500);
        }
    }
    return true;
}

std::string MarkdownTip::LoadTip(std::string const &tip, std::string const &tooltip)
{
    fs::path ph;
    wxString file;
    wxFile   f;
    if (_data_dir) {
        if (!_language.empty()) {
            ph = data_dir();
            ph /= "resources/tooltip/" + _language +  "/" + tip + ".md";
            file = from_u8(ph.string());
            if (wxFile::Exists(file) && f.Open(file)) {
                std::string content(f.Length(), 0);
                f.Read(&content[0], content.size());
                return content;
            }
        }
        ph = data_dir();
        ph /= "resources/tooltip/common/" + tip + ".md";
        file = from_u8(ph.string());
        if (wxFile::Exists(file) && f.Open(file)) {
            std::string content(f.Length(), 0);
            f.Read(&content[0], content.size());
            return content;
        }
    }
    /*
    file = var("tooltips.zip");
    if (wxFile::Exists(file) && f.Open(file)) {
        wxFileInputStream fs(f);
        wxZipInputStream zip(fs);
        file = tip + ".md";
        while (auto e = zip.GetNextEntry()) {
            if (e->GetName() == file) {
                if (zip.OpenEntry(*e)) {
                    std::string content(f.Length(), 0);
                    zip.Read(&content[0], content.size());
                    return content;
                }
                break;
            }
        }
    }
    */
    ph = resources_dir();
    ph /= "tooltip/" + _language + "/" + tip + ".md";
    file = from_u8(ph.string());
    if (wxFile::Exists(file) && f.Open(file)) {
        std::string content(f.Length(), 0);
        f.Read(&content[0], content.size());
        return content;
    }
    ph = resources_dir();
    ph /= "tooltip/" + tip + ".md";
    file = from_u8(ph.string());
    if (wxFile::Exists(file) && f.Open(file)) {
        std::string content(f.Length(), 0);
        f.Read(&content[0], content.size());
        return content;
    }
    if (!tooltip.empty()) return "#### " + _utf8(tip) + "\n" + tooltip;
    return (_tipView->GetParent() == this && tip.empty()) ? "" : LoadTip("", "");
}

void MarkdownTip::RunScript(std::string const& script)
{
    WebView::RunScript(_tipView, script);
}

wxWebView* MarkdownTip::CreateTipView(wxWindow* parent)
{
    wxWebView *tipView = WebView::CreateWebView(parent, "");
    Bind(wxEVT_WEBVIEW_LOADED, &MarkdownTip::OnLoaded, this);
    Bind(wxEVT_WEBVIEW_TITLE_CHANGED, &MarkdownTip::OnTitleChanged, this);
    Bind(wxEVT_WEBVIEW_ERROR, &MarkdownTip::OnError, this);
    return tipView;
}

void MarkdownTip::OnLoaded(wxWebViewEvent& event)
{
}

void MarkdownTip::OnTitleChanged(wxWebViewEvent& event)
{
    if (!_pendingScript.empty()) {
        RunScript(_pendingScript);
        _pendingScript.clear();
        return;
    }
#ifdef __linux__
    wxString str = "0";
#else
    wxString str = event.GetString();
#endif
    double height = 0;
    if (str.ToDouble(&height)) {
        if (height > _lastHeight - 10 && height < _lastHeight + 10)
            return;
        _lastHeight = height;
        height *= 1.25; height += 50;
        wxSize size = wxDisplay(this).GetClientArea().GetSize();
        if (height > size.y)
            height = size.y;
        wxPoint pos = _requestPos;
        if (pos.y + height > size.y)
            pos.y = size.y - height;
        this->SetSize({ 400, (int)height });
        this->SetPosition(pos);
    }
}
void MarkdownTip::OnError(wxWebViewEvent& event)
{
}

void MarkdownTip::OnTimer(wxTimerEvent& event)
{
    if (_hide) {
        wxPoint pos = ScreenToClient(wxGetMousePosition());
        if (GetClientRect().Contains(pos)) {
            BOOST_LOG_TRIVIAL(info) << "MarkdownTip::OnTimer: restart hide timer...";
            _timer->StartOnce();
            return;
        }
        BOOST_LOG_TRIVIAL(info) << "MarkdownTip::OnTimer: hide.";
        this->Hide();
    } else {
        BOOST_LOG_TRIVIAL(info) << "MarkdownTip::OnTimer: show.";
        this->Show();
    }
}

MarkdownTip* MarkdownTip::markdownTip(bool create)
{
    static MarkdownTip * markdownTip = nullptr;
    if (markdownTip == nullptr && create)
        markdownTip = new MarkdownTip;
    return markdownTip;
}

bool MarkdownTip::ShowTip(std::string const& tip, std::string const & tooltip, wxPoint pos)
{
#ifdef NDEBUG
    return false;
#endif
    return markdownTip()->ShowTip(pos, tip, tooltip);
}

void MarkdownTip::ExitTip()
{
    //if (auto tip = markdownTip(false))
    //    tip->Destroy();
}

void MarkdownTip::Reload()
{
    if (auto tip = markdownTip(false)) 
        tip->LoadStyle();
}

void MarkdownTip::Recreate(wxWindow *parent)
{
    if (auto tip = markdownTip(false)) {
        tip->Reparent(parent);
        tip->LoadStyle(); // switch language
    }
}

wxWindow* MarkdownTip::AttachTo(wxWindow* parent)
{
    MarkdownTip& tip = *markdownTip();
    tip._tipView = tip.CreateTipView(parent);
    tip._pendingScript = " ";
    return tip._tipView;
}

wxWindow* MarkdownTip::DetachFrom(wxWindow* parent)
{
    MarkdownTip& tip = *markdownTip();
    if (tip._tipView->GetParent() == parent) {
        tip.Destroy();
    }
    return NULL;
}

}
}
