using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.IO;
using nft.framework.plugin;
using System.Diagnostics;
using Encoder = System.Drawing.Imaging.Encoder;
namespace nft.framework.drawing {
    /// <summary>
    /// Manage reference count of Image object and dispose on proper timing.
    /// </summary>
    public class ImageRef {
        /// <summary>
        /// Used as TranspalentColor to indicate that this image has no color to be transpalent.
        /// </summary>
        public static Color Undefined = Color.FromArgb((~Color.Transparent.ToArgb())&0xffffff);
        public enum Lifetime : short { Immediate = 0, Short, Long, Permanent };
        private Lifetime life;
        private short refcount;
        private object imgref = null;
        private Color transpalent = Color.Magenta;
        protected IFileSource source;

        protected ImageRef(IFileSource src)
            : this(src, Lifetime.Short) {
        }

        protected ImageRef(IFileSource src, Lifetime lifetime) {
            this.source = src;
            this.life = lifetime;
            this.refcount = 0;
        }

        public IFileSource FileSource {
            get {
                return source;
            }
        }

       /// <summary>
       /// Do not call while reference count is not zero.
       /// </summary>
        protected virtual void ForceDispose(){
            if (refcount > 0)
                throw new InvalidOperationException("Reference count is not zero.");
            if (imgref != null) {
                if (imgref is WeakReference) {
                    WeakReference wr = (WeakReference)imgref;
                    if (wr.IsAlive) {
                        ((Image)wr.Target).Dispose();
                    }
                } else {
                    ((Image)imgref).Dispose();
                }
                imgref = null;
            }
        }

        protected virtual Image PrepareImage() {
            Image ret;
            if (imgref != null) {
                if (imgref is WeakReference) {
                    WeakReference wr = (WeakReference)imgref;
                    if (wr.IsAlive) {
                        ret = (Image)wr.Target;
                    } else {
                        ret = CreateImage();
                    }
                } else {
                    ret = (Image)imgref;
                }
            } else {
                ret = CreateImage();
            }
            return ret;
        }

        public int RefCount { get { return refcount; } }

        public Color TranspalentColor {
            get { return transpalent; }
            set { transpalent = value; }
        }

        /// <summary>
        /// Returns image and increment reference count.
        /// NEVER Dispose the returned image. call ReleaseRef method instead.
        /// </summary>
        public Image AddRef() {
            Image ret = null;
            if (refcount == 0) {
                ret = PrepareImage();
                imgref = ret;
            } else {
                ret = (Image)imgref;
            }
            this.refcount++;
            return ret;
        }

        /// <summary>
        /// Do not access without calling AddRef() method.
        /// </summary>
        public Image Image {
            get {
                return imgref as Image;
            }
        }

        protected virtual Image CreateImage() {
            //return Image.FromFile(((LocalFile)source).AbsolutePath);
            Stream s = source.OpenRead();
            Image img = Bitmap.FromStream(s);
            s.Close();
            s.Dispose();
            return new Bitmap(img);
        }

        protected virtual void DisposeImage(Image image) {
            image.Dispose();
        }

        /// <summary>
        /// Decrease reference count.
        /// Do not Dispose the image which returned by AddRef method.
        /// </summary>
        public void ReleaseRef() {
            if (refcount > 0) {
                this.refcount--;
                if (refcount == 0) {
                    Isolated();
                }
            }
        }

        protected void Isolated() {
            switch (life) {
                case Lifetime.Long:
                    imgref = new WeakReference(imgref, true);
                    break;
                case Lifetime.Short:
                    imgref = new WeakReference(imgref, false);
                    break;
                case Lifetime.Permanent:
                    break;
                case Lifetime.Immediate:
                    DisposeImage((Image)imgref);
                    imgref = null;
                    break;
            }
        }

        public Lifetime ReferenceLife {
            get { return this.life; }
            set { this.life = value; }
        }

        public override bool Equals(object obj) {
            ImageRef iref = obj as ImageRef;
            if (iref != null) {
                return source.Equals(iref.source);
            } else {
                return false;
            }
        }

        public override int GetHashCode() {
            return source.GetHashCode();
        }

        #region local non bundled image treatment.
        static private readonly Dictionary<String, ImageRef> pathlist = new Dictionary<String, ImageRef>();

        /// <summary>
        /// Get or create ImageRef object for an image specifed by the file path
        /// Caller do not need to call first 'IncreaseRefCount'
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        public static ImageRef FromFile(string filename) {
            Uri uri = new Uri(filename);
            FileInfo info = new FileInfo(uri.LocalPath);
            string keypath = uri.AbsolutePath;
            ImageRef refObj = null;
            if (!pathlist.TryGetValue(keypath, out refObj)) {
                if (info.Exists) {
                    IFileSource src = new LocalFile(info.FullName);
                    refObj = new ImageRef(src, Lifetime.Short);
                    pathlist.Add(keypath, refObj);
                } else {
                    Debug.WriteLine("Image file not found for path="+info.FullName);
                }
            }
            return refObj;
        }

        /// <summary>
        /// Get or create ImageRef object for an image from FileSource object.
        /// Caller do not need to call first 'IncreaseRefCount'
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static ImageRef FromFile(IFileSource source) {            
            string keypath = source.Uri;
            ImageRef refObj = null;
            if (!pathlist.TryGetValue(keypath, out refObj)) {
                Lifetime lt = source.AccessSpeed == FileAccessSpeed.VerySlow ? Lifetime.Permanent : Lifetime.Long;
                refObj = new ImageRef(source, lt);
                pathlist.Add(keypath, refObj);
            }
            return refObj;
        }

        public static void FreeIsolatedImages() {
            List<string> work = new List<string>();
            foreach (string key in pathlist.Keys) {
                ImageRef ir = pathlist[key];
                if (ir.RefCount == 0) {
                    ir.ForceDispose();
                    work.Add(key);
                }
            }
            foreach (string key in work) {
                pathlist.Remove(key);
            }
        }
        #endregion
    }

}
