using liblogtiny;
using libpaperang;
using libpaperang.Interfaces;
using libpaperang.Main;
using System;
using System.Timers;
using System.Windows;
using System.Windows.Forms;
//testing
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace paperangapp {
	public partial class MainWindow : Window {
		private ILogTiny logger;
		private BaseTypes.Connection mmjcx=BaseTypes.Connection.USB;
		private BaseTypes.Model mmjmd=BaseTypes.Model.T1;
		private IPrinter prtr=new USB(BaseTypes.Model.T1); // T1 used as a generic, prtr used soley for the PrinterAvailable attr. all paperang devices tested report the exact same USB identifiers.
		private Paperang mmj=null;
		private System.Timers.Timer usbpoll;
		private enum AppState {
			UnInitNoDev,
			UnInitDev,
			InitDev
		};
		private AppState state=AppState.UnInitNoDev;
		// TODO: is it out of scope for this library to provide functionality for printing bitmap data?
		public MainWindow() {
			InitializeComponent();
			logger = new LUITextbox();
			gMain.DataContext = (LUITextbox)logger;
			logger.Info("Application started");
			usbpoll = new System.Timers.Timer(200) {
				AutoReset = true
			};
			usbpoll.Elapsed += evtUsbPoll;
			usbpoll.Start();
			logger.Verbose("USB presence interval event started");
		}

		private void evtUsbPoll(object sender, ElapsedEventArgs e) =>_ = Dispatcher.BeginInvoke(new invDgtUsbPoll(dgtUsbPoll));
		private delegate void invDgtUsbPoll();
		private void dgtUsbPoll() {
			try {
				if(state == AppState.UnInitNoDev && prtr.PrinterAvailable) {
					btInitUSB.IsEnabled = true;
					state = AppState.UnInitDev;
					logger.Info("Printer plugged in");
				} else if(state == AppState.UnInitDev && !prtr.PrinterAvailable) {
					btInitUSB.IsEnabled = false;
					state = AppState.UnInitNoDev;
					logger.Info("Printer unplugged");
				} else if(state == AppState.InitDev && !prtr.PrinterAvailable) {
					logger.Info("Printer unplugged while initialised");
					USBDeInit();
				}

			} catch(Exception) {
			} finally { }
		}

		~MainWindow() {
			logger.Warn("Application closing");
			if(mmj!=null) USBDeInit();
		}
		private void BtClearLog_Click(object sender, RoutedEventArgs e) =>
			logger.Raw("!clearlog");
		private void SetP1_Click(object sender, RoutedEventArgs e) {
			mmjmd = BaseTypes.Model.P1;
			logger.Info("Model type set to P1 or P1S");
		}
		private void SetP2_Click(object sender, RoutedEventArgs e) {
			mmjmd = BaseTypes.Model.P2;
			logger.Info("Model type set to P2 or P2S");
		}
		private void SetT1_Click(object sender, RoutedEventArgs e) {
			mmjmd = BaseTypes.Model.T1;
			logger.Info("Model type set to T1");
		}
		private void BtInitUSB_Click(object sender, RoutedEventArgs e) => USBInit();
		private void USBInit() {
			mmj = new Paperang(mmjcx, mmjmd);
			mmj.SetLogContext(logger);
			logger.Verbose("# printers found: " + mmj.Printer.AvailablePrinters.Count);
			if(!mmj.Printer.PrinterAvailable) {
				logger.Error("Couldn't initialise printer as none is connected");
				return;
			}
			logger.Info("USB Initialising");
			mmj.Initialise();
			logger.Debug("PrinterInitialised? " + mmj.Printer.PrinterInitialised);
			logger.Debug("Printer initialised and ready");
			state = AppState.InitDev;
			btInitUSB.IsEnabled = false;
			btDeInitUSB.IsEnabled = true;
			gbOtherFunc.IsEnabled = true;
			gbPrinting.IsEnabled = true;
		}
		private void BtDeInitUSB_Click(object sender, RoutedEventArgs e) => USBDeInit();
		private void USBDeInit() {
			logger.Info("De-initialising printer");
			mmj.Printer.ClosePrinter();
			mmj.Printer.Deinitialise();
			mmj = null;
			state = AppState.UnInitDev;
			try {
				gbPrinting.IsEnabled = false;
				gbOtherFunc.IsEnabled = false;
				btInitUSB.IsEnabled = true;
				btDeInitUSB.IsEnabled = false;
			} catch(Exception) {
			} finally { }
		}
		private void BtFeed_Click(object sender, RoutedEventArgs e) => mmj.Feed((uint)slFeedTime.Value);
		private void BtPrintText_Click(object sender, RoutedEventArgs e) {
			Font fnt=new Font(txFont.Text, int.Parse(txSzF.Text));
			Graphics g=Graphics.FromImage(new Bitmap(mmj.Printer.LineWidth*8, 1));
			SizeF szText=g.MeasureString(txInput.Text, fnt);
			g.Dispose();
			Bitmap b=new Bitmap(mmj.Printer.LineWidth*8, (int)szText.Height);
			g = Graphics.FromImage(b);
			g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
			g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
			g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
			g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
			g.DrawString(txInput.Text, fnt, Brushes.Black, new PointF(0,0));
			g.Flush();
			PrintBitmap(b);
			g.Dispose();
			b.Dispose();
		}
		private void BtPrintImage_Click(object sender, RoutedEventArgs e) {
			logger.Debug("Loading image for print");
			OpenFileDialog r = new OpenFileDialog {
				Title="Select 1 (one) image file",
				Multiselect=false,
				Filter="PNG files|*.png|JPEG files|*.jpg;*.jpeg|Jraphics Interchange Format files|*.gif|Bitte-Mappe files|*.bmp|All of the above|*.jpg;*.jpeg;*.png;*.gif;*.bmp",
				AutoUpgradeEnabled=true
			};
			if (r.ShowDialog() == System.Windows.Forms.DialogResult.Cancel) {
				logger.Debug("Image load cancelled");
				return;
			}
			Image _=Image.FromFile(r.FileName);
			logger.Debug("Loaded image " + r.FileName);
			r.Dispose();
			logger.Debug("Disposed of dialog");
			Bitmap bimg=new Bitmap(_, (mmj.Printer.LineWidth*8),
				(int)((double)(mmj.Printer.LineWidth*8)*(double)((double)_.Height/(double)_.Width)));
			logger.Debug("Loaded image as Bitmap");
			_.Dispose();
			logger.Debug("Disposed of Image");
			PrintBitmap(bimg);
		}
		private void PrintBitmap(Bitmap bimg) {
			bimg = CopyToBpp(bimg);
			logger.Debug("Converted Bitmap to 1-bit");
			int hSzImg=bimg.Height;
			byte[] iimg = new byte[hSzImg*mmj.Printer.LineWidth];
			byte[] img;
			using(MemoryStream s = new MemoryStream()) {
				bimg.Save(s, ImageFormat.Bmp);
				img = s.ToArray();
			}
			logger.Debug("Got bitmap's bytes");
			bimg.Dispose();
			logger.Debug("Disposed of Bitmap");
			int startoffset=img.Length-(hSzImg*mmj.Printer.LineWidth);
			logger.Debug("Processing bytes with offset " + startoffset);
			for(int h = 0; h < hSzImg; h++) {
				for(int w = 0; w < mmj.Printer.LineWidth; w++) {
					iimg[(mmj.Printer.LineWidth * (hSzImg - 1 - h)) + (mmj.Printer.LineWidth - 1 - w)] = (byte)~
						(img[startoffset + (mmj.Printer.LineWidth * h) + (mmj.Printer.LineWidth - 1 - w)]);
				}
			}
			logger.Debug($"Have {img.Length} bytes of print data ({mmj.Printer.LineWidth*8}x{hSzImg}@1bpp)");
			mmj.PrintBytes(iimg, false);
			//mmj.Feed(175);
		}
		static uint BitSwap1(uint x) => ((x & 0x55555555u) << 1) | ((x & (~0x55555555u)) >> 1);
		static uint BitSwap2(uint x) => ((x & 0x33333333u) << 2) | ((x & (~0x33333333u)) >> 2);
		static uint BitSwap4(uint x) => ((x & 0x0f0f0f0fu) << 4) | ((x & (~0x0f0f0f0fu)) >> 4);
		static uint BitSwap(uint x) => BitSwap4(BitSwap2(BitSwap1(x)));
		#region GDIBitmap1bpp
		static Bitmap CopyToBpp(Bitmap b) {
			int w=b.Width, h=b.Height;
			IntPtr hbm = b.GetHbitmap();
			BITMAPINFO bmi = new BITMAPINFO {
				biSize=40,
				biWidth=w,
				biHeight=h,
				biPlanes=1,
				biBitCount=1,
				biCompression=BI_RGB,
				biSizeImage = (uint)(((w+7)&0xFFFFFFF8)*h/8),
				biXPelsPerMeter=1000000,
				biYPelsPerMeter=1000000
			};
			bmi.biClrUsed=2;
			bmi.biClrImportant=2;
			bmi.cols=new uint[256];
			bmi.cols[0]=MAKERGB(0, 0, 0);
			bmi.cols[1]=MAKERGB(255, 255, 255);
			IntPtr hbm0 = CreateDIBSection(IntPtr.Zero,ref bmi,DIB_RGB_COLORS,out _,IntPtr.Zero,0);
			IntPtr sdc = GetDC(IntPtr.Zero);
			IntPtr hdc = CreateCompatibleDC(sdc);
			_ = SelectObject(hdc, hbm);
			IntPtr hdc0 = CreateCompatibleDC(sdc);
			_ = SelectObject(hdc0, hbm0);
			_ = BitBlt(hdc0, 0, 0, w, h, hdc, 0, 0, SRCCOPY);
			Bitmap b0 = Image.FromHbitmap(hbm0);
			_ = DeleteDC(hdc);
			_ = DeleteDC(hdc0);
			_ = ReleaseDC(IntPtr.Zero, sdc);
			_ = DeleteObject(hbm);
			_ = DeleteObject(hbm0);
			return b0;
		}
		[System.Runtime.InteropServices.DllImport("gdi32.dll")]
		public static extern bool DeleteObject(IntPtr hObject);
		[System.Runtime.InteropServices.DllImport("user32.dll")]
		public static extern IntPtr GetDC(IntPtr hwnd);
		[System.Runtime.InteropServices.DllImport("gdi32.dll")]
		public static extern IntPtr CreateCompatibleDC(IntPtr hdc);
		[System.Runtime.InteropServices.DllImport("user32.dll")]
		public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);
		[System.Runtime.InteropServices.DllImport("gdi32.dll")]
		public static extern int DeleteDC(IntPtr hdc);
		[System.Runtime.InteropServices.DllImport("gdi32.dll")]
		public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
		[System.Runtime.InteropServices.DllImport("gdi32.dll")]
		public static extern int BitBlt(IntPtr hdcDst, int xDst, int yDst, int w, int h, IntPtr hdcSrc, int xSrc, int ySrc, int rop);
		static int SRCCOPY = 0x00CC0020;
		[System.Runtime.InteropServices.DllImport("gdi32.dll")]
		static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFO bmi, uint Usage, out IntPtr bits, IntPtr hSection, uint dwOffset);
		static uint BI_RGB = 0;
		static uint DIB_RGB_COLORS=0;
		[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
		public struct BITMAPINFO {
			public uint biSize;
			public int biWidth, biHeight;
			public short biPlanes, biBitCount;
			public uint biCompression, biSizeImage;
			public int biXPelsPerMeter, biYPelsPerMeter;
			public uint biClrUsed, biClrImportant;
			[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst=256)]
			public uint[] cols;
		}
		static uint MAKERGB(int r, int g, int b) => ((uint)(b&255)) | ((uint)((r&255)<<8)) | ((uint)((g&255)<<16));
		#endregion
	}
}
