#pragma once
#include <Windows.h>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <rapidjson\document.h>
#include <rapidjson\writer.h>
#include <rapidjson\stringbuffer.h>
#include <codecvt>
#include <locale>
#include "nstring.h"
#include "../priformatcli/priformatcli.h"
#include "../pkgread/pkgread.h"

static std::string ws2utf8 (const std::wstring &ws)
{
	std::wstring_convert <std::codecvt_utf8 <wchar_t>> conv;
	return conv.to_bytes (ws);
}
static rapidjson::Value ver_to_json (const VERSION &ver, rapidjson::Document::AllocatorType &alloc)
{
	rapidjson::Value obj (rapidjson::kObjectType);
	obj.AddMember ("major", ver.major, alloc);
	obj.AddMember ("minor", ver.minor, alloc);
	obj.AddMember ("build", ver.build, alloc);
	obj.AddMember ("revision", ver.revision, alloc);
	return obj;
}

struct pkginfo
{
	std::wstring filepath;
	bool valid = false;
	// 包的类型：Appx 包还是 AppxBundle 包。返回 PKGTYPE_* 宏 
	WORD type = 0;
	// 包的类型：是应用包、框架包还是资源包。
	// 对于 AppxBundle 包永远返回是资源包（因为框架包无法打包成 AppxBundle，资源包必须与应用包一同打包进 AppxBundle）。
	// 返回 PKGROLE_* 宏。
	WORD role = 0;
	struct 
	{
		std::wstring name,
			publisher,
			package_family_name,
			package_full_name,
			resource_id;
		VERSION version, realver;
		DWORD architecture;
	} identity;
	// x86: 0
	// x64: 9
	// Arm: 5
	// Neutral: 11
	// Arm64: 12
	std::set <WORD> get_architectures () const
	{
		std::set <WORD> ret;
		switch (type)
		{
			case PKGTYPE_APPX: {
				switch (identity.architecture)
				{
					case PKG_ARCHITECTURE_X86: ret.insert (0); break;
					case PKG_ARCHITECTURE_X64: ret.insert (9); break;
					case PKG_ARCHITECTURE_ARM: ret.insert (5); break;
					case PKG_ARCHITECTURE_ARM64: ret.insert (12); break;
					case PKG_ARCHITECTURE_NEUTRAL: ret.insert (11); break;
				}
			} break;
			case PKGTYPE_BUNDLE: {
				if (identity.architecture & PKG_ARCHITECTURE_X86) ret.insert (0);
				if (identity.architecture & PKG_ARCHITECTURE_X64) ret.insert (9);
				if (identity.architecture & PKG_ARCHITECTURE_ARM) ret.insert (5);
				if (identity.architecture & PKG_ARCHITECTURE_ARM64) ret.insert (12);
				// 由于架构历史发展原因：这样做是对的。
				if (identity.architecture & (PKG_ARCHITECTURE_X86 | PKG_ARCHITECTURE_X64 | PKG_ARCHITECTURE_ARM))
				{ ret.clear (); ret.insert (11); }
				if (identity.architecture & PKG_ARCHITECTURE_NEUTRAL) { ret.clear (); ret.insert (11); }
			}
		}
		return ret;
	}
	struct
	{
		std::wstring display_name;
		std::wstring publisher_display_name;
		std::wstring description;
		std::wstring logo;
		std::wstring logo_base64;
		bool framework = false;
		bool resource_package = false;
	} properties;
	struct application: std::map <std::wnstring, std::wstring>
	{
		using base = std::map <std::wnstring, std::wstring>;
		using base::base;
		application () = default;
		std::wstring user_model_id () { return this->at (L"AppUserModelID"); }
		friend bool operator == (application &a1, application &a2) { return a1.user_model_id () == a2.user_model_id (); }
		friend bool operator != (application &a1, application &a2) { return a1.user_model_id () != a2.user_model_id (); }
		explicit operator bool () { return this->user_model_id ().empty (); }
	};
	std::vector <application> applications;
	struct
	{
		std::vector <std::wnstring> capabilities_name;
		std::vector <std::wnstring> device_capabilities;
	} capabilities;
	struct dependency
	{
		std::wstring name, publisher;
		VERSION vermin;
		dependency (const std::wstring &name = L"", const std::wstring &pub = L"", const VERSION &ver = VERSION ()):
			name (name), publisher (pub), vermin (ver) {}
	};
	std::vector <dependency> dependencies;
	struct
	{
		std::set <std::wnstring> languages;
		std::set <UINT32> scales;
		// PKG_RESOURCES_DXFEATURE_LEVEL* 前缀常量
		std::set <DWORD> dx_feature_levels;
	} resources;
	struct
	{
		VERSION os_min_version, os_max_version_tested;
		std::wstring os_min_version_description, os_max_version_tested_description;
	} prerequisites;
	static pkginfo parse (const std::wstring &filepath)
	{
		pkginfo pkg;
		pkg.filepath = filepath;
		package_reader pread (filepath);
		pread.use_pri (true);
		pread.enable_pri_convert (true);
		pkg.valid = pread.valid ();
		if (!pkg.valid) return pkg;
		pkg.type = pread.package_type ();
		pkg.role = pread.package_role ();
		{
			auto id = pread.get_identity ();
			pkg.identity.architecture = id.architecture ();
			pkg.identity.name = id.name ();
			pkg.identity.package_family_name = id.package_family_name ();
			pkg.identity.package_full_name = id.package_full_name ();
			pkg.identity.publisher = id.publisher ();
			pkg.identity.resource_id = id.resource_id ();
			pkg.identity.version = id.version ();
			pkg.identity.realver = id.version (true);
		}
		{
			auto prop = pread.get_properties ();
			pkg.properties.description = prop.description ();
			pkg.properties.display_name = prop.display_name ();
			pkg.properties.framework = prop.framework ();
			pkg.properties.logo = prop.logo ();
			pkg.properties.logo_base64 = prop.logo_base64 ();
			pkg.properties.publisher_display_name = prop.publisher_display_name ();
			pkg.properties.resource_package = prop.resource_package ();
		}
		{
			auto prer = pread.get_prerequisites ();
			pkg.prerequisites.os_min_version = prer.os_min_version ();
			pkg.prerequisites.os_max_version_tested = prer.os_max_version_tested ();
			pkg.prerequisites.os_min_version_description = prer.os_min_version_description ();
			pkg.prerequisites.os_max_version_tested_description = prer.os_max_version_tested_description ();
		}
		{
			auto apps = pread.get_applications ();
			std::vector <package_reader::application> appmap;
			apps.get (appmap);
			pkg.applications.reserve (appmap.size ());
			for (auto &mp : appmap)
			{
				application app;
				for (auto &it : mp)
				{
					if (std::find (g_filepathitems.begin (), g_filepathitems.end (), it.first) != g_filepathitems.end ())
						app [it.first] = mp.newat_base64 (it.first);
					else app [it.first] = mp.newat (it.first);
				}
				pkg.applications.push_back (app);
			}
		}
		{
			auto caps = pread.get_capabilities ();
			std::vector <std::wstring> vws;
			caps.capabilities_name (vws);
			pkg.capabilities.capabilities_name.reserve (vws.size ());
			for (auto &it : vws) pkg.capabilities.capabilities_name.push_back (it);
			vws.clear ();
			caps.device_capabilities (vws);
			pkg.capabilities.device_capabilities.reserve (vws.size ());
			for (auto &it : vws) pkg.capabilities.device_capabilities.push_back (it);
		}
		{
			std::vector <package_reader::dependency> deps;
			auto depr = pread.get_dependencies ();
			pkg.dependencies.reserve (depr.get (deps));
			for (auto &it : deps)
			{
				dependency dep;
				dep.name = it.name;
				dep.publisher = it.publisher;
				dep.vermin = it.vermin;
				pkg.dependencies.push_back (dep);
			}
		}
		{
			auto ress = pread.get_resources ();
			{
				std::vector <std::wstring> langs;
				ress.languages (langs);
				for (auto &it : langs) pkg.resources.languages.insert (it);
			}
			{
				std::vector <UINT32> ss;
				ress.scales (ss);
				for (auto &it : ss) pkg.resources.scales.insert (it);
			}
			{
				DWORD dx = ress.dx_feature_level ();
				if (dx & PKG_RESOURCES_DXFEATURE_LEVEL9)
					pkg.resources.dx_feature_levels.insert (PKG_RESOURCES_DXFEATURE_LEVEL9);
				else if (dx & PKG_RESOURCES_DXFEATURE_LEVEL10)
					pkg.resources.dx_feature_levels.insert (PKG_RESOURCES_DXFEATURE_LEVEL10);
				else if (dx & PKG_RESOURCES_DXFEATURE_LEVEL11)
					pkg.resources.dx_feature_levels.insert (PKG_RESOURCES_DXFEATURE_LEVEL11);
				else if (dx & PKG_RESOURCES_DXFEATURE_LEVEL12)
					pkg.resources.dx_feature_levels.insert (PKG_RESOURCES_DXFEATURE_LEVEL12);
			}
		}
		return pkg;
	}
	std::wstring parseJson ()
	{
		using namespace rapidjson;
		Document doc;
		doc.SetObject ();
		auto &alloc = doc.GetAllocator ();
		doc.AddMember ("filepath", Value (ws2utf8 (filepath).c_str (), alloc), alloc);
		doc.AddMember ("valid", valid, alloc);
		doc.AddMember ("type", type, alloc);
		doc.AddMember ("role", role, alloc);
		{
			Value obj (kObjectType);
			obj.AddMember ("name", Value (ws2utf8 (identity.name).c_str (), alloc), alloc);
			obj.AddMember ("publisher", Value (ws2utf8 (identity.publisher).c_str (), alloc), alloc);
			obj.AddMember ("package_family_name", Value (ws2utf8 (identity.package_family_name).c_str (), alloc), alloc);
			obj.AddMember ("package_full_name", Value (ws2utf8 (identity.package_full_name).c_str (), alloc), alloc);
			obj.AddMember ("resource_id", Value (ws2utf8 (identity.resource_id).c_str (), alloc), alloc);
			obj.AddMember ("architecture", (uint32_t)identity.architecture, alloc);
			obj.AddMember ("version", ver_to_json (identity.version, alloc), alloc);
			obj.AddMember ("realver", ver_to_json (identity.realver, alloc), alloc);
			doc.AddMember ("identity", obj, alloc);
		}
		{
			Value obj (kObjectType);
			obj.AddMember ("display_name", Value (ws2utf8 (properties.display_name).c_str (), alloc), alloc);
			obj.AddMember ("publisher_display_name", Value (ws2utf8 (properties.publisher_display_name).c_str (), alloc), alloc);
			obj.AddMember ("description", Value (ws2utf8 (properties.description).c_str (), alloc), alloc);
			obj.AddMember ("logo", Value (ws2utf8 (properties.logo).c_str (), alloc), alloc);
			obj.AddMember ("logo_base64", Value (ws2utf8 (properties.logo_base64).c_str (), alloc), alloc);
			obj.AddMember ("framework", properties.framework, alloc);
			obj.AddMember ("resource_package", properties.resource_package, alloc);
			doc.AddMember ("properties", obj, alloc);
		}
		{
			Value arr (kArrayType);
			for (auto &app : applications)
			{
				Value obj (kObjectType);
				for (auto &it : app)
				{
					std::string key = ws2utf8 (it.first);
					std::string val = ws2utf8 (it.second);
					obj.AddMember (
						Value (key.c_str (), alloc),
						Value (val.c_str (), alloc),
						alloc
					);
				}
				arr.PushBack (obj, alloc);
			}
			doc.AddMember ("applications", arr, alloc);
		}
		{
			Value obj (kObjectType);
			{
				Value arr (kArrayType);
				for (auto &it : capabilities.capabilities_name)
					arr.PushBack (Value (ws2utf8 (it).c_str (), alloc), alloc);
				obj.AddMember ("capabilities_name", arr, alloc);
			}
			{
				Value arr (kArrayType);
				for (auto &it : capabilities.device_capabilities) arr.PushBack (Value (ws2utf8 (it).c_str (), alloc), alloc);
				obj.AddMember ("device_capabilities", arr, alloc);
			}
			doc.AddMember ("capabilities", obj, alloc);
		}
		{
			Value arr (kArrayType);

			for (auto& d : dependencies)
			{
				Value obj (kObjectType);
				obj.AddMember ("name", Value (ws2utf8 (d.name).c_str (), alloc), alloc);
				obj.AddMember ("publisher", Value (ws2utf8 (d.publisher).c_str (), alloc), alloc);
				obj.AddMember ("vermin", ver_to_json (d.vermin, alloc), alloc);
				arr.PushBack (obj, alloc);
			}
			doc.AddMember ("dependencies", arr, alloc);
		}
		{
			Value obj (kObjectType);
			{
				Value arr (kArrayType);
				for (auto &it : resources.languages) arr.PushBack (Value (ws2utf8 (it).c_str (), alloc), alloc);
				obj.AddMember ("languages", arr, alloc);
			}
			{
				Value arr (kArrayType);
				for (auto &it : resources.scales) arr.PushBack ((uint32_t)it, alloc);
				obj.AddMember ("scales", arr, alloc);
			}
			{
				Value arr (kArrayType);
				for (auto &it : resources.dx_feature_levels) arr.PushBack ((uint32_t)it, alloc);
				obj.AddMember ("dx_feature_levels", arr, alloc);
			}
			doc.AddMember ("resources", obj, alloc);
		}
		{
			Value obj (kObjectType);
			obj.AddMember ("os_min_version", ver_to_json (prerequisites.os_min_version, alloc), alloc);
			obj.AddMember ("os_max_version_tested", ver_to_json (prerequisites.os_max_version_tested, alloc), alloc);
			obj.AddMember ("os_min_version_description", Value (ws2utf8 (prerequisites.os_min_version_description).c_str (), alloc), alloc);
			obj.AddMember ("os_max_version_tested_description", Value (ws2utf8 (prerequisites.os_max_version_tested_description).c_str (), alloc), alloc);
			doc.AddMember ("prerequisites", obj, alloc);
		}
		StringBuffer buffer;
		Writer <StringBuffer> writer (buffer);
		doc.Accept (writer);
		std::string utf8 = buffer.GetString ();
		std::wstring_convert <std::codecvt_utf8 <wchar_t>> conv;
		return conv.from_bytes (utf8);
	}
};
