using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace Utility {
    public unsafe class UnsafeBitmap : IDisposable {
        Bitmap bitmap;

        // three elements used for MakeGreyUnsafe
        int width;
        BitmapData bitmapData = null;
        Byte* pBase = null;

        public UnsafeBitmap(Bitmap bitmap) {
            this.bitmap = new Bitmap(bitmap);
        }

        public UnsafeBitmap(int width, int height) {
            this.bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
        }

        public void Dispose() {
            if (pBase != null)
                UnlockBitmap(); // For safety
            bitmap.Dispose();
        }

        //public Bitmap Bitmap {
        //    get {
        //        return bitmap;
        //    }
        //}

        private Point PixelSize {
            get {
                GraphicsUnit unit = GraphicsUnit.Pixel;
                RectangleF bounds = bitmap.GetBounds(ref unit);

                return new Point((int)bounds.Width, (int)bounds.Height);
            }
        }

        public void LockBitmap() {
            GraphicsUnit unit = GraphicsUnit.Pixel;
            RectangleF boundsF = bitmap.GetBounds(ref unit);
            Rectangle bounds = new Rectangle((int)boundsF.X, (int)boundsF.Y, (int)boundsF.Width, (int)boundsF.Height);

            // Figure out the number of bytes in a row
            // This is rounded up to be a multiple of 4
            // bytes, since a scan line in an image must always be a multiple of 4 bytes
            // in length.
            width = ((int)boundsF.Width * sizeof(PixelData) + 3) & ~0x3;
            //if (width % 4 != 0) {
            //    width = 4 * (width / 4 + 1);    // RMF -- Why not width = (width + 3) & !3
            //}
            bitmapData = bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            pBase = (Byte*)bitmapData.Scan0.ToPointer();
        }

        public PixelData GetPixel(int x, int y) {
            return *PixelAt(x, y);
        }

        public PixelData GetPixel(Point pos) {
            return *PixelAt(pos);
        }

        public void SetPixel(int x, int y, PixelData colour) {
            PixelData* pixel = PixelAt(x, y);
            *pixel = colour;
        }

        public void UnlockBitmap() {
            bitmap.UnlockBits(bitmapData);
            bitmapData = null;
            pBase = null;
        }

        public PixelData* PixelAt(int x, int y) {
            return (PixelData*)(pBase + y * width + x * sizeof(PixelData));
        }

        public PixelData* PixelAt(Point pos) {
            return PixelAt(pos.X, pos.Y);
        }

        public PixelData this[Point pos] { get { return *PixelAt(pos); } set { SetPixel(pos.X, pos.Y, value); } }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct PixelData {
        public enum rgb {
            red = 0,
            green = 1,
            blue = 2
        }

        public PixelData(Color clr) {
            red = clr.R;
            green = clr.G;
            blue = clr.B;
        }

        // This must match the internal ordering of BGR!
        public byte blue;
        public byte green;
        public byte red;
        
        // The rest of the values are properties or methods

        public byte R { get { return red; } set { red = value; } }
        public byte G { get { return green; } set { green = value; } }
        public byte B { get { return blue; } set { blue = value; } }

        // http://juicystudio.com/services/colourcontrast.php#diffformula
        public int Distance(Color df) {
            return ColourDistance(df, this);
        }

        public static int ColourDistance(Color e1, Color e2) {
            // From http://www.compuphase.com/cmetric.htm

            // Try simple
            var cd1 = Math.Abs(e1.R - e2.R) + Math.Abs(e1.G - e2.G) + Math.Abs(e1.B - e2.B);
            //if (cd1 > 0)
            //    return cd1; // Avoid error message

            int rmean = ((int)e1.R + (int)e2.R) / 2;
            int r = (int)e1.R - (int)e2.R;
            int g = (int)e1.G - (int)e2.G;
            int b = (int)e1.B - (int)e2.B;
            return (((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8);
        }

        public static implicit operator Color(PixelData pd) {
            return Color.FromArgb(0xff, pd.R, pd.G, pd.B);
        }

        public static implicit operator PixelData(Color pd) {
            return new PixelData(pd);
        }
        
        public int Brightness { get { return (299 * red + 587 * green + 114 * blue) / 1000; } }
        public byte this[rgb ndx] {     // So we can index into colors
            get {
                switch (ndx) {
                    case rgb.red: return red;
                    case rgb.green: return green;
                    case rgb.blue: return blue;
                }
                throw new Exception("Invalid color index(" + ndx + ")");
            }
            set {
                switch (ndx) {
                    case rgb.red: red = value; break;
                    case rgb.green: green = value; break;
                    case rgb.blue: blue = value; break;
                }
                throw new Exception("Invalid color index(" + ndx + ")");
            }
        }
    }
}