using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Drawing;
using System.Windows.Forms;

using nft.core.game;
using nft.core.geometry;
using nft.util;
using nft.framework.drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Diagnostics;
using nft.framework.plugin;
using nft.framework;

namespace nft.contributions.game {
    public class CtbImageImportTerrainGenerator : CtbTerrainGenerator, ITerrainGenerator
	{
		//public const string KEY_MAX_HEIGHT = "MaxHeight";
        public const string KEY_SRCIMG_PATH = "SoruceImagePath";
        public const string KEY_HORZ_SCALING = "HorizontalScaling";
        public const string KEY_VERT_SCALING = "VerticalScaling";
        public const string KEY_COLOR_CHANNEL = "UseColorChannel";
        public const string KEY_SEA_LEVEL_HEIGHT = "SeaLevelHeight";

        public CtbImageImportTerrainGenerator(Plugin p, ParamsReader contrib) : base(p, contrib) { }		
		public override ITerrainGenerator Generator { get {	return this; } }

		#region ITerrainGenerator メンバ
        public override ITerrainMap Generate(ParamSet param) 
		{
            string src = param[KEY_SRCIMG_PATH];
            float vr = param[KEY_VERT_SCALING, 1.0f];
            float hr = param[KEY_HORZ_SCALING, 1.0f];
            ColorChannel cc = (ColorChannel)Enum.Parse(typeof(ColorChannel), param[KEY_COLOR_CHANNEL]);
            int sea = param[KEY_SEA_LEVEL_HEIGHT, 0];
            ImageRef iref = ImageRef.FromFile(src);
            return new BitmapSourceTerrainMap(iref, hr, vr, cc, sea);
		}
        #endregion

    }

    public class BitmapSourceTerrainMap : ITerrainMap {
        protected ImageRef imgRef = null;
        protected Size3DF size;
        protected ColorChannel channel;
        protected double vertRate;
        protected double horzRate;
        protected int seaLevel;
        
        public BitmapSourceTerrainMap(ImageRef img, double hRate, double vRate, ColorChannel channel, int seaHeight) {
            this.channel = channel;
            this.vertRate = vRate * hRate;
            this.horzRate = hRate;
            this.size = new Size3DF();
            this.seaLevel = ApplyVertRate(seaHeight * vertRate);
            SetImageRef(img);            
        }

        ~BitmapSourceTerrainMap() {
            Dispose(false);
        }

        protected void SetImageRef(ImageRef imgNew) {
            if (imgRef != null) {
                imgRef.ReleaseRef();
            }
            imgRef = imgNew;
            imgRef.AddRef();
            Image image = imgRef.Image;
            this.size.sx = (float)Math.Floor(image.Width * horzRate);
            this.size.sy = (float)Math.Floor(image.Height * horzRate);
            this.size.sz = (float)Math.Floor(255 * vertRate);
       }

        #region ITerrainMap メンバ

        public Size3D Size {
            get { return Size3D.Ceiling(size); }
        }

        public int Height(int x, int y) {
            PointF pt = new PointF((float)(x / horzRate), (float)(y / horzRate));
            Bitmap bmp = imgRef.Image as Bitmap;
            int sx = (int)Math.Floor(pt.X);
            int sy = (int)Math.Floor(pt.Y);
            int sx2 = (int)Math.Ceiling(pt.X + horzRate);
            int sy2 = (int)Math.Ceiling(pt.Y + horzRate);
            Rectangle r = new Rectangle(sx, sy, sx2 - sx, sy2 - sy);
            if (horzRate <= 1.0) {
                return CalcHeightN(bmp, pt, r);
            } else {
                Debug.WriteLine(r);
                return CalcHeightW(bmp, pt, r);
            }
        }

        public bool IsCliffedBounds(Direction4 direction4, int x, int y) {
            throw new NotImplementedException();
        }

        protected int ApplyVertRate(double h) {
            /*
            double h2 = (h - 127) * vertRate;
            return (int) (h2+127);
             */
            return (int)(h*vertRate);
        }

        protected int CalcHeightN(Bitmap bmp, PointF pt, Rectangle rect) {
            double height = 0;
            double vtop = rect.Top + 1 - pt.Y;
            double vleft = rect.Left + 1 - pt.X;
            double vbotm = pt.Y + horzRate - rect.Bottom + 1;
            double vrght = pt.X + horzRate - rect.Right + 1;
            Debug.Assert(vtop <= 1.0 && vleft <= 1.0 && vbotm <= 1.0 && vrght <= 1.0);
            rect.Y = bmp.Height - rect.Bottom;
            BitmapData data = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
            IntPtr ptr = data.Scan0;
            Int32 c;
            try {
                if (rect.Width <= 1) {
                    if (rect.Height <= 1) {
                        c = Marshal.ReadInt32(ptr);
                        height = PixelFormatUtil.GetChannelVal(c, channel);
                    } else {
                        c = Marshal.ReadInt32(ptr);
                        height += PixelFormatUtil.GetChannelVal(c, channel) * vleft;
                        c = Marshal.ReadInt32(ptr, 1);
                        height += PixelFormatUtil.GetChannelVal(c, channel) * vrght;
                        height /= horzRate;
                    }
                } else {
                    if (rect.Height <= 1) {
                        c = Marshal.ReadInt32(ptr);
                        height += PixelFormatUtil.GetChannelVal(c, channel) * vtop;
                        c = Marshal.ReadInt32(ptr, 1);
                        height += PixelFormatUtil.GetChannelVal(c, channel) * vbotm;
                        height /= horzRate;
                    } else {
                        c = Marshal.ReadInt32(ptr);
                        height += PixelFormatUtil.GetChannelVal(c, channel) * vtop * vleft;
                        c = Marshal.ReadInt32(ptr, 1);
                        height += PixelFormatUtil.GetChannelVal(c, channel) * vtop * vrght;
                        c = Marshal.ReadInt32(ptr, 2);
                        height += PixelFormatUtil.GetChannelVal(c, channel) * vbotm * vleft;
                        c = Marshal.ReadInt32(ptr, 3);
                        height += PixelFormatUtil.GetChannelVal(c, channel) * vbotm * vrght;
                        height /= (rect.Width * rect.Height);
                    }
                }
                return ApplyVertRate(height);
            } finally {
                bmp.UnlockBits(data);
            }
        }

        protected int CalcHeightW(Bitmap bmp, PointF pt, Rectangle rect) {
            Rectangle inner = new Rectangle((int)Math.Ceiling(pt.X), (int)Math.Ceiling(pt.Y),
                (int)Math.Floor(pt.X + horzRate), (int)Math.Floor(pt.Y + horzRate));
            double height = 0;
            double vtop = inner.Top - pt.Y;
            double vleft = inner.Left - pt.X;
            double vbotm = pt.Y + horzRate - inner.Bottom;
            double vrght = pt.X + horzRate - inner.Right;
            Debug.Assert(vtop <= 1.0 && vleft <= 1.0 && vbotm <= 1.0 && vrght <= 1.0);
            if (rect.Right > bmp.Width) rect.Width = bmp.Width - rect.X;
            if (rect.Bottom > bmp.Height) rect.Height = bmp.Height - rect.Y;
            rect.Y = bmp.Height - rect.Bottom;
            BitmapData data = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
            IntPtr ptr = data.Scan0;
            Int32 c;
            int offset = 0;
            if (vtop > 0) {
                double h0 = 0;
                if (vleft > 0) {
                    c = Marshal.ReadInt32(ptr, offset++);
                    h0 += PixelFormatUtil.GetChannelVal(c, channel) * vleft;
                }
                for (int x = 0; x < inner.Width; x++) {
                    c = Marshal.ReadInt32(ptr, offset++);
                    h0 += PixelFormatUtil.GetChannelVal(c, channel);
                }
                if (vrght > 0) {
                    c = Marshal.ReadInt32(ptr, offset++);
                    h0 += PixelFormatUtil.GetChannelVal(c, channel) * vrght;
                }
                height += h0 * vtop;
            }
            for (int y = inner.Top; y < inner.Bottom; y++) {
                double h0 = 0;
                if (vleft > 0) {
                    c = Marshal.ReadInt32(ptr, offset++);
                    h0 += PixelFormatUtil.GetChannelVal(c, channel) * vleft;
                }
                for (int x = 0; x < inner.Width; x++) {
                    c = Marshal.ReadInt32(ptr, offset++);
                    h0 += PixelFormatUtil.GetChannelVal(c, channel);
                }
                if (vrght > 0) {
                    c = Marshal.ReadInt32(ptr, offset++);
                    h0 += PixelFormatUtil.GetChannelVal(c, channel) * vrght;
                }
                height += h0;
            }
            if (vbotm > 0) {
                double h0 = 0;
                if (vleft > 0) {
                    c = Marshal.ReadInt32(ptr, offset++);
                    h0 += PixelFormatUtil.GetChannelVal(c, channel) * vleft;
                }
                for (int x = 0; x < inner.Width; x++) {
                    c = Marshal.ReadInt32(ptr, offset++);
                    h0 += PixelFormatUtil.GetChannelVal(c, channel);
                }
                if (vrght > 0) {
                    c = Marshal.ReadInt32(ptr, offset++);
                    h0 += PixelFormatUtil.GetChannelVal(c, channel) * vrght;
                }
                height += h0 * vbotm;
            }
            bmp.UnlockBits(data);
            return ApplyVertRate(height / (rect.Width * rect.Height));
        }

        public int DetailedHeight(double dx, double dy) {
            double x = dx / horzRate;
            double y = dy / horzRate;
            Bitmap bmp = imgRef.Image as Bitmap;
            int x0 = (int)Math.Floor(x);
            int x1 = (int)Math.Ceiling(x);
            int y0 = (int)Math.Floor(y);
            int y1 = (int)Math.Ceiling(y);
            if (x1 >= size.sx || y1 >= size.sy) return -1;
            int c = bmp.GetPixel(x0, y0).ToArgb();
            int h = PixelFormatUtil.GetChannelVal(c, channel);
            if (x0 == x1) {
                if (y0 != y1) {
                    double v = h * (y1 - y);
                    c = bmp.GetPixel(x0, y1).ToArgb();
                    h = PixelFormatUtil.GetChannelVal(c, channel);
                    v += h * (y - y0);
                    c = (int)v;
                }
            } else {
                if (y0 == y1) {
                    double v = h * (x1 - x);
                    c = bmp.GetPixel(x1, y0).ToArgb();
                    h = PixelFormatUtil.GetChannelVal(c, channel);
                    v += h * (x - x0);
                    c = (int)v;
                } else {
                    double v = h * (x1 - x) * (y1 - y);                   
                    c = bmp.GetPixel(x0, y1).ToArgb();
                    h = PixelFormatUtil.GetChannelVal(c, channel);
                    v += h * (x1 - x) * (y - y0);
                    c = bmp.GetPixel(x1, y1).ToArgb();
                    h = PixelFormatUtil.GetChannelVal(c, channel);
                    v += h * (x - x0) * (y - y0);
                    c = bmp.GetPixel(x1, y0).ToArgb();
                    h = PixelFormatUtil.GetChannelVal(c, channel);
                    v += h * (x - x0) * (y1 - y);
                    c = (int)v;
                }
            }            
            return ApplyVertRate(h);
        }

        public bool IsDetailedHeight {
            get { return false; }// horzRate < 0.5; }
        }

        public int WaterDepth(int x, int y) {
            return 0;
        }

        public int HeightOffset {
            get { return seaLevel; }
        }

        protected void Dispose(bool disposing) {
            if (disposing) {
                if (imgRef != null) {
                    imgRef.ReleaseRef();
                }
            }
            imgRef = null;
        }

        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

    }

}
