using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VisimBox.Resources;

namespace VisimBox.Renderer.Render2D
{

    public struct FontLetter
    {
        public System.Drawing.RectangleF TexturePosition;

        public FontLetter(float x, float y, float width, float height)
        {
            TexturePosition = new System.Drawing.RectangleF(x, y, width, height);
        }
    }


    public class Font : IResource, IResourceConsumer
    {
        protected Texture _Texture;

        protected Dictionary<char, FontLetter > Letters;

        /// <summary>
        /// Height of the font in pixels
        /// </summary>
        public int Height { get; set; }

        #region Constructors

        protected Font()
        {
        }

        /// <summary>
        /// Creates font from system font, exports standard ascii characters
        /// </summary>
        /// <param name="systemfont">System.Drawing.Font</param>
        public Font(System.Drawing.Font systemfont)
        {
            CreateFromFont(systemfont);
        }

        /// <summary>
        /// Creates font from system font, using specified characters
        /// </summary>
        /// <param name="systemfont">System.Drawing.Font</param>
        public Font(System.Drawing.Font systemfont, char[] characters)
        {
            CreateFromFont(systemfont, characters);
        }

        /// <summary>
        /// Creates font from system font with specified characters
        /// </summary>
        /// <param name="font"></param>
        /// <param name="characters"></param>
        public void CreateFromFont(System.Drawing.Font systemfont, char[] characters)
        {
            if (systemfont == null)
                throw new ArgumentNullException("systemfont");

            if (_Texture != null)
                Dispose();

            //NOTE!!! should cut to more rectangular bitmap probably....

            string totalstring = new string(characters);

            Height = systemfont.Height;

            //first find out the size

            int width = 512;
            int height = Height;

            VisiMath.MathTools.NearestTextureSize(ref width, ref height);


            //guess size
            System.Drawing.Bitmap canvas = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

            System.Drawing.Graphics gr = System.Drawing.Graphics.FromImage(canvas);

            System.Drawing.SizeF sizeF = gr.MeasureString(totalstring, systemfont);

            if ((sizeF.Width > width) || (sizeF.Height > height))
            {
                //resize
                gr.Dispose();
                canvas.Dispose();

                width = (int)sizeF.Width;
                height = (int)sizeF.Height;
                VisiMath.MathTools.NearestTextureSize(ref width, ref height);
                canvas = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                gr = System.Drawing.Graphics.FromImage(canvas);
            }
            
            System.Drawing.Brush br = new System.Drawing.SolidBrush(System.Drawing.Color.White);

            float xpos = 0;
            float ypos = 0;
            
            System.Drawing.RectangleF rf = new System.Drawing.RectangleF(new System.Drawing.PointF(0, 0), sizeF);
            System.Drawing.StringFormat strfor = new System.Drawing.StringFormat(System.Drawing.StringFormat.GenericTypographic);
            System.Drawing.CharacterRange[] cr = new System.Drawing.CharacterRange[1];
            cr[0] = new System.Drawing.CharacterRange(0, 1);
            strfor.FormatFlags = System.Drawing.StringFormatFlags.NoClip; 
            strfor.SetMeasurableCharacterRanges(cr);

            //gr.DrawString(totalstring, systemfont, br, rf, strfor);
                

            Letters = new Dictionary<char, FontLetter>(characters.Length);

            //Write all characters to the bitmap and calculate sizes
            for (int i = 0; i < characters.Length; i++)
            {
                string st = new string(characters[i],1);

                //regions = gr.MeasureCharacterRanges(totalstring, systemfont, System.Drawing.RectangleF.Empty, strfor);

                //sizeF = gr.MeasureString(st, systemfont, 500, strfor);
                sizeF = gr.MeasureString(st, systemfont);                
                
                System.Drawing.Region[] regions = gr.MeasureCharacterRanges(st, systemfont, System.Drawing.RectangleF.Empty, strfor);
                
                gr.DrawString(st, systemfont, br, xpos, ypos);

                System.Drawing.RectangleF rect = regions[0].GetBounds(gr);

                float x, y, w, h;
                w = (sizeF.Width-2) / width;

                if (characters[i] == ' ')
                {
                    w = 5.0f / width;
                }

                //w = (rect.Right) / width;
                //h = sizeF.Height / height;
                h = 1.0f;
                x = ((float)xpos) / width;
                y = 0.0f;

                Letters.Add(characters[i], new FontLetter(x, y, w, h));
                xpos += sizeF.Width;
                xpos -= 2;
            }

            br.Dispose();
            gr.Dispose();

            //Bitmap now contains all characters.
            //canvas.Save("daka.png");

            string name = systemfont.Name+height.ToString();
            _Texture = VisimEngine.Instance.Renderer.TextureMan.Add(ref name, canvas);
            _Texture.AddReference(this);

            canvas.Dispose();
            if (_Texture == null)
            {
                throw new Exception("CreateFont: Couldn't create texture!");
            }
        }


        /// <summary>
        /// Creates font from system font, using standard ASCII character set (from 32 ' ' to 128) if no other parameters are added
        /// </summary>
        /// <param name="systemfont">System.Drawing.Font</param>
        /// <param name="start">First character</param>
        /// <param name="end">Last character</param>
        public void CreateFromFont(System.Drawing.Font systemfont, int start = 32, int end = 128)
        {
            char[] characters = new char[end - start];
            for (int i = 0; i < characters.Length; i++)
            {
                characters[i] = (char)(start + i);
            }
            CreateFromFont(systemfont, characters);
        }


        #endregion


        #region Drawing text

        /// <summary>
        /// Returns simple block of sprites for text (without any limiting)
        /// </summary>
        /// <param name="x">Required starting X position</param>
        /// <param name="y">Required starting Y position</param>
        /// <param name="str">text to convert</param>
        /// <returns>Block of sprites for each letter</returns>
        public SpriteInfo[] GetTextSprites(int x, int y, string str)
        {
            SpriteInfo[] rval = new SpriteInfo[str.Length];

            for (int i = 0; i < str.Length; i++)
            {
                FontLetter letter;
                if (Letters.TryGetValue(str[i], out letter))
                {                                        
                    int wi = LetterWidth(str[i]);
                    rval[i] = new SpriteInfo(x, y, wi, Height, letter.TexturePosition.Left, letter.TexturePosition.Top, letter.TexturePosition.Width, letter.TexturePosition.Height);
                    x += wi;
                }
            }
            return rval;
        }


        /// <summary>
        /// Returns batch of the sprites for text
        /// </summary>
        /// <param name="str">Text to convert to sprites</param>
        /// <param name="maxwidth">maximal allowed width (if 0, no limit)</param>
        /// <param name="maxheight">maximal allowed height (if 0, no limit)</param>
        /// <returns>SpriteBatch with text</returns>
        public SpriteBatch GetTextSprites(string str, int maxwidth = 0, int maxheight = 0)
        {
            int xpos = 0;
            int ypos = 0;

            int i = 0;

            List<SpriteInfo> sprites = new List<SpriteInfo>(str.Length);

            while (i < str.Length)
            {
                if (str[i] == '\n')
                {
                    xpos = 0;
                    ypos += Height;
                    i++;
                    if ((maxheight != 0) && (ypos > maxheight))
                        break;
                }
                else
                {
                    //get next word.
                    string st = str.Substring(i);
                    int idx = st.IndexOf('\n');
                    if (idx != -1)
                    {
                        st = st.Substring(0, idx);
                    }
                    idx = st.IndexOf(' ');
                    if (idx != -1)
                    {
                        if (idx != (st.Length - 1))
                            st = st.Substring(0, idx + 1);
                    }
                    int wi = GetLineWidth(st);
                    if ((maxwidth != 0 ) && ((wi + xpos) > maxwidth))
                    {
                        xpos = 0;
                        ypos += Height;
                        if ((maxheight != 0) && (ypos > maxheight))
                            break;
                    }

                    sprites.AddRange(GetTextSprites(xpos, ypos, st));
                    xpos += wi;
                    i += st.Length;
                }
            }

            List<Vertex2D> spritelist = new List<Vertex2D>(sprites.Count * 4);

            foreach (var sprite in sprites)
            {
                spritelist.AddRange(sprite.Vertices);
            }

            //optimize, put it directly to the array

            SpriteBatch rval = new SpriteBatch(_Texture, sprites);
            return rval;
        }

        /// <summary>
        /// Simple text drawing on selected coordinate.
        /// </summary>
        /// <param name="x">X coordinate</param>
        /// <param name="y">Y coordinate</param>
        /// <param name="text">text </param>
        public void DrawText(IRenderContext rc, int x, int y, string text)
        {
            SpriteBatch sprites = GetTextSprites(text);
            rc.DrawSprites(x, y, sprites);
        }
        
        /// <summary>
        /// Draw text on selected coordinate with specified max size of the text block.
        /// </summary>
        /// <param name="x">X coordinate</param>
        /// <param name="y">Y coordinate</param>
        /// <param name="maxheight">Maximal height of the text block</param>
        /// <param name="maxwidth">Maximal width of the text block</param>
        /// <param name="text">text </param>
        public void DrawText(IRenderContext rc, int x, int y, int maxwidth, int maxheight, string text)
        {
            SpriteBatch sprites = GetTextSprites(text, maxwidth, maxheight);
            rc.DrawSprites(x, y, sprites);
        }


        #endregion


        #region Letters

        /// <summary>
        /// Returns width of the letter (including space after the letter)
        /// </summary>
        /// <param name="ch">Required letter</param>
        /// <returns>Width in pixels or zero if font doesn't contain letter</returns>
        public int LetterWidth(char ch)
        {
            
            FontLetter fl;
            if (Letters.TryGetValue(ch, out fl))
            {
                return (int)(1.1f * fl.TexturePosition.Width * _Texture.Width);
            }
            return 0;
        }

        #endregion



        #region Repaging and utilities

        /// <summary>
        /// Returns width of the string line in pixels
        /// </summary>
        /// <param name="str">String to measure</param>
        /// <returns>String width</returns>
        public int GetLineWidth(string str)
        {
            int xpos = 0;
            for (int i = 0; i < str.Length; i++)
            {
                xpos += LetterWidth(str[i]);
            }
            return xpos;
        }

        /// <summary>
        /// Calculates text area taken by the text (for text with more lines).
        /// Resulting area is from x/y to maxx/maxxy
        /// </summary>
        /// <param name="x">Starting x</param>
        /// <param name="y">Starting y</param>
        /// <param name="str">text</param>
        /// <param name="maxx">Maximal X value</param>
        /// <param name="maxy">Maximal Y value</param>
        public void GetTextArea(int x, int y, string str, out int maxx, out int maxy)
        {
            int xposmax = x;
            int xpos = x;
            int ypos = y;

            for (int i = 0; i < str.Length; i++)
            {
                if (str[i] == '\n')
                {
                    if (xpos > xposmax)
                        xposmax = xpos;
                    xpos = x;
                    ypos += Height;
                }
                else
                    xpos+=LetterWidth(str[i]);
            }
            if (xpos > xposmax)
                xposmax = xpos;

            maxx = xposmax;
            maxy = ypos + Height;
        }

        /// <summary>
        /// Repages string (inserts end of line characters) so maximal width will be only specified size in pixels
        /// </summary>
        /// <param name="width">Maximal pixel width</param>
        /// <param name="str">String to repage</param>
        /// <returns>Repaged string</returns>
        public string RepageToString(int width, string str)
        {
            List<string> rep = Repage(width, str);

            string rval = "";
            foreach (string s in rep)
            {
                rval += s + "\n";
            }
            return rval;
        }

        /// <summary>
        /// Reformats string to several lines so they can be shown in specified screen width
        /// </summary>
        /// <param name="width">Requested pixel width</param>
        /// <param name="str">String to repage</param>
        /// <returns>List of lines</returns>
        public List<string> Repage(int width, string str)
        {
            List<string> rval = new List<string>();
            rval.Add("");

            int wi = 0;

            int idx = 0;
            while (idx < str.Length)
            {
                //get line
                string st = str.Substring(idx);

                int eol = st.IndexOf('\n');
                if (eol != -1)
                {
                    st = st.Substring(0, eol);
                }

                //solve line

                int idx2 = 0;

                while (idx2 < st.Length)
                {
                    string line = st.Substring(idx2);

                    int idx3 = line.IndexOf(' ');
                    if (idx3 != -1)
                    {
                        line = line.Substring(0, idx3 + 1);
                    }

                    int w = GetLineWidth(line);

                    if ((wi + w) > width)
                    {
                        // new line
                        rval.Add("");
                        wi = 0;
                    }
                    wi += w;

                    rval[rval.Count - 1] += line;
                    idx2 += line.Length;
                    idx += line.Length;
                }
                if ((idx < str.Length) && (str[idx] == '\n'))
                {
                    rval[rval.Count - 1] += "\n";
                    rval.Add("");
                    idx++;
                }
                if (idx >= str.Length)
                    break;
            }
            return rval;
        }

        /// <summary>
        /// Reformats string to several pages so each page would fit inside specified window
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <returns></returns>
        public string[] Repage(int width, int height, string str)
        {
            List<string> repaged = Repage(width, str);

            List<string> rval = new List<string>();

            double y = 0;
            int idx = 0;
            rval.Add("");
            for (int i = 0; i < repaged.Count; i++)
            {
                rval[idx] += repaged[i];
                y += Height;
                if ((y >= height))
                {
                    y = 0;
                    idx++;
                    rval.Add("");
                }
            }

            string[] fin = new string[rval.Count];
            for (int i = 0; i < rval.Count; i++)
            {
                fin[i] = rval[i];
            }
            return fin;
        }




        #endregion 




        #region IResource Members


        /// <summary>
        /// If true, resource will stay in memory even if it's not referenced
        /// </summary>
        public virtual bool AlwaysStay { get; set; }

        /// <summary>
        /// Type of the resource - font
        /// </summary>
        public ResourceType ResourceType
        {
            get { return Resources.ResourceType.Font; }
        }

        /// <summary>
        /// Usage of the font
        /// </summary>
        public ResourceUsage Usage
        {
            get { return ResourceUsage.Static; }
        }

        protected string _FileName;

        /// <summary>
        /// Filename of the font
        /// </summary>
        public string FileName
        {
            get
            {
                return _FileName;
            }
            set
            {
                _FileName = value;
            }
        }

        #endregion

        #region IDisposable Members

        public void Clear()
        {
            if (_Texture != null)
            {
                _Texture.RemoveReference(this);                
                //_Texture.Dispose(); //dont dispose, someone might be using it, leave it on manager
                _Texture = null;
            }
        }

        public void Dispose()
        {
            Clear();
            DisposeConsumers();
        }

        #endregion

        #region IResource Members


        protected List<IResourceConsumer> Consumers = new List<IResourceConsumer>();

        /// <summary>
        /// Number of current references
        /// </summary>
        public int References { get { return Consumers.Count; } }

        /// <summary>
        /// Returns number of null generic references
        /// </summary>
        public int NullReferences
        {
            get
            {
                int rval = 0;
                for (int i = 0; i < Consumers.Count; i++)
                {
                    if (Consumers[i] == null)
                        rval++;
                }
                return rval;
            }
        }

        /// <summary>
        /// Adds reference to this resource
        /// </summary>
        /// <param name="consumer">Consumer of resource</param>
        public virtual void AddReference(IResourceConsumer consumer)
        {
            Consumers.Add(consumer);
        }

        /// <summary>
        /// Removes reference to this resource
        /// </summary>
        /// <param name="consumer">Consumer of resource</param>
        public virtual void RemoveReference(IResourceConsumer consumer)
        {
            Consumers.Remove(consumer);
        }

        /// <summary>
        /// Manager wants to replace this resource by another - resource should inform all of it's consumers that it's being replaced 
        /// and update references based on their response
        /// It should also check if new resource is of compatible type
        /// </summary>
        /// <param name="newresource">New resource</param>
        /// <returns>True if resource is compatible type and all consumers accepted resource changing, false otherwise</returns>
        public virtual bool ReplaceReferences(IResource newresource)
        {
            //todo: add type checking?!
            bool rval = true;

            for (int i = 0; i < Consumers.Count; i++)
            {
                IResourceConsumer consumer = Consumers[i];
                if (consumer != null)
                {
                    bool consresponse = consumer.ResourceReplaced(this, newresource);

                    if (consresponse)
                    {
                        RemoveReference(consumer);

                        if (newresource != null)
                        {
                            newresource.AddReference(consumer);
                        }
                    }
                    else rval = false;
                }
            }

            return rval;
        }

        protected virtual void DisposeConsumers()
        {
            for (int i = 0; i < Consumers.Count; i++)
            {
                if (Consumers[i] != null)
                {
                    Consumers[i].ResourceDisposing(this);
                }
            }
            Consumers.Clear();
        }


        #endregion

        #region IResourceConsumer Members

        public void ResourceDisposing(IResource resource)
        {
            if (_Texture == null)
                return;
            if ((resource as Texture) == _Texture)
            {
                //somebody stole our texture! Let's dispose ourselves
                Console.DebugLine("Font" + this.FileName + " lost it's texture!");
                Dispose();
            }
        }

        public bool ResourceReplaced(IResource original, IResource newresource)
        {
            if ((original as Texture) == _Texture)
            {
                //somebody changed our texture!

                Texture newtex = newresource as Texture;
                if ((newtex) == null)
                {
                    Console.DebugLine("Attempt to replace font texture with null or uncompatible resource type!");
                    return false;
                }
                _Texture = newtex;
                return true;
            }
            Console.DebugLine("Font: ResourceReplaced callback with wrong data?");
            return false;
        }

        #endregion
    }
}
