///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, David Kocík @kocikdav, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966
///|/ Copyright (c) 2020 vintagepc
///|/ Copyright (c) 2019 Stephan Reichhelm @stephanr
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "FlashAir.hpp"

#include <algorithm>
#include <ctime>
#include <boost/filesystem/path.hpp>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <boost/algorithm/string/predicate.hpp>

#include <wx/frame.h>
#include <wx/event.h>
#include <wx/progdlg.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/checkbox.h>

#include "libslic3r/PrintConfig.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/format.hpp"
#include "Http.hpp"

namespace fs = boost::filesystem;
namespace pt = boost::property_tree;

namespace Slic3r {

FlashAir::FlashAir(DynamicPrintConfig *config) :
	host(config->opt_string("print_host"))
{}

const char* FlashAir::get_name() const { return "FlashAir"; }

bool FlashAir::test(wxString &msg) const
{
	// Since the request is performed synchronously here,
	// it is ok to refer to `msg` from within the closure

	const char *name = get_name();

	bool res = false;
	auto url = make_url("command.cgi", "op", "118");

	BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get upload enabled at: %2%") % name % url;

	auto http = Http::get(std::move(url));
	http.on_error([&](std::string body, std::string error, unsigned status) {
			BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting upload enabled: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
			res = false;
			msg = format_error(body, error, status);
		})
        .on_complete([&](std::string body, unsigned) {
			BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got upload enabled: %2%") % name % body;

			res = boost::starts_with(body, "1");
			if (! res) {
				msg = _(L("Upload not enabled on FlashAir card."));
			}
		})
		.perform_sync();

	return res;
}

wxString FlashAir::get_test_ok_msg () const
{
	return _(L("Connection to FlashAir works correctly and upload is enabled."));
}

wxString FlashAir::get_test_failed_msg (wxString &msg) const
{
    return GUI::format_wxstr("%s: %s\n%s"
                    , _u8L("Could not connect to FlashAir")
                    , msg
                    , _u8L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required."));
}

bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
	const char *name = get_name();

	const auto upload_filename = upload_data.upload_path.filename();
	const auto upload_parent_path = upload_data.upload_path.parent_path();
	wxString test_msg;
	if (! test(test_msg)) {
		error_fn(std::move(test_msg));
		return false;
	}

	bool res = false;

    std::string strDest = upload_parent_path.string();
    if (strDest.front()!='/') // Needs a leading / else root uploads fail.
    {
        strDest.insert(0,"/");
    }

	auto urlPrepare = make_url("upload.cgi", "WRITEPROTECT=ON&FTIME", timestamp_str());
    auto urlSetDir = make_url("upload.cgi","UPDIR",strDest);
	auto urlUpload = make_url("upload.cgi");

	BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3% / %4%, filename: %5%")
		% name
		% upload_data.source_path
		% urlPrepare
		% urlUpload
		% upload_filename.string();

	// set filetime for upload and make card writeprotect to prevent filesystem damage
	auto httpPrepare = Http::get(std::move(urlPrepare));
	httpPrepare.on_error([&](std::string body, std::string error, unsigned status) {
            BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error preparing upload: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
			error_fn(format_error(body, error, status));
			res = false;
		})
		.on_complete([&, this](std::string body, unsigned) {
			BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got prepare result: %2%") % name % body;
			res = boost::icontains(body, "SUCCESS");
			if (! res) {
				BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
				error_fn(format_error(body, L("Unknown error occured"), 0));
			}
		})
		.perform_sync();
	
	if(! res ) {
		return res;
	}
	
	// start file upload
    auto httpDir = Http::get(std::move(urlSetDir));
    httpDir.on_error([&](std::string body, std::string error, unsigned status) {
            BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error setting upload dir: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
            error_fn(format_error(body, error, status));
            res = false;
        })
        .on_complete([&, this](std::string body, unsigned) {
            BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got dir select result: %2%") % name % body;
            res = boost::icontains(body, "SUCCESS");
            if (! res) {
                BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
                error_fn(format_error(body, L("Unknown error occured"), 0));
            }
        })
        .perform_sync();

    if(! res ) {
        return res;
    }

	auto http = Http::post(std::move(urlUpload));
	http.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
		.on_complete([&](std::string body, unsigned status) {
			BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
			res = boost::icontains(body, "SUCCESS");
			if (! res) {
				BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
				error_fn(format_error(body, L("Unknown error occured"), 0));
			}
		})
		.on_error([&](std::string body, std::string error, unsigned status) {
			BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
			error_fn(format_error(body, error, status));
			res = false;
		})
		.on_progress([&](Http::Progress progress, bool &cancel) {
			prorgess_fn(std::move(progress), cancel);
			if (cancel) {
				// Upload was canceled
				BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Upload canceled") % name;
				res = false;
			}
		})
		.perform_sync();

	return res;
}

std::string FlashAir::timestamp_str() const
{
	auto t = std::time(nullptr);
	auto tm = *std::localtime(&t);

	unsigned long fattime = ((tm.tm_year - 80) << 25) | 
							((tm.tm_mon + 1) << 21) |
							(tm.tm_mday << 16) |
							(tm.tm_hour << 11) |
							(tm.tm_min << 5) |
							(tm.tm_sec >> 1);

	return (boost::format("%1$#x") % fattime).str();
}

std::string FlashAir::make_url(const std::string &path) const
{
	if (host.find("http://") == 0 || host.find("https://") == 0) {
		if (host.back() == '/') {
			return (boost::format("%1%%2%") % host % path).str();
		} else {
			return (boost::format("%1%/%2%") % host % path).str();
		}
	} else {
		if (host.back() == '/') {
			return (boost::format("http://%1%%2%") % host % path).str();
		} else {
			return (boost::format("http://%1%/%2%") % host % path).str();
		}
	}
}

std::string FlashAir::make_url(const std::string &path, const std::string &arg, const std::string &val) const
{
	if (host.find("http://") == 0 || host.find("https://") == 0) {
		if (host.back() == '/') {
			return (boost::format("%1%%2%?%3%=%4%") % host % path % arg % val).str();
		} else {
			return (boost::format("%1%/%2%?%3%=%4%") % host % path % arg % val).str();
		}
	} else {
		if (host.back() == '/') {
			return (boost::format("http://%1%%2%?%3%=%4%") % host % path % arg % val).str();
		} else {
			return (boost::format("http://%1%/%2%?%3%=%4%") % host % path % arg % val).str();
		}
	}
}

}
