#include "deck_manager.h"
#include "game.h"
#include "myfilesystem.h"
#include "network.h"

namespace ygo {

DeckManager deckManager;
std::vector<std::wstring> DeckManager::deckComments;
void DeckManager::LoadLFListSingle(const char* path) {
	auto cur = _lfList.rend();
	FILE* fp = myfopen(path, "r");
	char linebuf[256]{};
	wchar_t strBuffer[256]{};
	if(fp) {
		while(std::fgets(linebuf, sizeof linebuf, fp)) {
			if(linebuf[0] == '#')
				continue;
			if(linebuf[0] == '!') {
				auto len = std::strcspn(linebuf, "\r\n");
				linebuf[len] = 0;
				BufferIO::DecodeUTF8(&linebuf[1], strBuffer);
				LFList newlist;
				newlist.listName = strBuffer;
				newlist.hash = 0x7dfcee6a;
				_lfList.push_back(newlist);
				cur = _lfList.rbegin();
				continue;
			}
			if (cur == _lfList.rend())
				continue;
			char* pos = linebuf;
			errno = 0;
			auto result = std::strtoul(pos, &pos, 10);
			if (errno || result > UINT32_MAX)
				continue;
			if (pos == linebuf || *pos != ' ')
				continue;
			uint32_t code = static_cast<uint32_t>(result);
			errno = 0;
			int count = std::strtol(pos, &pos, 10);
			if (errno)
				continue;
			if (count < 0 || count > 2)
				continue;
			cur->content[code] = count;
			cur->hash = cur->hash ^ ((code << 18) | (code >> 14)) ^ ((code << (27 + count)) | (code >> (5 - count)));
		}
		std::fclose(fp);
	}
}
void DeckManager::LoadLFListSingle(irr::io::IReadFile* reader) {
    if (!reader)
        return;

    auto cur = _lfList.rend();
    char linebuf[256]{};
    wchar_t strBuffer[256]{};

    // 读取文件内容直到结束
    while (reader->getPos() < reader->getSize()) {
        // 手动读取一行
        int i = 0;
        char ch;
        while (i < sizeof(linebuf) - 1 && reader->getPos() < reader->getSize()) {
            reader->read(&ch, 1);
            if (ch == '\n' || ch == '\r') {
                // 处理换行符
                if (ch == '\r' && reader->getPos() < reader->getSize()) {
                    // 检查是否有\n
                    long pos = reader->getPos();
                    char nextCh;
                    reader->read(&nextCh, 1);
                    if (nextCh != '\n') {
                        reader->seek(pos); // 回退
                    }
                }
                break;
            }
            linebuf[i++] = ch;
        }
        linebuf[i] = '\0';

        if(linebuf[0] == '#')
            continue;
        if(linebuf[0] == '!') {
            auto len = std::strcspn(linebuf, "\r\n");
            linebuf[len] = 0;
            BufferIO::DecodeUTF8(&linebuf[1], strBuffer);
            LFList newlist;
            newlist.listName = strBuffer;
            newlist.hash = 0x7dfcee6a;
            _lfList.push_back(newlist);
            cur = _lfList.rbegin();
            continue;
        }
        if (cur == _lfList.rend())
            continue;
        char* pos = linebuf;
        errno = 0;
        auto result = std::strtoul(pos, &pos, 10);
        if (errno || result > UINT32_MAX)
            continue;
        if (pos == linebuf || *pos != ' ')
            continue;
        uint32_t code = static_cast<uint32_t>(result);
        errno = 0;
        int count = std::strtol(pos, &pos, 10);
        if (errno)
            continue;
        if (count < 0 || count > 2)
            continue;
        cur->content[code] = count;
        cur->hash = cur->hash ^ ((code << 18) | (code >> 14)) ^ ((code << (27 + count)) | (code >> (5 - count)));
    }
}
void DeckManager::LoadLFList(irr::android::InitOptions *options) {
    irr::io::path workingDir = options->getWorkDir();
	LoadLFListSingle((workingDir + path("/expansions/lflist.conf")).c_str());
	LoadLFListSingle((workingDir + path("/lflist.conf")).c_str());
	LFList nolimit;
	nolimit.listName = L"N/A";
	nolimit.hash = 0;
	_lfList.push_back(nolimit);
}
const wchar_t* DeckManager::GetLFListName(unsigned int lfhash) {
	auto lit = std::find_if(_lfList.begin(), _lfList.end(), [lfhash](const ygo::LFList& list) {
		return list.hash == lfhash;
	});
	if(lit != _lfList.end())
		return lit->listName.c_str();
	return dataManager.unknown_string;
}
const LFList* DeckManager::GetLFList(unsigned int lfhash) {
	auto lit = std::find_if(_lfList.begin(), _lfList.end(), [lfhash](const ygo::LFList& list) {
		return list.hash == lfhash;
	});
	if (lit != _lfList.end())
		return &(*lit);
	return nullptr;
}
static unsigned int checkAvail(unsigned int ot, unsigned int avail) {
	if((ot & avail) == avail)
		return 0;
	if((ot & AVAIL_OCG) && (avail != AVAIL_OCG))
		return DECKERROR_OCGONLY;
	if((ot & AVAIL_TCG) && (avail != AVAIL_TCG))
		return DECKERROR_TCGONLY;
	return DECKERROR_NOTAVAIL;
}
unsigned int DeckManager::CheckDeck(const Deck& deck, unsigned int lfhash, int rule) {
	std::unordered_map<int, int> ccount;
	// rule
	if(deck.main.size() < DECK_MIN_SIZE || deck.main.size() > DECK_MAX_SIZE)
		return (DECKERROR_MAINCOUNT << 28) | (unsigned)deck.main.size();
	if(deck.extra.size() > EXTRA_MAX_SIZE)
		return (DECKERROR_EXTRACOUNT << 28) | (unsigned)deck.extra.size();
	if(deck.side.size() > SIDE_MAX_SIZE)
		return (DECKERROR_SIDECOUNT << 28) | (unsigned)deck.side.size();
	auto lflist = GetLFList(lfhash);
	if (!lflist)
		return 0;
	auto& list = lflist->content;
	const unsigned int rule_map[6] = { AVAIL_OCG, AVAIL_TCG, AVAIL_SC, AVAIL_CUSTOM, AVAIL_OCGTCG, 0 };
	unsigned int avail = 0;
	if (rule >= 0 && rule < (int)(sizeof rule_map / sizeof rule_map[0]))
		avail = rule_map[rule];
	for (auto& cit : deck.main) {
		auto gameruleDeckError = checkAvail(cit->second.ot, avail);
		if(gameruleDeckError)
			return (gameruleDeckError << 28) | cit->first;
		if (cit->second.type & (TYPES_EXTRA_DECK | TYPE_TOKEN))
			return (DECKERROR_MAINCOUNT << 28);
		int code = cit->second.alias ? cit->second.alias : cit->first;
		ccount[code]++;
		int dc = ccount[code];
		if(dc > 3)
			return (DECKERROR_CARDCOUNT << 28) | cit->first;
		auto it = list.find(code);
		if(it != list.end() && dc > it->second)
			return (DECKERROR_LFLIST << 28) | cit->first;
	}
	for (auto& cit : deck.extra) {
		auto gameruleDeckError = checkAvail(cit->second.ot, avail);
		if(gameruleDeckError)
			return (gameruleDeckError << 28) | cit->first;
		if (!(cit->second.type & TYPES_EXTRA_DECK) || cit->second.type & TYPE_TOKEN)
			return (DECKERROR_EXTRACOUNT << 28);
		int code = cit->second.alias ? cit->second.alias : cit->first;
		ccount[code]++;
		int dc = ccount[code];
		if(dc > 3)
			return (DECKERROR_CARDCOUNT << 28) | cit->first;
		auto it = list.find(code);
		if(it != list.end() && dc > it->second)
			return (DECKERROR_LFLIST << 28) | cit->first;
	}
	for (auto& cit : deck.side) {
		auto gameruleDeckError = checkAvail(cit->second.ot, avail);
		if(gameruleDeckError)
			return (gameruleDeckError << 28) | cit->first;
		if (cit->second.type & TYPE_TOKEN)
			return (DECKERROR_SIDECOUNT << 28);
		int code = cit->second.alias ? cit->second.alias : cit->first;
		ccount[code]++;
		int dc = ccount[code];
		if(dc > 3)
			return (DECKERROR_CARDCOUNT << 28) | cit->first;
		auto it = list.find(code);
		if(it != list.end() && dc > it->second)
			return (DECKERROR_LFLIST << 28) | cit->first;
	}
	return 0;
}
uint32_t DeckManager::LoadDeck(Deck& deck, uint32_t dbuf[], int mainc, int sidec, bool is_packlist) {
	deck.clear();
	uint32_t errorcode = 0;
	auto& _datas = dataManager.GetDataTable();
	for(int i = 0; i < mainc; ++i) {
		auto code = dbuf[i];
		auto it = _datas.find(code);
		if(it == _datas.end()) {
			errorcode = code;
			continue;
		}
		auto& cd = it->second;
		if (cd.type & TYPE_TOKEN) {
			errorcode = code;
			continue;
		}
		if(is_packlist) {
			deck.main.push_back(it);
			continue;
		}
		if (cd.type & TYPES_EXTRA_DECK) {
			if (deck.extra.size() < EXTRA_MAX_SIZE)
				deck.extra.push_back(it);
		}
		else {
			if (deck.main.size() < DECK_MAX_SIZE)
				deck.main.push_back(it);
		}
	}
	for(int i = 0; i < sidec; ++i) {
		auto code = dbuf[mainc + i];
		auto it = _datas.find(code);
		if(it == _datas.end()) {
			errorcode = code;
			continue;
		}
		auto& cd = it->second;
		if (cd.type & TYPE_TOKEN) {
			errorcode = code;
			continue;
		}
		if(deck.side.size() < SIDE_MAX_SIZE)
			deck.side.push_back(it);
	}
	return errorcode;
}
uint32_t DeckManager::LoadDeckFromStream(Deck& deck, std::istringstream& deckStream, bool is_packlist) {
    // 清空之前的注释
    deckComments.clear();

    int ct = 0;
    int mainc = 0, sidec = 0;
    uint32_t cardlist[PACK_MAX_SIZE]{};
    bool is_side = false;
    std::string linebuf;

    while (std::getline(deckStream, linebuf, '\n') && ct < PACK_MAX_SIZE) {
        // 缓存以##或###开头的注释行
        if (linebuf.length() >= 2 && linebuf[0] == '#' && linebuf[1] == '#') {
            wchar_t wline[256];
            BufferIO::DecodeUTF8(linebuf.c_str(), wline);
            deckComments.push_back(wline);
            continue;
        }

        if (linebuf[0] == '!') {
            is_side = true;
            continue;
        }
        if (linebuf[0] < '0' || linebuf[0] > '9')
            continue;
        errno = 0;
        auto code = std::strtoul(linebuf.c_str(), nullptr, 10);
        if (errno || code > UINT32_MAX)
            continue;
        cardlist[ct++] = code;
        if (is_side)
            ++sidec;
        else
            ++mainc;
    }
    return LoadDeck(deck, cardlist, mainc, sidec, is_packlist);
}
bool DeckManager::LoadSide(Deck& deck, uint32_t dbuf[], int mainc, int sidec) {
	std::unordered_map<uint32_t, int> pcount;
	std::unordered_map<uint32_t, int> ncount;
	for(size_t i = 0; i < deck.main.size(); ++i)
		pcount[deck.main[i]->first]++;
	for(size_t i = 0; i < deck.extra.size(); ++i)
		pcount[deck.extra[i]->first]++;
	for(size_t i = 0; i < deck.side.size(); ++i)
		pcount[deck.side[i]->first]++;
	Deck ndeck;
	LoadDeck(ndeck, dbuf, mainc, sidec);
	if (ndeck.main.size() != deck.main.size() || ndeck.extra.size() != deck.extra.size() || ndeck.side.size() != deck.side.size())
		return false;
	for(size_t i = 0; i < ndeck.main.size(); ++i)
		ncount[ndeck.main[i]->first]++;
	for(size_t i = 0; i < ndeck.extra.size(); ++i)
		ncount[ndeck.extra[i]->first]++;
	for(size_t i = 0; i < ndeck.side.size(); ++i)
		ncount[ndeck.side[i]->first]++;
	for (auto& cdit : ncount)
		if (cdit.second != pcount[cdit.first])
			return false;
	deck = ndeck;
	return true;
}
void DeckManager::GetCategoryPath(wchar_t* ret, int index, const wchar_t* text, bool showPack) {//hide packlist if showing on duelling ready
	wchar_t catepath[256];
	switch(index) {
	case DECK_CATEGORY_PACK:
		if (showPack) {
			myswprintf(catepath, L"./pack");
		} else {
			myswprintf(catepath, L"./windbot/Decks");
		}
		break;
	case DECK_CATEGORY_BOT:
		if (showPack) {
			myswprintf(catepath, L"./windbot/Decks");
		} else {
			myswprintf(catepath, L"./deck");
		}
		break;
	case -1:
	case DECK_CATEGORY_NONE:
	case DECK_CATEGORY_SEPARATOR:
		if (showPack) {
			myswprintf(catepath, L"./deck");
		} else {
			myswprintf(catepath, L"./deck/%ls", text);
		}
		break;
	default:
		myswprintf(catepath, L"./deck/%ls", text);
	}
	BufferIO::CopyWStr(catepath, ret, 256);
}
void DeckManager::GetDeckFile(wchar_t* ret, irr::gui::IGUIComboBox* cbCategory, irr::gui::IGUIComboBox* cbDeck) {
	wchar_t filepath[256];
	wchar_t catepath[256];
	const wchar_t* deckname = cbDeck->getItem(cbDeck->getSelected());
	if(deckname != nullptr) {
		GetCategoryPath(catepath, cbCategory->getSelected(), cbCategory->getText(), cbCategory == mainGame->cbDBCategory);
		myswprintf(filepath, L"%ls/%ls.ydk", catepath, deckname);
		BufferIO::CopyWStr(filepath, ret, 256);
	}
	else {
		BufferIO::CopyWStr(L"", ret, 256);
	}
}
FILE* DeckManager::OpenDeckFile(const wchar_t* file, const char* mode) {
	FILE* fp = mywfopen(file, mode);
	return fp;
}
irr::io::IReadFile* DeckManager::OpenDeckReader(const wchar_t* file) {
	char file2[256];
	BufferIO::EncodeUTF8(file, file2);
	auto reader = dataManager.FileSystem->createAndOpenFile(file2);
	return reader;
}
bool DeckManager::LoadCurrentDeck(std::istringstream& deckStream, bool is_packlist) {
	LoadDeckFromStream(current_deck, deckStream, is_packlist);
	return true;  // the above LoadDeck has return value but we ignore it here for now
}
bool DeckManager::LoadCurrentDeck(const wchar_t* file, bool is_packlist) {
	current_deck.clear();
	if (!file[0])
		return false;
	char deckBuffer[MAX_YDK_SIZE]{};
	auto reader = OpenDeckReader(file);
	if(!reader) {
		wchar_t localfile[256];
		myswprintf(localfile, L"./deck/%ls.ydk", file);
		reader = OpenDeckReader(localfile);
	}
	if(!reader && !mywcsncasecmp(file, L"./pack", 6)) {
		wchar_t zipfile[256];
		myswprintf(zipfile, L"%ls", file + 2);
		reader = OpenDeckReader(zipfile);
	}
	if(!reader)
		return false;
	std::memset(deckBuffer, 0, sizeof deckBuffer);
	int size = reader->read(deckBuffer, sizeof deckBuffer);
	reader->drop();
	if (size >= (int)sizeof deckBuffer) {
		return false;
	}
	std::istringstream deckStream(deckBuffer);
	LoadDeckFromStream(current_deck, deckStream, is_packlist);
	return true;  // the above function has return value but we ignore it here for now
}
bool DeckManager::LoadCurrentDeck(irr::gui::IGUIComboBox* cbCategory, irr::gui::IGUIComboBox* cbDeck) {
	wchar_t filepath[256];
	GetDeckFile(filepath, cbCategory, cbDeck);
	bool is_packlist = (cbCategory->getSelected() == DECK_CATEGORY_PACK);
	if(!LoadCurrentDeck(filepath, is_packlist))
		return false;
	if (mainGame->is_building)
		mainGame->deckBuilder.RefreshPackListScroll();
	return true;
}
/**
 * @brief 将卡牌组保存到字符串流中
 * @param deck 要保存的卡牌组对象
 * @param deckStream 用于存储卡牌组数据的字符串流
 *
 * 该函数将卡牌组按照特定格式保存到字符串流中，
 * 包括主卡组、额外卡组和副卡组的内容
 */
void DeckManager::SaveDeck(const Deck& deck, std::stringstream& deckStream) {
	// 写入文件头标识和创建者信息
	deckStream << "#created by ..." << std::endl;

	// 保存主卡组卡片
	deckStream << "#main" << std::endl;
	for(size_t i = 0; i < deck.main.size(); ++i)
		deckStream << deck.main[i]->first << std::endl;

	// 保存额外卡组卡片
	deckStream << "#extra" << std::endl;
	for(size_t i = 0; i < deck.extra.size(); ++i)
		deckStream << deck.extra[i]->first << std::endl;

	// 保存副卡组卡片
	deckStream << "!side" << std::endl;
	for(size_t i = 0; i < deck.side.size(); ++i)
		deckStream << deck.side[i]->first << std::endl;

    // 将缓存的注释写入文件末尾
    if (!deckComments.empty()) {
        deckStream << "\r\n";  // 添加换行符隔断
        for (const auto& comment : deckComments) {
            char utf8line[512];
            BufferIO::EncodeUTF8(comment.c_str(), utf8line);
            deckStream << utf8line << std::endl;
        }
    }
}

/**
 * @brief 保存卡组数据到指定文件
 * @param deck 要保存的卡组对象
 * @param file 保存的目标文件路径
 * @return 保存成功返回true，失败返回false
 */
bool DeckManager::SaveDeck(const Deck& deck, const wchar_t* file) {
	// 检查并创建deck目录
	if(!FileSystem::IsDirExists(L"./deck") && !FileSystem::MakeDir(L"./deck"))
		return false;

	// 打开卡组文件用于写入
	FILE* fp = OpenDeckFile(file, "w");
	if(!fp)
		return false;

	// 将卡组数据序列化到字符串流中
	std::stringstream deckStream;
	SaveDeck(deck, deckStream);

	// 将序列化的数据写入文件
	std::fputs(deckStream.str().c_str(), fp);
	std::fclose(fp);
	return true;
}
bool DeckManager::DeleteDeck(const wchar_t* file) {
	return FileSystem::RemoveFile(file);
}
bool DeckManager::CreateCategory(const wchar_t* name) {
	if(!FileSystem::IsDirExists(L"./deck") && !FileSystem::MakeDir(L"./deck"))
		return false;
	if(name[0] == 0)
		return false;
	wchar_t localname[256];
	myswprintf(localname, L"./deck/%ls", name);
	return FileSystem::MakeDir(localname);
}
bool DeckManager::RenameCategory(const wchar_t* oldname, const wchar_t* newname) {
	if(!FileSystem::IsDirExists(L"./deck") && !FileSystem::MakeDir(L"./deck"))
		return false;
	if(newname[0] == 0)
		return false;
	wchar_t oldlocalname[256];
	wchar_t newlocalname[256];
	myswprintf(oldlocalname, L"./deck/%ls", oldname);
	myswprintf(newlocalname, L"./deck/%ls", newname);
	return FileSystem::Rename(oldlocalname, newlocalname);
}
bool DeckManager::DeleteCategory(const wchar_t* name) {
	wchar_t localname[256];
	myswprintf(localname, L"./deck/%ls", name);
	if(!FileSystem::IsDirExists(localname))
		return false;
	return FileSystem::DeleteDir(localname);
}
bool DeckManager::SaveDeckArray(const DeckArray& deck, const wchar_t* name) {
	if (!FileSystem::IsDirExists(L"./deck") && !FileSystem::MakeDir(L"./deck"))
		return false;
	FILE* fp = OpenDeckFile(name, "w");
	if (!fp)
		return false;
	std::fprintf(fp, "#created by ...\n#main\n");
	for (const auto& code : deck.main)
		std::fprintf(fp, "%u\n", code);
	std::fprintf(fp, "#extra\n");
	for (const auto& code : deck.extra)
		std::fprintf(fp, "%u\n", code);
	std::fprintf(fp, "!side\n");
	for (const auto& code : deck.side)
		std::fprintf(fp, "%u\n", code);
	std::fclose(fp);
	return true;
}
int DeckManager::TypeCount(std::vector<code_pointer> list, unsigned int ctype) {
	int res = 0;
	for(size_t i = 0; i < list.size(); ++i) {
		code_pointer cur = list[i];
		if(cur->second.type & ctype)
			res++;
	}
	return res;
}
}
