
#region ================== Copyright (c) 2007 Pascal vd Heiden

/*
 * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
 * This program is released under GNU General Public License
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 */

#endregion

#region ================== Namespaces

using System.Drawing;
using System.IO;
using CodeImp.DoomBuilder.IO;

#endregion

namespace CodeImp.DoomBuilder.Data
{
	internal static class ImageDataFormat
	{
		// Input guess formats
		public const int UNKNOWN = 0;			// No clue.
		public const int DOOMPICTURE = 1;		// Could be Doom Picture format	(column list rendered data)
		public const int DOOMFLAT = 2;			// Could be Doom Flat format	(raw 8-bit pixel data)
		public const int DOOMCOLORMAP = 3;		// Could be Doom Colormap format (raw 8-bit pixel palette mapping)
		
		// File format signatures
		private static readonly int[] PNG_SIGNATURE = new[] { 137, 80, 78, 71, 13, 10, 26, 10 };
		private static readonly int[] GIF_SIGNATURE = new[] { 71, 73, 70 };
		private static readonly int[] BMP_SIGNATURE = new[] { 66, 77 }; 
		private static readonly int[] DDS_SIGNATURE = new[] { 68, 68, 83, 32 };
		private static readonly int[] JPG_SIGNATURE = new[] { 255, 216, 255 }; //mxd
		private static readonly int[] PCX_SIGNATURE = new[] { 10, 5, 1, 8 }; //mxd

        // Try load image data with the appropriate image reader. Returns null if the image could not be loaded
        public static Bitmap TryLoadImage(Stream data, int guessformat = UNKNOWN, Playpal palette = null)
        {
            int offsetx, offsety;
            return TryLoadImage(data, guessformat, palette, out offsetx, out offsety);
        }

        public static Bitmap TryLoadImage(Stream data, int guessformat, Playpal palette, out int offsetx, out int offsety)
        {
            offsetx = int.MinValue;
            offsety = int.MinValue;
            try
            {
                if (data == null) return null;

                // Data long enough to check for signatures?
                if (data.Length > 10)
                {
                    IImageReader loader = null;
                    if (CheckSignature(data, PNG_SIGNATURE))
                        loader = new FrameworkImageReader(true);
                    else if (CheckSignature(data, JPG_SIGNATURE))
                        loader = new FrameworkImageReader(false);
                    else if (CheckSignature(data, PCX_SIGNATURE))
                        loader = new PcxImageReader();
                    else if (CheckTgaSignature(data))
                        loader = new TgaImageReader();

                    if (loader != null)
                    {
                        data.Seek(0, SeekOrigin.Begin);
                        try
                        {
                            Bitmap image = loader.ReadAsBitmap(data, out offsetx, out offsety);
                            if (image != null) // The older loaders return null when they should throw an exception
                                return image;
                        }
                        catch
                        {
                        }
                    }
                }

                IImageReader doomloader = null;

                // Could it be a doom picture?
                switch (guessformat)
                {
                    case DOOMPICTURE:
                        // Check if data is valid for a doom picture
                        data.Seek(0, SeekOrigin.Begin);
                        DoomPictureReader picreader = new DoomPictureReader(palette);
                        if (picreader.Validate(data)) doomloader = picreader;
                        break;

                    case DOOMFLAT:
                        // Check if data is valid for a doom flat
                        data.Seek(0, SeekOrigin.Begin);
                        DoomFlatReader flatreader = new DoomFlatReader(palette);
                        if (flatreader.Validate(data)) doomloader = flatreader;
                        break;

                    case DOOMCOLORMAP:
                        // Check if data is valid for a doom colormap
                        data.Seek(0, SeekOrigin.Begin);
                        DoomColormapReader colormapreader = new DoomColormapReader(palette);
                        if (colormapreader.Validate(data)) doomloader = colormapreader;
                        break;
                }

                if (doomloader != null)
                {
                    data.Seek(0, SeekOrigin.Begin);
                    Bitmap image = doomloader.ReadAsBitmap(data, out offsetx, out offsety);
                    if (image != null)
                        return image;
                }

                return null;
            }
            catch
            {
                return null;
            }
		}

		// This checks a signature as byte array
		// NOTE: Expects the stream position to be at the start of the
		// signature, and expects the stream to be long enough.
		private static bool CheckSignature(Stream data, int[] sig)
		{
			//mxd. Rewind the data first
			data.Seek(0, SeekOrigin.Begin);
			
			// Go for all bytes
			foreach(int s in sig)
			{
				// When byte doesnt match the signature, leave
				if(data.ReadByte() != s) return false;
			}

			// Signature matches
			return true;
		}

		//mxd. This tries to guess if a given image is in TGA format...
		private static bool CheckTgaSignature(Stream data)
		{
			// TGA header is 18 bytes long
			if(data.Length < 18) return false;
			
			// Rewind the data first
			data.Seek(0, SeekOrigin.Begin);

			// Read TGA header
			int idlength = data.ReadByte(); // Can be 0 or the length of ID string, whatever that is
			int colormap = data.ReadByte(); // Can be 0 or 1
			if(colormap != 0 && colormap != 1) return false;

			int imagetype = data.ReadByte(); // Can be 0, 1, 2, 3, 9, 10, 11
			if((imagetype > 3 && imagetype < 9) || imagetype > 11) return false;

			data.Position += 9; // Skip some stuff...

			int width = data.ReadByte() + (data.ReadByte() << 8);
			if(width <= 0 || width > 8192) return false;

			int height = data.ReadByte() + (data.ReadByte() << 8);
			if(height <= 0 || height > 8192) return false;

			int bitsperpixel = data.ReadByte();  // Can be 8, 16, 24, 32
			return (bitsperpixel == 8 || bitsperpixel == 16 || bitsperpixel == 24 || bitsperpixel == 32);
		}
	}
}
