// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net;
using System.Reflection;
using System.Threading;
using ClassicalSharp;
using ClassicalSharp.Network;
using Launcher.Drawing;
using Launcher.Gui.Screens;
using Launcher.Patcher;
using Launcher.Web;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
using OpenTK.Platform;

namespace Launcher {

	public sealed partial class LauncherWindow {
		
		public INativeWindow Window;
		public IDrawer2D Drawer;	
		public Screen Screen;
		
		/// <summary> Whether the client drawing area needs to be redrawn/presented to the screen. </summary>
		public bool Dirty, pendingRedraw;
				/// <summary> The specific area/region of the window that needs to be redrawn. </summary>
		public Rectangle DirtyArea;
		
		public string Username;
		public AsyncDownloader Downloader;
		
		public int Width, Height;	
		public Bitmap Framebuffer;
		
		/// <summary> Whether at the next tick, the launcher window should proceed to stop displaying frames and subsequently exit. </summary>
		public bool ShouldExit;
		public bool ShouldUpdate;
		
		public List<ServerListEntry> Servers = new List<ServerListEntry>();
		
		public string FontName = "Arial";
		
		public bool Minimised {
			get { return Window.WindowState == WindowState.Minimized || (Width == 1 && Height == 1); }
		}
		
		public bool IsKeyDown(Key key) { return Keyboard.Get(key); }
		
		internal ResourceFetcher fetcher;
		internal UpdateCheckTask checkTask;
		bool fullRedraw;
		
		Font logoFont;
		PlatformDrawer platformDrawer;
		public void Init() {
			Window.Resize += Resize;
			Window.FocusedChanged += RedrawAll;
			Window.WindowStateChanged += Resize;
			Window.Redraw += RedrawPending;
			Keyboard.KeyDown += KeyDown;
			
			ClassicalSharp.Program.CleanupMainDirectory();
			LoadFont();
			logoFont = new Font(FontName, 32, FontStyle.Regular);
			
			string path = Assembly.GetExecutingAssembly().Location;
			try {
				Window.Icon = Icon.ExtractAssociatedIcon(path);
			} catch (Exception ex) {
				ErrorHandler.LogError("LauncherWindow.Init() - Icon", ex);
			}
			//Minimised = Window.WindowState == WindowState.Minimized;
			
			PlatformID platform = Environment.OSVersion.Platform;
			if (platform == PlatformID.Win32Windows) {
				platformDrawer = new WinOldPlatformDrawer();
			} else if (Configuration.RunningOnWindows) {
				platformDrawer = new WinPlatformDrawer();
			} else if (Configuration.RunningOnX11) {
				platformDrawer = new X11PlatformDrawer();
			} else if (Configuration.RunningOnMacOS) {
				platformDrawer = new OSXPlatformDrawer();
			}
			
			IDrawer2D.Cols['g'] = new PackedCol(125, 125, 125);
		}
		
		void LoadFont() {
			Options.Load();
			FontName = Options.Get("gui-fontname", "Arial");
			try {
				using (Font f = new Font(FontName, 16)) { }
			} catch (Exception) {
				FontName = "Arial";
				Options.Set("gui-fontname", "Arial");
			}
		}

		void Resize(object sender, EventArgs e) {
			UpdateClientSize();
			platformDrawer.Resize();
			RedrawAll(sender, e);
		}
		
		void RedrawPending(object sender, EventArgs e) {
			// in case we get multiple of these events
			pendingRedraw = true;
			Dirty = true;
		}
		
		void RedrawAll(object sender, EventArgs e) {
			if (Program.ShowingErrorDialog) return;
			RedrawBackground();			
			if (Screen != null) Screen.Resize();
			fullRedraw = true;
		}
		
		public void SetScreen(Screen screen) {
			if (Screen != null) Screen.Dispose();
			RedrawBackground();
			Screen = screen;
			screen.Init();
		}
		
		public bool ConnectToServer(List<ServerListEntry> publicServers, string hash) {
			if (String.IsNullOrEmpty(hash)) return false;
			
			ClientStartData data = null;
			for (int i = 0; i < publicServers.Count; i++) {
				ServerListEntry entry = publicServers[i];
				if (entry.Hash != hash) continue;
				
				data = new ClientStartData(Username, entry.Mppass,
				                           entry.IPAddress, entry.Port, entry.Name);
				Client.Start(data, true, ref ShouldExit);
				return true;
			}
			
			// Fallback to private server handling
			try {
				// TODO: Rewrite to be async
				FetchServerTask task = new FetchServerTask(Username, hash);
				task.RunAsync(this);
				
				while (!task.Completed) { task.Tick(); Thread.Sleep(10); }
				if (task.WebEx != null) throw task.WebEx;
				
				data = task.Info;
			} catch (WebException ex) {
				ErrorHandler.LogError("retrieving server information", ex);
				return false;
			} catch (ArgumentOutOfRangeException) {
				return false;
			}
			Client.Start(data, true, ref ShouldExit);
			return true;
		}
		
		void UpdateClientSize() {
			Size size = Window.ClientSize;
			Width  = Math.Max(size.Width,  1);
			Height = Math.Max(size.Height, 1);
		}
		
		public void Run() {
			Window = Factory.CreateWindow(640, 400, Program.AppName,
			                              GraphicsMode.Default, DisplayDevice.Default);
			Window.Visible = true;
			Drawer = new GdiPlusDrawer2D();
			UpdateClientSize();
			
			Init();
			TryLoadTexturePack();
			platformDrawer.window = Window;
			platformDrawer.Init();
			
			Downloader = new AsyncDownloader(Drawer);
			Downloader.Init("");
			Downloader.Cookies = new CookieContainer();
			Downloader.KeepAlive = true;
			
			fetcher = new ResourceFetcher();
			fetcher.CheckResourceExistence();
			checkTask = new UpdateCheckTask();
			checkTask.RunAsync(this);
			
			if (!fetcher.AllResourcesExist) {
				SetScreen(new ResourcesScreen(this));
			} else {
				SetScreen(new MainScreen(this));
			}
			
			while (true) {
				Window.ProcessEvents();
				if (!Window.Exists) break;
				if (ShouldExit) {
					if (Screen != null) {
						Screen.Dispose();
						Screen = null;
					}
					break;
				}
				
				checkTask.Tick();
				Screen.Tick();
				if (Dirty) Display();
				Thread.Sleep(10);
			}
			
			if (Options.Load()) {
				LauncherSkin.SaveToOptions();
				Options.Save();
			}
			
			if (ShouldUpdate)
				Updater.Applier.ApplyUpdate();
			if (Window.Exists)
				Window.Close();
		}
		
		void Display() {
			if (pendingRedraw) {
				RedrawAll(null, null);
				pendingRedraw = false;
			}
			
			Screen.OnDisplay();
			Dirty = false;
			
			Rectangle rec = new Rectangle(0, 0, Framebuffer.Width, Framebuffer.Height);
			if (!fullRedraw && DirtyArea.Width > 0) {
				rec = DirtyArea;
			}
			platformDrawer.Redraw(Framebuffer, rec);
			DirtyArea = Rectangle.Empty;
			fullRedraw = false;
		}
		
		void KeyDown(object sender, KeyboardKeyEventArgs e) {
			if (IsShutdown(e.Key)) ShouldExit = true;
		}
		
		public void Dispose() {
			Window.Resize -= Resize;
			Window.FocusedChanged -= RedrawAll;
			Window.WindowStateChanged -= Resize;
			Window.Redraw -= RedrawPending;
			Keyboard.KeyDown -= KeyDown;
			
			List<FastBitmap> bitmaps = FetchFlagsTask.Bitmaps;
			for (int i = 0; i < bitmaps.Count; i++) {
				bitmaps[i].Dispose();
				bitmaps[i].Bitmap.Dispose();
			}
			logoFont.Dispose();
		}
		
		bool WinDown { get { return IsKeyDown(Key.WinLeft) || IsKeyDown(Key.WinRight); } }
		bool AltDown { get { return IsKeyDown(Key.AltLeft) || IsKeyDown(Key.AltRight); } }
		
		bool IsShutdown(Key key) {
			if (key == Key.F4 && AltDown) return true;
			
			// On OSX, Cmd+Q should also terminate the process.
			if (!OpenTK.Configuration.RunningOnMacOS) return false;
			return key == Key.Q && WinDown;
		}
		
		public FastBitmap LockBits() {
			return new FastBitmap(Framebuffer, true, false);
		}
	}
}
