/*  
	Licensed under the Apache License, Version 2.0 (the "License");
	you may not use this file except in compliance with the License.
	You may obtain a copy of the License at
	
	http://www.apache.org/licenses/LICENSE-2.0
	
	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
 
*/

using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Media.Imaging;

namespace WPCordovaClassLib.Cordova.Commands
{
    public class ImageExifOrientation
    {
        public const int Portrait = 1;
        public const int PortraitUpsideDown = 3;
        public const int LandscapeLeft = 6;
        public const int LandscapeRight = 8;
    }

    public class ImageExifHelper
    {

        public static Stream RotateStream(Stream stream, int angle)
        {
            stream.Position = 0;
            if (angle % 90 != 0 || angle < 0)
            {
                throw new ArgumentException();
            }
            if (angle % 360 == 0)
            {
                return stream;
            }

            angle = angle % 360;

            BitmapImage bitmap = new BitmapImage();
            bitmap.SetSource(stream);
            WriteableBitmap wbSource = new WriteableBitmap(bitmap);

            WriteableBitmap wbTarget = null;

            int srcPixelWidth = wbSource.PixelWidth;
            int srcPixelHeight = wbSource.PixelHeight;

            if (angle % 180 == 0)
            {
                wbTarget = new WriteableBitmap(srcPixelWidth, srcPixelHeight);
            }
            else
            {
                wbTarget = new WriteableBitmap(srcPixelHeight, srcPixelWidth);
            }

            int destPixelWidth = wbTarget.PixelWidth;
            int[] srcPxls = wbSource.Pixels;
            int[] destPxls = wbTarget.Pixels;

            // this ugly if/else is to avoid a conditional check for every pixel
            if (angle == 90)
            {
                for (int x = 0; x < srcPixelWidth; x++)
                {
                    for (int y = 0; y < srcPixelHeight; y++)
                    {
                        destPxls[(srcPixelHeight - y - 1) + (x * destPixelWidth)] = srcPxls[x + y * srcPixelWidth];
                    }
                }
            }
            else if (angle == 180)
            {
                for (int x = 0; x < srcPixelWidth; x++)
                {
                    for (int y = 0; y < srcPixelHeight; y++)
                    {
                        destPxls[(srcPixelWidth - x - 1) + (srcPixelHeight - y - 1) * srcPixelWidth] = srcPxls[x + y * srcPixelWidth];
                    }
                }
            }
            else if (angle == 270)
            {
                for (int x = 0; x < srcPixelWidth; x++)
                {
                    for (int y = 0; y < srcPixelHeight; y++)
                    {
                        destPxls[y + (srcPixelWidth - x - 1) * destPixelWidth] = srcPxls[x + y * srcPixelWidth];
                    }
                }
            }

            MemoryStream targetStream = new MemoryStream();
            wbTarget.SaveJpeg(targetStream, destPixelWidth, wbTarget.PixelHeight, 0, 100);
            return targetStream;
        }

        public static int getImageOrientationFromStream(Stream imgStream)
        {

            // 0xFFD8 : jpgHeader
            // 0xFFE1 :
            // 0x???? : length of exif data
            // 0x????, 0x???? : Chars 'E','x','i','f'
            // 0x0000 : 2 empty bytes
            // <== mark beginning of tags SIZE:ID:VALUE
            // 0x???? : 'II' or 'MM' for Intel or Motorola ( always getting II on my WP7 devices ), determins littleEndian-ness
            // 0x002A : marker value
            // 0x???? : offset to the Image File Data

            // XXXX possible space before actual tag data ... we skip to mark + offset

            // 0x???? number of exif tags present

            // make sure we are at the begining
            imgStream.Seek(0, SeekOrigin.Begin);
            BinaryReader reader = new BinaryReader(imgStream);

            byte[] jpgHdr = reader.ReadBytes(2); // always (0xFFD8)

            byte start = reader.ReadByte(); // 0xFF
            byte index = reader.ReadByte(); // 0xE1

            while (start == 0xFF && index != 0xE1) // This never seems to happen, todo: optimize
            {
                // Get the data length
                ushort dLen = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
                // skip along
                reader.ReadBytes(dLen - 2);
                start = reader.ReadByte();
                index = reader.ReadByte();
            }

            // It's only success if we found the 0xFFE1 marker
            if (start != 0xFF || index != 0xE1)
            {
                //   throw new Exception("Could not find Exif data block");
                Debug.WriteLine("Did not find EXIF data");
                return 0;
            }

            // read 2 byte length of EXIF data
            ushort exifLen = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
            String exif = ""; // build the string
            for (var n = 0; n < 4; n++)
            {
                exif += reader.ReadChar();
            }
            if (exif != "Exif")
            {
                // did not find exif data ...
                Debug.WriteLine("Did not find EXIF data");
                return 0;
            }

            // read 2 empty bytes
            //ushort emptyBytes = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
            reader.ReadBytes(2);

            long headerMark = reader.BaseStream.Position; // where are we now <==

            //bool isLEndian = (reader.ReadChar() + "" + reader.ReadChar()) == "II";
            reader.ReadBytes(2); // 'II' or 'MM', but we don't care

            if (0x002A != BitConverter.ToUInt16(reader.ReadBytes(2), 0))
            {
                Debug.WriteLine("Error in data != 0x002A");
                return 0;
            }

            // Get the offset to the IFD (image file directory)
            ushort imgOffset = BitConverter.ToUInt16(reader.ReadBytes(2), 0);

            imgStream.Position = headerMark + imgOffset;
            ushort tagCount = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
            for (ushort x = 0; x < tagCount; x++)
            {
                // Orientation = 0x112, aka 274
                if (0x112 == BitConverter.ToUInt16(reader.ReadBytes(2), 0))
                {
                    ushort dType = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
                    // don't care ..
                    uint comps = reader.ReadUInt32();
                    byte[] tagData = reader.ReadBytes(4);
                    int orientation = (int)tagData[0];
                    Debug.WriteLine("orientation = " + orientation.ToString());
                    return orientation;
                    // 6 means rotate clockwise 90 deg
                    // 8 means rotate counter-clockwise 90 deg
                    // 1 means all is good
                    // 3 means flip vertical
                }
                // skip to the next item, 12 bytes each
                reader.BaseStream.Seek(10, SeekOrigin.Current);
            }
            return 0;
        }

    }
}
