/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 *
 * Copyright 2023 Armin Novak <armin.novak@thincast.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <string>
#include <fstream>
#include <algorithm>
#include <iomanip>
#include <iostream>
#if __has_include(<filesystem>)
#include <filesystem>
namespace fs = std::filesystem;
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
#error Could not find system header "<filesystem>" or "<experimental/filesystem>"
#endif

static void usage(const char* prg)
{
	std::cerr << prg << " <file> <type> <class name> <dst path>" << std::endl;
}

static int write_comment_header(std::ostream& out, const fs::path& prg, const std::string& fname)
{
	out << "/* AUTOGENERATED file, do not edit" << std::endl
	    << " *" << std::endl
	    << " * generated by '" << prg.filename() << "'" << std::endl
	    << " *" << std::endl
	    << " * contains the converted file '" << fname << "'" << std::endl
	    << " */" << std::endl
	    << std::endl;
	return 0;
}

static int write_cpp_header(std::ostream& out, const fs::path& prg, const fs::path& file,
                            const std::string& name, const std::string& type)
{
	auto fname = file.filename().string();
	auto rc = write_comment_header(out, prg, fname);
	if (rc != 0)
		return rc;

	out << "#include <vector>" << std::endl
	    << "#include \"" << name << ".hpp\"" << std::endl
	    << std::endl
	    << "std::string " << name << "::name() {" << std::endl
	    << "return \"" << fname << "\";" << std::endl
	    << "}" << std::endl
	    << "std::string " << name << "::type() {" << std::endl
	    << "return \"" << type << "\";" << std::endl
	    << "}" << std::endl
	    << std::endl
	    << "const SDLResourceFile " << name << "::_initializer(type(), name(), init());"
	    << std::endl
	    << std::endl
	    << "std::vector<unsigned char> " << name << "::init() {" << std::endl
	    << "static const unsigned char data[] = {" << std::endl;

	return 0;
}

static int readwrite(std::ofstream& out, std::ifstream& ifs)
{
	size_t pos = 0;
	char c = 0;

	std::ios backup(nullptr);
	backup.copyfmt(out);

	while (ifs.read(&c, 1) && ifs.good())
	{
		unsigned val = c & 0xff;
		out << "0x" << std::hex << std::setfill('0') << std::setw(2) << val;
		if (ifs.peek() != EOF)
			out << ",";
		if ((pos++ % 16) == 15)
			out << std::endl;
	}

	out.copyfmt(backup);

	return 0;
}

static int write_cpp_trailer(std::ostream& out)
{
	out << std::endl;
	out << "};" << std::endl;
	out << std::endl;
	out << "return std::vector<unsigned char>(data, data + sizeof(data));" << std::endl;
	out << "}" << std::endl;
	return 0;
}

static int write_hpp_header(const fs::path& prg, const fs::path& file, const std::string& name,
                            const std::string& fname)
{
	std::ofstream out(file, std::ios::out);
	if (!out.is_open())
	{
		std::cerr << "Failed to open output file '" << file << "'" << std::endl;
		return -1;
	}
	auto rc = write_comment_header(out, prg, fname);
	if (rc != 0)
		return rc;

	out << "#pragma once" << std::endl
	    << std::endl
	    << "#include <vector>" << std::endl
	    << "#include <string>" << std::endl
	    << "#include \"sdl_resource_file.hpp\"" << std::endl
	    << std::endl
	    << "class " << name << " {" << std::endl
	    << "public:" << std::endl
	    << name << "() = delete;" << std::endl
	    << std::endl
	    << "static std::string name();" << std::endl
	    << "static std::string type();" << std::endl
	    << std::endl
	    << "private:" << std::endl
	    << "static std::vector<unsigned char> init();" << std::endl
	    << "static const SDLResourceFile _initializer;" << std::endl
	    << std::endl
	    << "};" << std::endl;
	return 0;
}

int main(int argc, char* argv[])
{
	fs::path prg(argv[0]);
	if (argc != 5)
	{
		usage(argv[0]);
		return -1;
	}

	fs::path file(argv[1]);
	std::string etype = argv[2];
	std::string cname = argv[3];
	fs::path dst(argv[4]);
	fs::path hdr(argv[4]);

	dst /= cname + ".cpp";
	hdr /= cname + ".hpp";

	std::ofstream out;
	out.open(dst);
	if (!out.is_open())
	{
		std::cerr << "Failed to open output file '" << dst << "'" << std::endl;
		return -2;
	}

	std::ifstream ifs(file, std::ios::in | std::ios::binary);
	if (!ifs.is_open())
	{
		std::cerr << "Failed to open input file '" << file << "'" << std::endl;
		return -3;
	}

	auto rc = write_cpp_header(out, prg, file, cname, etype);
	if (rc != 0)
		return -1;

	rc = readwrite(out, ifs);
	if (rc != 0)
		return rc;

	rc = write_cpp_trailer(out);
	if (rc != 0)
		return rc;
	return write_hpp_header(prg, hdr, cname, file.filename().string());
}
