#include <windows.h>
#include <wingdi.h>
#include <commctrl.h>
#include "components/chatwnd.h"
#include "discordtypes.h"
#include "http.h"
#include "ws.h"
#include "discordapi.h"
#include <cjson/cJSON.h>
#include <winnt.h>
#include "config.h"
#include "globals.h"
#include "hmap/hashmap.h"
#include "components/snoctrl.h"
#include "components/pnghlp.h"
#include "rc/res.h"
#include "login.h"
#define GUILD_PADDING 4
static DiscordMessage *PendingRndMsg = NULL;
static DiscordMessage *LastRndMsg = NULL; // rnd is render NOT random :sob:
char *curChannel;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
static HWND hWnd;
static HWND hMsg;
static HWND hMsgList;
static HWND hSend;
static HWND hChTree;
static HWND hDMsList;
static HWND hGSel;
static HWND hPfpArea;
static HWND hMemList;
static HIMAGELIST hSidePfps;
HINSTANCE hInstance;
SSL *GatewaySSL;
char *token;
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, PSTR pCmdLine,
				   int nCmdShow) {
#ifdef test_token
	token = test_token;
#else
	// prompt for token
	HKEY hKey;
	RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\Backcord", 0, NULL,
					REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &hKey,
					NULL);

	DWORD type, size = 0;
	LONG qres = RegQueryValueExA(hKey, "Token", NULL, &type, NULL, &size);

	if (qres == ERROR_SUCCESS) {
		token = malloc(size);
		RegQueryValueExA(hKey, "Token", NULL, &type, (BYTE *)token, &size);
	} else {
		token = PromptToken();
		RegSetValueExA(hKey, "Token", 0, REG_SZ, (const BYTE *)token,
					   (DWORD)(strlen(token) + 1));
	}

	RegCloseKey(hKey);

#endif
	INITCOMMONCONTROLSEX icex;
	icex.dwICC = ICC_WIN95_CLASSES | ICC_STANDARD_CLASSES | ICC_BAR_CLASSES |
				 ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES;
	InitCommonControls();
	InitSnowsControls();
	hSidePfps = ImageList_Create(32, 32, ILC_COLOR32 | ILC_MASK, 12, 1);
	hInstance = hinstance;
	// register the window claws
	const char CLASS_NAME[] = "BackcordMain";

	WNDCLASS wc = {};

	wc.lpfnWndProc = WindowProc;
	wc.hInstance = hInstance;
	wc.lpszClassName = CLASS_NAME;
	wc.hCursor = LoadCursorA(NULL, IDC_ARROW);
	RegisterClass(&wc);
	hWnd = CreateWindowEx(0,				   // Optional window styles.
						  CLASS_NAME,		   // Window class
						  "Backcord",		   // Window text
						  WS_OVERLAPPEDWINDOW, // Window style

						  // Size and position
						  CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,

						  NULL,		 // Parent window
						  NULL,		 // Menu
						  hInstance, // Instance handle
						  NULL		 // Additional application data
	);

	if (hWnd == NULL) {
		return 0;
	}
	ShowWindow(hWnd, nCmdShow);

	MSG msg = {};
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return 0;
}
BOOL identify = FALSE;
void onDiscordGuildLoad(DiscordGuild *guild, char *id) {

	GUIGuild uigld;
	uigld.icon = NULL;
	uigld.id = id;
	uigld.title = guild->name;
	uigld.data = guild;
	uigld.MentionCount = guild->MentionCount;
	GuildView_InsertGuild(hGSel, uigld);

	if (guild->IconHash) {
		char tempPath[MAX_PATH];
		DWORD len = GetTempPathA(MAX_PATH, tempPath);
		char *path = malloc(MAX_PATH + strlen(guild->id) + 1);
		wsprintfA(path, "%s%s.png", tempPath, guild->IconHash);
		if (GetFileAttributesA(path) == INVALID_FILE_ATTRIBUTES) {
			char *path = DiscordFetchTmpGuildIcon(guild->id,
												  guild->IconHash); // testing
		}
		GuildView_SetIcon(hGSel, LoadPNGImage(path), id);
		free(path);
	}
}
void onDiscordUpdatedGuildReadState(DiscordGuild guild) {
	GuildView_SetMentionCount(hGSel, guild.id, guild.MentionCount);
}
static uint64_t channel_hash(const void *item, uint64_t seed0, uint64_t seed1) {
	const ChannelUIEntry *e = item;
	return hashmap_sip(e->id, strlen(e->id), seed0, seed1);
}
static int channel_compare(const void *a, const void *b, void *udata) {
	return strcmp(((ChannelUIEntry *)a)->id, ((ChannelUIEntry *)b)->id);
}

void onDiscordReceiveMessage(DiscordMessage msg) {
	if (curChannel && strcmp(msg.channelID, curChannel) == 0) {
		if (msg.author.avatar) {
			char tempPath[MAX_PATH];
			DWORD len = GetTempPathA(MAX_PATH, tempPath);
			char *path = malloc(MAX_PATH + strlen(msg.author.avatar) + 1);
			wsprintfA(path, "%s%s.png", tempPath, msg.author.avatar);
			if (GetFileAttributesA(path) == INVALID_FILE_ATTRIBUTES) {
				char *path = DiscordFetchTmpPfp(msg.author.id,
												msg.author.avatar); // testing
			}
			ChatView_SetUserPfp(path, msg.author.id);
		}
		InsertChatMessage(msg, hMsgList); // the backend provides strdup
	}
}
void *msgoldwndproc;
LRESULT CALLBACK MsgBarSubclassProc(HWND hwnd, UINT msg, WPARAM wParam,
									LPARAM lParam) {
	if (msg == WM_KEYDOWN) {
		if (wParam == VK_RETURN) {
			if (!(GetKeyState(VK_SHIFT) &
				  0x8000)) // if no shift then do cool stuff
			{
				char *msgcontent = malloc(GetWindowTextLength(hMsg) + 1);
				GetWindowText(hMsg, msgcontent, GetWindowTextLength(hMsg) + 1);
				DiscordSendMessage(curChannel, msgcontent);

				SetWindowText(hwnd, ""); // clear input
				return 0;				 // preventing uhh newline
			}
		}
	}
	return CallWindowProc(msgoldwndproc, hwnd, msg, wParam, lParam);
}
HBITMAP ResizeBitmap(HBITMAP hOrig, int w, int h) {
	BITMAP bm;
	GetObject(hOrig, sizeof(bm), &bm);

	HDC hdcScreen = GetDC(NULL);
	HDC hdcSrc = CreateCompatibleDC(hdcScreen);
	HDC hdcDst = CreateCompatibleDC(hdcScreen);
	HBITMAP hResized = CreateCompatibleBitmap(hdcScreen, w, h);

	SelectObject(hdcSrc, hOrig);
	SelectObject(hdcDst, hResized);

	SetStretchBltMode(hdcDst, HALFTONE);
	SetBrushOrgEx(hdcDst, 0, 0, NULL);

	StretchBlt(hdcDst, 0, 0, w, h, hdcSrc, 0, 0, bm.bmWidth, bm.bmHeight,
			   SRCCOPY);

	DeleteDC(hdcSrc);
	DeleteDC(hdcDst);
	ReleaseDC(NULL, hdcScreen);
	DeleteObject(hOrig);

	return hResized;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
							LPARAM lParam) {
	switch (uMsg) {
	case WM_CREATE:
		InitHTTP();
		OpenWebSocket("gateway.discord.gg", "/?encoding=json&v=9", &GatewaySSL);

		RECT rc;
		GetClientRect(hwnd, &rc);
		hGSel = CreateWindowEx(0,				// Optional window styles.
							   "BackcordGuild", // Window class
							   "",				// Window text
							   WS_CHILD | WS_VISIBLE | LVS_LIST, // Window style

							   // Size and position
							   0, 0, 75, rc.bottom - rc.top - 80,

							   hwnd,	   // Parent window
							   (HMENU)102, // Menu
							   hInstance,  // Instance handle
							   NULL		   // Additional application data
		);
		GuildView_SetDMsIcon(
			hGSel, LoadPNGImageFromResource(hInstance, IDI_BACKCORDIC));
		hChTree = CreateWindowExA(WS_EX_CLIENTEDGE, WC_TREEVIEWA, NULL,
								  WS_CHILD | WS_VISIBLE | TVS_HASLINES |
									  TVS_LINESATROOT | TVS_HASBUTTONS,
								  75, 0, 128, rc.bottom - rc.top - 80, hwnd,
								  (HMENU)103, hInstance, NULL);
		hDMsList = CreateWindowExA(
			WS_EX_CLIENTEDGE, WC_LISTVIEWA, NULL, WS_CHILD | WS_VISIBLE, 75, 0,
			128, rc.bottom - rc.top - 80, hwnd, (HMENU)103, hInstance, NULL);
		ShowWindow(hDMsList, SW_HIDE);
		hMsgList = CreateWindowEx(0,			  // Optional window styles.
								  "BackcordChat", // Window class
								  "",			  // Window text
								  WS_CHILD | WS_VISIBLE, // Window style

								  // Size and position
								  75 + 128, 0, rc.right - 128 - 75,
								  rc.bottom - rc.top - 25,

								  hwnd,		  // Parent window
								  (HMENU)105, // Menu
								  hInstance,  // Instance handle
								  NULL);	  // Additional application data

		hPfpArea = CreateWindowEx(
			0,				// Optional window styles.
			"BUTTON",		// Window class
			"User Profile", // Window text
			ES_MULTILINE | WS_CHILD | WS_VISIBLE | BS_GROUPBOX, // Window style

			// Size and position
			0, rc.bottom - rc.top - 80, 128 + 75, 80,

			hwnd,	   // Parent window
			NULL,	   // Menu
			hInstance, // Instance handle
			NULL	   // Additional application data
		);
		SendMessage(hPfpArea, WM_SETFONT, (WPARAM)regfont, 0);
		hMsg = CreateWindowEx(WS_EX_CLIENTEDGE, // Optional window styles.
							  "EDIT",			// Window class
							  "",				// Window text
							  ES_MULTILINE | ES_AUTOVSCROLL | WS_CHILD |
								  WS_VISIBLE, // Window style

							  // Size and position
							  128 + 75, rc.bottom - rc.top - 25,
							  rc.right - rc.left - 128 - 75 - 25, 25,

							  hwnd,		  // Parent window
							  (HMENU)104, // Menu
							  hInstance,  // Instance handle
							  NULL		  // Additional application data
		);
		SendMessage(hMsg, WM_SETFONT, (WPARAM)regfont, 0);
		msgoldwndproc = (void *)SetWindowLongPtr(hMsg, GWLP_WNDPROC,
												 (long)MsgBarSubclassProc);
		hSend = CreateWindowEx(
			0,									   // Optional window styles.
			"BUTTON",							   // Window class
			"E",								   // Window text
			BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE, // Window style

			// Size and position
			rc.right - 25, rc.bottom - rc.top - 25, 25, 25,

			hwnd,		  // Parent window
			(HMENU)(101), // Menu
			hInstance,	  // Instance handle
			NULL		  // Additional application data
		);
		SendMessage(hSend, WM_SETFONT, (WPARAM)regfont, 0);
		return 0;
		break;
	case WM_SIZE: {
		int width = LOWORD(lParam);
		int height = HIWORD(lParam);
		SetWindowPos(hPfpArea, NULL, 0, height - 80, 0, 0,
					 SWP_NOSIZE | SWP_NOZORDER);
		SetWindowPos(hGSel, NULL, 0, 0, 75, height - 80, SWP_NOZORDER);
		SetWindowPos(hMsg, NULL, 128 + 75, height - 25, width - 128 - 75 - 25,
					 25, SWP_NOZORDER);
		SetWindowPos(hChTree, NULL, 75, 0, 128, height - 80, SWP_NOZORDER);
		SetWindowPos(hMsgList, NULL, 128 + 75, 0, width - 128 - 75, height - 25,
					 SWP_NOZORDER);
		SetWindowPos(hSend, NULL, width - 25, height - 25, 25, 25,
					 SWP_NOZORDER | SWP_NOSIZE);
		return 0;
		break;
	}
	case WM_COMMAND:
		if (lParam == hSend) {
			char msgcontent[GetWindowTextLength(hMsg) + 1];
			GetWindowText(hMsg, msgcontent, GetWindowTextLength(hMsg) + 1);
			DiscordSendMessage(curChannel, msgcontent);
		}
		break;
	case WM_NOTIFY:
		NMHDR pNMHDR = *(NMHDR *)lParam;
		LPNMLVCUSTOMDRAW lvcd = (LPNMLVCUSTOMDRAW)lParam;
		if (pNMHDR.hwndFrom == hGSel) {
			LPNMGUILDVIEW pnmv = (LPNMGUILDVIEW)lParam;
			if (pnmv->index == GUILDVIEW_DMS) {
				DiscordChannel *dms;
				int cnt = DiscordListPrivateChannels(&dms);
				TreeView_DeleteAllItems(hChTree);
				TreeView_SetImageList(hChTree, hSidePfps, TVSIL_NORMAL);
				TVINSERTSTRUCT tvInsert = {0};
				HTREEITEM hParent = TVI_ROOT;
				int pfpidx = 0;
				for (int i = 0; i < cnt; i++) {
					if (dms[i].type == CHANNEL_TYPE_DM) {
						printf("%s\n", dms[i].receipents[0].Username);

						if (dms[i].receipents[0].avatar) {

							char tempPath[MAX_PATH];
							DWORD len = GetTempPathA(MAX_PATH, tempPath);
							char path[MAX_PATH +
									   strlen(dms[i].receipents[0].avatar) + 1];
							wsprintfA(path, "%s%s.png", tempPath,
									  dms[i].receipents[0].avatar);

							if (GetFileAttributesA(path) ==
								INVALID_FILE_ATTRIBUTES) {
								char* path2 = DiscordFetchTmpPfp(
									dms[i].receipents[0].id,
									dms[i].receipents[0].avatar); // testing
								ImageList_Add(
									hSidePfps,
									ResizeBitmap(LoadPNGBitmap(path2), 32, 32),
									NULL);
								free(path2);
							} else {

								ImageList_Add(
									hSidePfps,
									ResizeBitmap(LoadPNGBitmap(path), 32, 32),
									NULL);
							}
						}

						tvInsert.hParent = hParent; // Parent is the root item
						tvInsert.hInsertAfter = TVI_LAST;
						tvInsert.item.mask = TVIF_TEXT | TVIF_PARAM |
											 TVIS_EXPANDED | TVIF_STATE |
											 TVIF_IMAGE;
						tvInsert.item.pszText =
							dms[i].receipents[0].DisplayName
								? dms[i].receipents[0].DisplayName
								: dms[i].receipents[0].Username;
						tvInsert.item.lParam = (LPARAM) & (dms[i]);
						tvInsert.item.state = TVIS_EXPANDED;
						tvInsert.item.stateMask = TVIS_EXPANDED;
						tvInsert.item.iImage =
							dms[i].receipents[0].avatar ? pfpidx : 0;
						HTREEITEM lres = (HTREEITEM)SendMessage(
							hChTree, TVM_INSERTITEM, 0, (LPARAM)&tvInsert);
						if (dms[i].receipents[0].avatar) {
							pfpidx++;
						}
					}
				}
			} else {

				DiscordGuild *guild;
				guild = pnmv->guild.data;

				DiscordChannel *Channels;
				int ChannelCount =
					DiscordListGuildChannels(guild->id, &Channels);
				TreeView_DeleteAllItems(hChTree);
				TreeView_SetImageList(hChTree, NULL, TVSIL_NORMAL);
				struct hashmap *ChannelUITable =
					hashmap_new(sizeof(ChannelUIEntry), 0, 0, 0, channel_hash,
								channel_compare, NULL, NULL);
				for (int i = 0; i < ChannelCount; i++) {
					char *VisualName;
					if (Channels[i].type == GUILD_TEXT) {
						VisualName = malloc(strlen(Channels[i].name) + 2);
						*VisualName = '#';
						strcpy(VisualName + 1, Channels[i].name);
					} else {
						VisualName = malloc(strlen(Channels[i].name) + 1);
						strcpy(VisualName, Channels[i].name);
					}
					TVINSERTSTRUCT tvInsert = {0};
					HTREEITEM hParent = TVI_ROOT;
					if (Channels[i].parentID) {
						ChannelUIEntry key = {0};
						key.id = Channels[i].parentID;
						if (hashmap_get(ChannelUITable, &key)) {
							ChannelUIEntry *chnl =
								(ChannelUIEntry *)(hashmap_get(ChannelUITable,
															   &key));
							hParent = chnl->itm;
						}
					}
					tvInsert.hParent = hParent; // Parent is the root item
					tvInsert.hInsertAfter = TVI_LAST;
					tvInsert.item.mask =
						TVIF_TEXT | TVIF_PARAM | TVIS_EXPANDED | TVIF_STATE;
					tvInsert.item.pszText = (VisualName);
					tvInsert.item.lParam = (LPARAM) & (Channels[i]);
					tvInsert.item.state = TVIS_EXPANDED;
					tvInsert.item.stateMask = TVIS_EXPANDED;
					HTREEITEM lres = (HTREEITEM)SendMessage(
						hChTree, TVM_INSERTITEM, 0, (LPARAM)&tvInsert);
					if (Channels[i].type == GUILD_CATEGORY) {
						ChannelUIEntry *chnl = malloc(sizeof(ChannelUIEntry));
						chnl->itm = lres;
						chnl->id = strdup(Channels[i].id);
						hashmap_set(ChannelUITable, chnl);
					}
					free(VisualName);
				}
			}
		} else if (pNMHDR.hwndFrom == hChTree) {
			LPNMTREEVIEW pnmv = (LPNMTREEVIEW)lParam;
			if (pNMHDR.code == TVN_SELCHANGED &&
				pnmv->itemNew.state & TVIS_SELECTED) {
				DiscordChannel *chnl = ((DiscordChannel *)pnmv->itemNew.lParam);
				if (chnl->type != GUILD_CATEGORY) {
					char *title = malloc(14 + strlen(pnmv->itemNew.pszText));
					wsprintf(title, "Backcord - %s", pnmv->itemNew.pszText);
					SetWindowTextA(hwnd, title);
					DiscordMessage *msgs;
					int msgcnt = DiscordGetChannelHistory(chnl->id, 50, &msgs);
					ClearChatControl(hMsgList);
					ListView_DeleteAllItems(hMsgList);
					if (curChannel)
						free(curChannel);

					curChannel = strdup(chnl->id);
					if (msgcnt > 0) {
						for (int i = msgcnt - 1; i >= 0; i--) {
							if (msgs[i].author.avatar) {
								char tempPath[MAX_PATH];
								DWORD len = GetTempPathA(MAX_PATH, tempPath);
								char *path =
									malloc(MAX_PATH +
										   strlen(msgs[i].author.avatar) + 1);
								wsprintfA(path, "%s%s.png", tempPath,
										  msgs[i].author.avatar);
								if (GetFileAttributesA(path) ==
									INVALID_FILE_ATTRIBUTES) {
									char *path = DiscordFetchTmpPfp(
										msgs[i].author.id,
										msgs[i].author.avatar); // testing
								}
								ChatView_SetUserPfp(path, msgs[i].author.id);
							}
							InsertChatMessage((msgs[i]), hMsgList);
						}
					}
					ListView_EnsureVisible(hMsgList, msgcnt - 1, FALSE);
					free(title);
					free(msgs);
				}
			}
			break;
		}
		break;
	case WM_PAINT: {
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hwnd, &ps);

		// All painting occurs here, between BeginPaint and EndPaint.

		FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_BTNFACE + 1));
		EndPaint(hwnd, &ps);
		return 0;
		break;
	}
	case WM_CLOSE:
		DestroyWindow(hwnd);
		PostQuitMessage(0);
	case WM_DESTROY:
		CleanupHTTP();
		return TRUE;
		break;
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}