/* Copyright 2020, Michele Santullo
 * This file is part of orotool.
 *
 * Orotool is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Orotool is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Orotool.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "timer_oro_api.hpp"
#include "orotool_config.hpp"
#include "oro/api.hpp"
#if defined(OROTOOL_WITH_LZMA)
#	include "lzma.hpp"
#endif
#include "base64.hpp"
#include "magic_enum.hpp"
#if defined(OROTOOL_WITH_RESTCCPP)
#	include <restc-cpp/error.h>
#elif defined(OROTOOL_WITH_NAP)
#	include "oro/api_nap_exception.hpp"
#	include <curl/curl.h>
#endif

namespace duck {

namespace {
	template <oro::DBOperation Op> auto invoke_api_func (oro::Api& api, oro::SourceFormat store_mode);

	template <>
	inline auto invoke_api_func<oro::DBOperation::Icons> (oro::Api& api, oro::SourceFormat store_mode) {
		return api.items_icons(oro::SourceFormat::None != store_mode);
	}

	template <>
	inline auto invoke_api_func<oro::DBOperation::Items> (oro::Api& api, oro::SourceFormat store_mode) {
		return api.items_list(oro::SourceFormat::None != store_mode);
	}

	template <>
	inline auto invoke_api_func<oro::DBOperation::Creators> (oro::Api& api, oro::SourceFormat store_mode) {
		return api.fame_list(oro::SourceFormat::None != store_mode);
	}

	template <>
	inline auto invoke_api_func<oro::DBOperation::Shops> (oro::Api& api, oro::SourceFormat store_mode) {
		return api.market_list(oro::SourceFormat::None != store_mode);
	}
} //unnamed namespace

template<oro::DBOperation Op>
inline TimerOroApi<Op>::TimerOroApi (
	const eve::Context& ctx,
	const TimerSettings& settings,
	EventiaThreadPool* pool,
	oro::Api* oro_api,
	oro::OriginsDB* db
) :
	TimerBase(ctx, Op, settings, pool, oro_api, db),
	m_retries_left(TimerBase::error_retries())
{
}

template<oro::DBOperation Op>
inline void TimerOroApi<Op>::fetch_data (oro::SourceFormat store_mode) {
	bool schedule_retry = false;
	try {
		auto results = invoke_api_func<Op>(oro_api(), store_mode);
		set_next_timer(results.header);

		oro::Source raw_src;
		raw_src.format = store_mode;
		switch (store_mode) {
		case oro::SourceFormat::Plain:
			raw_src.data = std::move(results.raw_response);
			break;
		case oro::SourceFormat::Base64_xz:
#if defined(OROTOOL_WITH_LZMA)
			{
				Lzma lzma;
				raw_src.data = base64_encode(lzma.compress(results.raw_response));
			}
			break;
#endif
		default:
			raw_src.format = oro::SourceFormat::None;
		}
		this->update_db(results.data, results.header, raw_src);
	}
#if defined(OROTOOL_WITH_RESTCCPP)
	catch (const restc_cpp::RequestFailedWithErrorException& err) {
		const int status_code = err.http_response.status_code;
		if (429 == err.http_response.status_code) {
			schedule_retry = true;
			//server received too many requests, give up for now
		}
		else {
			throw;
		}
	}
#elif defined(OROTOOL_WITH_NAP)
	catch (const oro::ServerError& err) {
		const int status_code = err.error_code();
		switch (status_code) {
		case 503:
			std::cerr << "Server returned 503 during request for " <<
				magic_enum::enum_name(Op) << '\n';
			schedule_retry = true;
			break;
		case 429:
			schedule_retry = true;
			break;
		default:
			throw;
		}
	}
	catch (const oro::ConnectionError& err) {
		switch (err.curl_code()) {
		case CURLE_OPERATION_TIMEDOUT:
		case CURLE_COULDNT_RESOLVE_PROXY:
		case CURLE_COULDNT_RESOLVE_HOST:
		case CURLE_COULDNT_CONNECT:
			if (m_retries_left) {
				--m_retries_left;
				this->set_timer(error_timeout());
				return;
			}
			else {
				throw;
			}
		default:
			throw;
		}
	}
#endif

	m_retries_left = error_retries();
	if (schedule_retry) {
		oro::Header head;
		head.date.ts = std::chrono::time_point_cast<oro::timestamp_t::duration>(std::chrono::system_clock::now());
		head.rate_limit = 1;
		head.rate_limit_remaining = 0;
		head.rate_limit_reset = 600;
		head.retry_after = 600;
		head.server = "forged_header";
		set_next_timer(head);
		this->reset_db_access_time(Op);
	}
}

template class TimerOroApi<oro::DBOperation::Shops>;
template class TimerOroApi<oro::DBOperation::Icons>;
template class TimerOroApi<oro::DBOperation::Items>;
template class TimerOroApi<oro::DBOperation::Creators>;

} //namespace duck
