using System;
using System.IO;
using System.Drawing;
using System.ComponentModel;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace Orciid.Media.Converters
{
	public class Jpeg2000Converter
	{
		private static string pathtokakadu = Path.GetTempPath();
		
		private const int KAKADU_TIMEOUT = 60000;
		private const string KAKADU_ENCODING_ARGS = "-rate 1.5 Clayers=20 Creversible=yes " +
			"Clevels=8 Cprecincts={256,256},{256,256},{128,128} Corder=RPCL " +
			"ORGgen_plt=yes ORGtparts=R Cblk={32,32}";
		
		private Jpeg2000Converter()
		{
		}

		public static void Register()
		{
			MimeType.RegisterExtension("jp2", "image/jp2");
			MimeType.RegisterExtension("bmp", "image/bmp");

			MediaConverter.RegisterFileConverter("image/bmp", "image/jp2",
				Capabilities.None, false, new FileConverter(ConvertBitmapToJpeg2000File));
			MediaConverter.RegisterFileConverter("image/jp2", "image/bmp",
				Capabilities.Resize | Capabilities.Crop | Capabilities.AddBorder, 
				false, new FileConverter(ConvertJpeg2000ToAnyFile));

			foreach (MediaConverter.ConverterRegistration registration in MediaConverter.GetRegisteredConverters())
			{
				if (registration.FromType == "image/bmp" && registration.ToType != "image/jp2")
					MediaConverter.RegisterFileConverter("image/jp2", registration.ToType,
						registration.Capabilities | Capabilities.Crop, false, 
						new FileConverter(ConvertJpeg2000ToAnyFile));
				if (registration.ToType == "image/bmp" && registration.FromType != "image/jp2")
					MediaConverter.RegisterFileConverter(registration.FromType, "image/jp2",
						registration.Capabilities, false, new FileConverter(ConvertAnyToJpeg2000File));
			}
		}

		public static void SetPathToKakadu(string path)
		{
			pathtokakadu = path;
		}

		internal static Size GetDimensions(string file)
		{
			string tempfile = Path.GetTempFileName();
			Debug.WriteLine("Created temporary file " + tempfile);
			try
			{
				ProcessStartInfo info = new ProcessStartInfo("kdu_expand.exe",
					String.Format("-i \"{0}\" -record \"{1}\"", file, tempfile));
				if (RunExternalProgram(info))
				{
					string record;
					using (StreamReader reader = File.OpenText(tempfile))
						record = reader.ReadToEnd();
					Regex regex = new Regex(@"^Ssize=\{(\d+),(\d+)\}", RegexOptions.Multiline);
					Match match = regex.Match(record);
					if (match.Success)
						return new Size(
							Int32.Parse(match.Groups[2].Value),
							Int32.Parse(match.Groups[1].Value));
				}
				return Size.Empty;
			}
			finally
			{
				File.Delete(tempfile);
			}
		}

		private static bool ConvertBitmapToJpeg2000File(string sourcefile, string targetfile,
			Parameters parameters)
		{
			ProcessStartInfo info = new ProcessStartInfo("kdu_compress.exe",
				String.Format("-i \"{0}\" -o \"{1}\" {2}",
				sourcefile, targetfile, KAKADU_ENCODING_ARGS));
			return RunExternalProgram(info);
		}

		private static bool ConvertJpeg2000ToAnyFile(string sourcefile, string targetfile,
			Parameters parameters)
		{
			bool onestep = (MimeType.GetFromExtension(targetfile) == "image/bmp") &&
				parameters.Equals(Parameters.Default);

			Size dimensions = Size.Empty;
			RectangleF region = new RectangleF(0, 0, 1, 1);

			if (parameters.CropX != Parameters.None || parameters.CropY != Parameters.None ||
				parameters.CropW != Parameters.None || parameters.CropH != Parameters.None)
			{
				if (dimensions.IsEmpty)
					dimensions = GetDimensions(sourcefile);
				if (dimensions.Height == 0 || dimensions.Width == 0)
					throw new Exception("Could not retrieve image dimensions");
				region = new RectangleF(
					(float)parameters.CropX / (float)dimensions.Width,
					(float)parameters.CropY / (float)dimensions.Height,
					(float)parameters.CropW / (float)dimensions.Width,
					(float)parameters.CropH / (float)dimensions.Height);
				dimensions.Height = parameters.CropH;
				dimensions.Width = parameters.CropW;
			}

			int reduce = 0;
			if (parameters.TargetWidth != Parameters.Unspecified &&
				parameters.TargetHeight != Parameters.Unspecified)
			{
				if (dimensions.IsEmpty)
					dimensions = GetDimensions(sourcefile);
				if (!dimensions.IsEmpty && 
					(parameters.TargetWidth < dimensions.Width ||
					parameters.TargetHeight < dimensions.Height))
				{
					while (dimensions.Width / (1 << (reduce + 1)) > parameters.TargetWidth ||
						dimensions.Height / (1 << (reduce + 1)) > parameters.TargetHeight)
						reduce++;
				}
			}
			string tempfile = Util.Tools.GetTempFileName("bmp");
			try
			{
				ProcessStartInfo info = new ProcessStartInfo("kdu_expand.exe",
					String.Format("-i \"{0}\" -o \"{1}\" -reduce {2} -region {{{4},{3}}},{{{6},{5}}}",
					sourcefile, 
					onestep ? targetfile : tempfile, 
					reduce,
					region.X,
					region.Y,
					region.Width,
					region.Height));
				if (RunExternalProgram(info))
				{
					if (onestep)
						return true;
					else
					{
						Parameters p = new Parameters(
							parameters.TargetWidth, parameters.TargetHeight,
							parameters.ExactTargetSize, parameters.BorderColor,
							Parameters.None, Parameters.None, Parameters.None, Parameters.None,
							parameters.Quality);
						return MediaConverter.Convert(tempfile, targetfile, p);
					}
				}
				else
					return false;
			}
			finally
			{
				File.Delete(tempfile);
			}			
		}

		private static bool ConvertAnyToJpeg2000File(string sourcefile, string targetfile,
			Parameters parameters)
		{
			string tempfile = Util.Tools.GetTempFileName("bmp");
			try
			{
				bool success;
				success = MediaConverter.Convert(sourcefile, tempfile, parameters);
				success = success && ConvertBitmapToJpeg2000File(tempfile, targetfile, parameters);
				return success;
			}
			finally
			{
				File.Delete(tempfile);
			}
		}

		private static bool RunExternalProgram(ProcessStartInfo info)
		{
			Debug.WriteLine(info.FileName + " " + info.Arguments, "KAKADU");
			info.CreateNoWindow = true;
			info.WindowStyle = ProcessWindowStyle.Hidden;
			Process process = Process.Start(info);
			process.WaitForExit(KAKADU_TIMEOUT);			
			if (process.HasExited)
			{
				return true;
			}
			else
			{
				process.Kill();
				return false;
			}
		}
	}
}
