using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.IO.Compression;
using FreeImageAPI;
using FreeImageAPI.IO;
using FreeImageAPI.Plugins;

namespace Sample08
{
	public sealed class SerializationPlugin : LocalPlugin
	{
		// Header for the file
		private byte[] header = new byte[] { 0xff, 0x12, 0x0f, 0xff, 0x01, 0x00 };

		// Structure that will store all bitmap data.
		[Serializable]
		private struct SerialDib
		{
			public uint width;
			public uint height;
			public int pitch;
			public uint bpp;
			public uint red_mask;
			public uint green_mask;
			public uint blue_mask;
			public byte[] data;
		}

		// Implementation of 'GetImplementedMethods()'
		// All implemented methods are listed.
		protected override LocalPlugin.MethodFlags GetImplementedMethods()
		{
			return
				MethodFlags.DescriptionProc |
				MethodFlags.SupportsExportBPPProc |
				MethodFlags.SupportsExportTypeProc |
				MethodFlags.SupportsICCProfilesProc |
				MethodFlags.LoadProc |
				MethodFlags.SaveProc |
				MethodFlags.ValidateProc |
				MethodFlags.ExtensionListProc;
		}

		// Returns a format string.
		protected override string FormatProc()
		{
			return "Serialization";
		}

		// Returns a more specific description
		protected override string DescriptionProc()
		{
			return "Serializes bitmaps for .NET";
		}

		// Returns whether a color depth is supported.
		protected override bool SupportsExportBPPProc(int bpp)
		{
			return ((bpp == 1) ||
					(bpp == 4) ||
					(bpp == 8) ||
					(bpp == 16) ||
					(bpp == 24) ||
					(bpp == 32));
		}

		// This plugin can only export standard bitmaps
		protected override bool SupportsExportTypeProc(FREE_IMAGE_TYPE type)
		{
			return (type == FREE_IMAGE_TYPE.FIT_BITMAP);
		}

		// This plugin does not support icc profiles
		protected override bool SupportsICCProfilesProc()
		{
			return false;
		}

		// The function reads the first bytes of the file and compares it
		// with the predefined header.
		protected override bool ValidateProc(ref FreeImageIO io, fi_handle handle)
		{
			for (int i = 0; i < header.Length; i++)
				if (ReadByte(io, handle) != header[i])
					return false;
			return true;
		}

		// Loading function
		protected override FIBITMAP LoadProc(ref FreeImageIO io, fi_handle handle, int page, int flags, IntPtr data)
		{
			// Check if the data has the correct format
			if (!ValidateProc(ref io, handle))
			{
				// Create a free-image message
				FreeImage.OutputMessageProc(format, "Invalid format.");
				// return 0 (operation failed)
				return FIBITMAP.Zero;
			}

			SerialDib sdib;
			int read = 0;
			System.IO.MemoryStream stream = new System.IO.MemoryStream();
			byte[] buffer = new byte[1024];

			do
			{
				// Use the helper function 'Read' to read from the source
				read = Read(io, handle, 1, 1024, ref buffer);

				// Store the data in a temporary buffer
				stream.Write(buffer, 0, read);
			}
			while (read != 0);

			// Set the memory stream back to the beginning.
			stream.Position = 0;

			// Unzip the stream
			GZipStream zipStream = new GZipStream(stream, CompressionMode.Decompress);

			// Create a serializer
			BinaryFormatter formatter = new BinaryFormatter();

			// Deserialize the stream
			sdib = (SerialDib)formatter.Deserialize(zipStream);

			// Unload the stream
			zipStream.Dispose();

			// Use 'ConvertFromRawBits and the deserialized struct to recreate the bitmap
			// In this case the marshaller is used to create the needed IntPtr to the data
			// array.
			FIBITMAP dib = FreeImage.ConvertFromRawBits(
				Marshal.UnsafeAddrOfPinnedArrayElement(sdib.data, 0),
				(int)sdib.width, (int)sdib.height, sdib.pitch, sdib.bpp,
				sdib.red_mask, sdib.green_mask, sdib.blue_mask,
				false);

			// Unload the temporary stream
			stream.Dispose();

			// Return the created bitmap
			return dib;
		}

		// Saving function
		protected override bool SaveProc(ref FreeImageIO io, FIBITMAP dib, fi_handle handle, int page, int flags, IntPtr data)
		{
			SerialDib sdib;
			uint size = FreeImage.GetDIBSize(dib);

			// Store all data needed to recreate the bitmap
			sdib.width = FreeImage.GetWidth(dib);
			sdib.height = FreeImage.GetHeight(dib);
			sdib.pitch = (int)FreeImage.GetPitch(dib);
			sdib.bpp = FreeImage.GetBPP(dib);
			sdib.red_mask = FreeImage.GetRedMask(dib);
			sdib.green_mask = FreeImage.GetGreenMask(dib);
			sdib.blue_mask = FreeImage.GetBlueMask(dib);
			sdib.data = new byte[size];

			// Copy the bitmaps data into the structures byte-array
			// The marshaller is used to create an IntPtr for using
			// 'ConvertToRawBits'.
			FreeImage.ConvertToRawBits(Marshal.UnsafeAddrOfPinnedArrayElement(sdib.data, 0),
						  dib, sdib.pitch, sdib.bpp,
						  sdib.red_mask, sdib.green_mask, sdib.blue_mask,
						  false);

			// Use the healper function to write the header to the destination
			if (Write(io, handle, (uint)header.Length, 1, ref header) != 1)
				return false;

			// Create a serializer
			BinaryFormatter formatter = new BinaryFormatter();

			// Create a temporary stream
			MemoryStream stream = new MemoryStream();

			// Create a compression stream
			GZipStream zipStream = new GZipStream(stream, CompressionMode.Compress);

			// Serialize the structure into the compression stream
			formatter.Serialize(zipStream, sdib);

			// Unload the compression stream
			zipStream.Dispose();

			// Get the result data
			byte[] buffer = stream.GetBuffer();

			// Use the healper function 'Write' to write the data to the destination
			if (Write(io, handle, 1, (uint)buffer.Length, ref buffer) != buffer.Length)
			{
				// Unload the temporary stream
				stream.Dispose();
				return false;
			}

			// Unload the temporary stream
			stream.Dispose();
			return true;
		}

		// Return a list of supported file extensions (comma seperated)
		protected override string ExtensionListProc()
		{
			return "ser";
		}

		// Implementation of 'ToString()'
		public override string ToString()
		{
			return DescriptionProc();
		}
	}
}
