//------------------------------------------------------------------------------
// <copyright file=”*.cs” company=”EhLib Team”>
//     Copyright (c) 2017 Dmitry V. Bolshakov   
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

namespace EhLib
{

  [DesignerCategory("Code")]
  [ToolboxItem(false)]
  [TypeConverter(typeof(ExpandableObjectConverter))]
  public class CellImageBox : Component
  {

    #region private consts
    private static readonly object EventKeyPaint = new object();
    #endregion

    #region privates
    private HorizontalAlignment horzAlign = HorizontalAlignment.Left;
    private VerticalAlignment vertAlign = VerticalAlignment.Center;
    private TextImageRelation textImageRelation = TextImageRelation.ImageBeforeText;
    private int imageIndex;
    private string imageKey = String.Empty;
    private bool useIntegerIndex = true;
    private ImageList imageList;
    private Image image;
    private Padding padding = Padding.Empty;
    private bool sizeStored;
    private Size customSize;
    private bool visibleStored;
    private bool customVisible;
    private ICellImageBoxOwner owner;
    #endregion

    protected ICellImageBoxOwner Owner { get { return owner; } }

    public CellImageBox(ICellImageBoxOwner owner)
    {
      this.owner = owner;
      imageIndex = -1;
    }

    #region design-time properties
    [DefaultValue(null)]
    public Image Image
    {
      get
      {
        if (image == null && imageList != null)
        {
          int actualIndex = ActualImageIndex;

          if (actualIndex >= imageList.Images.Count)
          {
            actualIndex = imageList.Images.Count - 1;
          }

          if (actualIndex >= 0)
          {
            return imageList.Images[actualIndex];
          }
        }
        return image;
      }

      set
      {
        if (Image != value)
        {
          image = value;
          if (image != null)
          {
            ImageIndex = -1;
            ImageList = null;
          }
          Owner.CellImageBoxChanged(this);
        }
      }
    }

    [DefaultValue(HorizontalAlignment.Left)]
    public HorizontalAlignment HorzAlign
    {
      get
      {
        return horzAlign;
      }
      set
      {
        if (value != horzAlign)
        {
          horzAlign = value;
          Owner.CellImageBoxChanged(this);
        }
      }
    }

    [DefaultValue(VerticalAlignment.Center)]
    public VerticalAlignment VertAlign
    {
      get
      {
        return vertAlign;
      }
      set
      {
        if (value != vertAlign)
        {
          vertAlign = value;
          Owner.CellImageBoxChanged(this);
        }
      }
    }

    [TypeConverterAttribute(typeof(ImageIndexConverter))]
    [Editor("System.Windows.Forms.Design.ImageIndexEditor", typeof(UITypeEditor))]
    [DefaultValue(-1)]
    [RefreshProperties(RefreshProperties.Repaint)]
    public int ImageIndex
    {
      get
      {
        if (imageIndex != -1 &&
            imageList != null &&
            imageIndex >= imageList.Images.Count)
        {
          return imageList.Images.Count - 1;
        }
        else
        {
          return ActualImageIndex;
        }
      }
      set
      {
        if (value < -1)
        {
          throw new ArgumentOutOfRangeException("ImageIndex");
        }
        if (imageIndex != value)
        {
          if (value != -1)
          {
            image = null;
          }

          imageKey = String.Empty;
          imageIndex = value;
          useIntegerIndex = true;

          Owner.CellImageBoxChanged(this);
        }
      }
    }

    [TypeConverter(typeof(ImageKeyConverter))]
    [Editor("System.Windows.Forms.Design.ImageIndexEditor", typeof(UITypeEditor))]
    [DefaultValue("")]
    [RefreshProperties(RefreshProperties.Repaint)]
    public string ImageKey
    {
      get
      {
        return imageKey;
      }
      set
      {
        if (imageKey != value)
        {
          if (value != null)
            image = null;

          imageIndex = -1;
          imageKey = (value == null ? String.Empty : value);
          useIntegerIndex = false;
          Owner.CellImageBoxChanged(this);
        }
      }
    }

    [DefaultValue(null)]
    [RefreshProperties(RefreshProperties.Repaint)]
    public ImageList ImageList
    {
      get
      {
        return imageList;
      }
      set
      {
        if (imageList != value)
        {
          EventHandler recreateHandler = new EventHandler(ImageListRecreateHandle);
          EventHandler disposedHandler = new EventHandler(DetachImageList);

          if (imageList != null)
          {
            imageList.RecreateHandle -= recreateHandler;
            imageList.Disposed -= disposedHandler;
          }

          if (value != null)
            image = null;

          imageList = value;

          if (value != null)
          {
            value.RecreateHandle += recreateHandler;
            value.Disposed += disposedHandler;
          }

          Owner.CellImageBoxChanged(this);
        }
      }
    }

    [DefaultValue(TextImageRelation.ImageBeforeText)]
    public TextImageRelation TextImageRelation
    {
      get
      {
        return textImageRelation;
      }
      set
      {
        if (value != TextImageRelation)
        {
          textImageRelation = value;
          //LayoutTransaction.DoLayoutIf(AutoSize, ParentInternal, this, PropertyNames.TextImageRelation);
          Owner.CellImageBoxChanged(this);
        }
      }
    }

    public Padding Padding
    {
      get
      {
        return padding;
      }
      set
      {
        padding = value;
        Owner.CellImageBoxChanged(this);
      }
    }

    public Size Size
    {
      get
      {
        if (sizeStored)
          return customSize;
        else
          return DefaultSize();
      }
      set
      {
        customSize = value;
        sizeStored = true;
        Owner.CellImageBoxChanged(this);
      }
    }

    public bool Visible
    {
      get
      {
        if (visibleStored)
          return customVisible;
        else
          return DefaultVisible();
      }
      set
      {
        customVisible = value;
        visibleStored = true;
        Owner.CellImageBoxChanged(this);
      }
    }
    #endregion

    #region event
    public event EventHandler<CellImageBoxPaintEventArgs> Paint
    {
      add
      {
        this.Events.AddHandler(EventKeyPaint, value);
      }
      remove
      {
        this.Events.RemoveHandler(EventKeyPaint, value);
      }
    }
    #endregion

    #region run-time properties
    [Browsable(false)]
    public virtual int ActualImageIndex
    {
      get
      {
        if (useIntegerIndex)
        {
          return imageIndex;
        }
        else if (ImageList != null)
        {
          return ImageList.Images.IndexOfKey(ImageKey);
        }
        else
        {
          return -1;
        }
      }
    }
    #endregion

    #region methods
    private void ImageListRecreateHandle(object sender, EventArgs e)
    {
      Owner.CellImageBoxChanged(this);
    }

    private void DetachImageList(object sender, EventArgs e)
    {
      ImageList = null;
    }

    public virtual bool ShouldSerializePadding()
    {
      return (padding != Padding.Empty);
    }

    public virtual void ResetPadding()
    {
      padding = Padding.Empty;
      Owner.CellImageBoxChanged(this);
    }

    public virtual Size DefaultSize()
    {
      if (Image != null)
        return new Size(Image.Size.Width + Padding.Left + Padding.Right,
                        Image.Size.Height + Padding.Top + Padding.Bottom);
      else
        return Size.Empty;
    }

    public virtual bool ShouldSerializeSize()
    {
      return sizeStored;
    }

    public virtual void ResetSize()
    {
      sizeStored = false;
      Owner.CellImageBoxChanged(this);
    }

    public bool DefaultVisible()
    {
      return (Image != null);
    }

    public virtual bool ShouldSerializeVisible()
    {
      return visibleStored;
    }

    public virtual void ResetVisible()
    {
      visibleStored = false;
      Owner.CellImageBoxChanged(this);
    }

    public virtual void LayoutImageAndText(Rectangle clienRect, TextLayoutParams textLayout, out Rectangle imageRect, out Rectangle textRect)
    {
      imageRect = Rectangle.Empty;
      textRect = clienRect;

      if (!Visible)
        return;

      Size imageAreaSize = GetFittedSizeFor(clienRect.Size);
      Size checkTextSize;
      Size textSize;

      switch (TextImageRelation)
      {
        case TextImageRelation.Overlay:
          textRect = clienRect;
          imageRect = clienRect;
          break;

        case TextImageRelation.ImageAboveText:
          switch (VertAlign)
          {
            case VerticalAlignment.Top:
              imageRect = new Rectangle(clienRect.Left, clienRect.Top, clienRect.Width, imageAreaSize.Height);
              textRect = new Rectangle(clienRect.Left, imageRect.Bottom, clienRect.Width, clienRect.Bottom - imageRect.Bottom);
              break;
            case VerticalAlignment.Center:
            case VerticalAlignment.Bottom:
              checkTextSize = new Size(clienRect.Width, clienRect.Height - imageAreaSize.Height);
              textSize = EhLibUtils.MeasureText(EhLibUtils.DisplayGraphicsCash, textLayout.Text, textLayout.Font, checkTextSize, textLayout.WrapMode);

              int totalHeight = textSize.Height + Size.Height;
              Rectangle totalRect = new Rectangle(clienRect.Left, clienRect.Top, clienRect.Width, totalHeight);
              if (VertAlign == VerticalAlignment.Center)
                totalRect = EhLibUtils.RectCenter(totalRect, clienRect);
              else
                totalRect.Y = clienRect.Bottom - totalRect.Height;

              imageRect = new Rectangle(totalRect.Left, totalRect.Top, totalRect.Width, imageAreaSize.Height);
              textRect = new Rectangle(totalRect.Left, imageRect.Bottom, totalRect.Width, totalRect.Bottom - imageRect.Bottom);
              break;
          }
          break;

        case TextImageRelation.TextAboveImage:
          checkTextSize = new Size(clienRect.Width, 0);
          textSize = EhLibUtils.MeasureText(EhLibUtils.DisplayGraphicsCash, textLayout.Text, textLayout.Font, checkTextSize, textLayout.WrapMode);

          switch (VertAlign)
          {
            case VerticalAlignment.Top:
              textRect = new Rectangle(clienRect.Left, clienRect.Top, clienRect.Width, textSize.Height);
              imageRect = new Rectangle(clienRect.Left, clienRect.Top + textSize.Height, clienRect.Width, imageAreaSize.Height);
              break;
            case VerticalAlignment.Center:
            case VerticalAlignment.Bottom:

              int totalHeight = textSize.Height + Size.Height;
              Rectangle totalRect = new Rectangle(clienRect.Left, clienRect.Top, clienRect.Width, totalHeight);
              if (VertAlign == VerticalAlignment.Center)
                totalRect = EhLibUtils.RectCenter(totalRect, clienRect);
              else
                totalRect.Y = clienRect.Bottom - totalRect.Height;

              textRect = new Rectangle(totalRect.Left, totalRect.Top, totalRect.Width, textSize.Height);
              imageRect = new Rectangle(totalRect.Left, totalRect.Top + textSize.Height, totalRect.Width, imageAreaSize.Height);
              break;
          }
          break;

        case TextImageRelation.ImageBeforeText:
          switch (HorzAlign)
          {
            case HorizontalAlignment.Left:
              imageRect = new Rectangle(clienRect.Left, clienRect.Top, imageAreaSize.Width, clienRect.Height);
              textRect = new Rectangle(imageRect.Right, clienRect.Top, clienRect.Right - imageRect.Right, clienRect.Height);
              break;
            case HorizontalAlignment.Center:
            case HorizontalAlignment.Right:
              checkTextSize = new Size(clienRect.Width - imageAreaSize.Width, clienRect.Height);
              textSize = EhLibUtils.MeasureText(EhLibUtils.DisplayGraphicsCash, textLayout.Text, textLayout.Font, checkTextSize, textLayout.WrapMode);

              int totalWidth = textSize.Width + imageAreaSize.Width;
              Rectangle totalRect = new Rectangle(clienRect.Left, clienRect.Top, totalWidth, clienRect.Height);
              if (HorzAlign == HorizontalAlignment.Center)
                totalRect = EhLibUtils.RectCenter(totalRect, clienRect);
              else
                totalRect.X = clienRect.Right - totalRect.Width;

              if (totalRect.Width < clienRect.Width)
              {
                imageRect = new Rectangle(totalRect.Left, totalRect.Top, imageAreaSize.Width, totalRect.Height);
                textRect = new Rectangle(imageRect.Right, totalRect.Top, totalRect.Right - imageRect.Right, totalRect.Height);
              }
              else
              {
                imageRect = new Rectangle(clienRect.Left, clienRect.Top, imageAreaSize.Width, clienRect.Height);
                textRect = new Rectangle(imageRect.Right, clienRect.Top, clienRect.Right - imageRect.Right, clienRect.Height);
              }
              break;
          }
          if (imageRect.Right > clienRect.Right)
          {
            imageRect.Width = clienRect.Width;
            textRect.Width = 0;
          }
          break;

        case TextImageRelation.TextBeforeImage:
          checkTextSize = new Size(clienRect.Width - imageAreaSize.Width, clienRect.Height);
          textSize = EhLibUtils.MeasureText(EhLibUtils.DisplayGraphicsCash, textLayout.Text, textLayout.Font, checkTextSize, textLayout.WrapMode);

          switch (textLayout.HorzAlign)
          {
            case HorizontalAlignment.Left:
              textRect = new Rectangle(clienRect.Left, clienRect.Top, textSize.Width, clienRect.Height);
              imageRect = new Rectangle(clienRect.Left + textSize.Width, clienRect.Top, clienRect.Width - textSize.Width, clienRect.Height);
              break;
            case HorizontalAlignment.Center:
            case HorizontalAlignment.Right:

              int totalWidth = textSize.Width + imageAreaSize.Width;
              Rectangle totalRect = new Rectangle(clienRect.Left, clienRect.Top, totalWidth, clienRect.Height);
              if (textLayout.HorzAlign == HorizontalAlignment.Center)
                totalRect = EhLibUtils.RectCenter(totalRect, clienRect);
              else
                totalRect.X = clienRect.Right - totalRect.Width;

              if (totalRect.Width < clienRect.Width)
              {
                textRect = new Rectangle(totalRect.Left, totalRect.Top, textSize.Width, totalRect.Height);
                imageRect = new Rectangle(totalRect.Left + textSize.Width, totalRect.Top, imageAreaSize.Width, totalRect.Height);
              }
              else
              {
                textRect = new Rectangle(clienRect.Left, clienRect.Top, textSize.Width, clienRect.Height);
                imageRect = new Rectangle(clienRect.Left + textSize.Width, clienRect.Top, clienRect.Width - textSize.Width, clienRect.Height);
              }
              break;
          }
          if (textRect.Right > clienRect.Right)
          {
            textRect.Width = clienRect.Width;
            imageRect.Width = 0;
          }
          if (imageRect.Width < 0)
          {
            imageRect.Width = 0;
          }
          break;

        default:
          textRect = clienRect;
          imageRect = clienRect;
          break;
      }
    }

    private Size GetFittedSizeFor(Size sizeFor)
    {
      Size sizeForImage = new Size(sizeFor.Width - Padding.Left - Padding.Right, sizeFor.Height - Padding.Top - Padding.Bottom);
      Size imageSize = new Size(Size.Width - Padding.Left - Padding.Right, Size.Height - Padding.Top - Padding.Bottom);
      Size result = EhLibUtils.GetFittedImageSize(imageSize, sizeForImage);
      return new Size(result.Width + Padding.Left + Padding.Right, result.Height + Padding.Top + Padding.Bottom);
    }

    protected internal virtual Rectangle CalcTextRect(Rectangle clienRect, TextLayoutParams textLayout)
    {
      switch (TextImageRelation)
      {
        case TextImageRelation.Overlay:
        case TextImageRelation.ImageAboveText:
        case TextImageRelation.TextAboveImage:
          return clienRect;

        case TextImageRelation.ImageBeforeText:
        case TextImageRelation.TextBeforeImage:
          Rectangle result = clienRect;
          result.Width = result.Width - Size.Width;
          if (result.Width < 0)
            result.Width = 0;

          return result;

        default:
          return clienRect;
      }
    }

    protected internal virtual void ProcessPaint(CellImageBoxPaintEventArgs e)
    {
      HandlePaintEvent(e);
      if (!e.Handled)
        OnPaint(e);
    }

    protected void HandlePaintEvent(CellImageBoxPaintEventArgs e)
    {
      var eh = this.Events[EventKeyPaint] as EventHandler<CellImageBoxPaintEventArgs>;

      if (eh != null)
        eh(this, e);
    }

    protected internal void OnPaint(CellImageBoxPaintEventArgs e)
    {
      if (Image == null) return;

      Rectangle clientRect = EhLibUtils.TrimPadding(e.PaintRect, Padding);
      Size imageSize = Image.Size;

      EhLibUtils.DrawFitImage(e.Graphics, Image, clientRect, HorzAlign, VertAlign);
    }
    #endregion
  }

  public class CellImageBoxPaintEventArgs : HandledEventArgs
  {
    private Graphics graphics;
    private Rectangle paintRect;
    private EventArgs baseEventArgs;
    private CellImageBox cellImageBox;

    public CellImageBoxPaintEventArgs(CellImageBox cellImageBox, Graphics graphics, Rectangle paintRect, EventArgs baseEventArgs)
    {
      this.cellImageBox = cellImageBox;
      this.graphics = graphics;
      this.paintRect = paintRect;
      this.baseEventArgs = baseEventArgs;
    }

    public void Reset(Graphics graphics, Rectangle paintRect, EventArgs baseEventArgs)
    {
      this.graphics = graphics;
      this.paintRect = paintRect;
      this.baseEventArgs = baseEventArgs;
    }

    public CellImageBox CellImageBox
    {
      get { return this.cellImageBox; }
    }

    public EventArgs BaseEventArgs
    {
      get { return this.baseEventArgs; }
    }

    public Graphics Graphics
    {
      get { return this.graphics; }
    }

    public Rectangle PaintRect
    {
      get { return this.paintRect; }
      set { paintRect = value; }
    }

    public virtual void Paint(CellImageBoxPaintEventArgs e)
    {
      CellImageBox.OnPaint(e);
    }

  }

  public interface ICellImageBoxOwner
  {
    void CellImageBoxChanged(CellImageBox imageBox);
  }

  public class TextLayoutParams
  {
    public string Text { get; set; }
    public Font Font { get; set; }
    public HorizontalAlignment HorzAlign { get; set; }
    public VerticalAlignment VertAlign { get; set; }
    public DataGridTextWrapMode WrapMode { get; set; }
  }

}
