using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Windows.Forms;
using Ck_Objects;

namespace PictureViewer
{
    public class PictureViewer : PictureBox
    {
        PictureViewer cMyRef = null;
        public List<classPictureViewer_Region> _lstRegions = new List<classPictureViewer_Region>();
        public List<classPictureViewer_Region> lstRegions
        {
            get { return _lstRegions; }
            set
            {
                _lstRegions = value;
                defineRegions();
                Display();
            }
        }

        classSweepAndPrune cSweepPrune = new classSweepAndPrune();
        public classPictureViewer_Region _cPV_RegionUnderMouse = null;
        public classPictureViewer_Region cPV_RegionUnderMouse
        {
            get { return _cPV_RegionUnderMouse; }
        }

        public PictureViewer()
        {
            Init(null);
        }

        public PictureViewer(Bitmap bmp)
        {
            Init(bmp);
        }

        void Init(Bitmap bmp)
        {
            cMyRef = this;
            SizeMode = PictureBoxSizeMode.Normal;

            MouseWheel += PictureViewer_MouseWheel;
            MouseMove += PictureViewer_MouseMove;
            SizeChanged += PictureViewer_SizeChanged;

            classPictureViewer_Region cRegion_Default = new classPictureViewer_Region(ref cMyRef, "Default Region");
            cRegion_Default.bmpSource = bmp;
            lstRegions.Add(cRegion_Default);

            Display();
        }
        Point ptCursorShift = new Point();

        private void PictureViewer_MouseMove(object sender, MouseEventArgs e)
        {
            Point ptMouse = new Point(e.X, e.Y);
            classSweepAndPrune.classElement cSP_Ele = cSweepPrune.Search(ptMouse);
            
            if (cSP_Ele != null)
            {
                _cPV_RegionUnderMouse = (classPictureViewer_Region)(cSP_Ele.obj);
                ptCursorShift = new Point(Cursor.Position.X - ptMouse.X, Cursor.Position.Y - ptMouse.Y);

                Point ptRelCenterScreen = new Point(cPV_RegionUnderMouse.recArea.Left + cPV_RegionUnderMouse.ptScreenCenter.X - e.X,
                                           cPV_RegionUnderMouse.recArea.Top + cPV_RegionUnderMouse.ptScreenCenter.Y - e.Y);

                cPV_RegionUnderMouse.ptMousePosition = new Point(cPV_RegionUnderMouse.ptFocus.X - (int)(ptRelCenterScreen.X * cPV_RegionUnderMouse.Zoom),
                                                                 cPV_RegionUnderMouse.ptFocus.Y - (int)(ptRelCenterScreen.Y * cPV_RegionUnderMouse.Zoom));
            }
        }
                     
        bool bolSizeChanged = true;
        private void PictureViewer_SizeChanged(object sender, EventArgs e)
        {
            defineRegions();
        }

        void defineRegions()
        {
            bolSizeChanged = true;
            cSweepPrune.lstElements.Clear();
            for (int intRegionCounter = 0; intRegionCounter < lstRegions.Count; intRegionCounter++)
            {
                classPictureViewer_Region cPV_Region = lstRegions[intRegionCounter];
                cPV_Region.ParentSizeChanged();


                classSweepAndPrune.classElement cSP_Ele = new classSweepAndPrune.classElement();
                cSP_Ele.obj = (object)cPV_Region;
                cSP_Ele.rec = cPV_Region.recArea;
                cSweepPrune.Add(ref cSP_Ele);
            }
            Display();
        }

        bool bolIgnoreMouseWheel = false;
        public bool IgnoreMouseWheel
        {
            get { return bolIgnoreMouseWheel; }
            set { bolIgnoreMouseWheel = value; }
        }

        private void PictureViewer_MouseWheel(object sender, MouseEventArgs e)
        {
            if (cPV_RegionUnderMouse == null || IgnoreMouseWheel)
                    return;
            Point ptRelCenterScreen = new Point(cPV_RegionUnderMouse.recArea.Left + cPV_RegionUnderMouse.ptScreenCenter.X - e.X, 
                                                cPV_RegionUnderMouse.recArea.Top + cPV_RegionUnderMouse.ptScreenCenter.Y - e.Y);

            cPV_RegionUnderMouse.ptFocus = new Point(cPV_RegionUnderMouse.ptFocus.X - (int)(ptRelCenterScreen.X * cPV_RegionUnderMouse.Zoom),
                                                         cPV_RegionUnderMouse.ptFocus.Y - (int)(ptRelCenterScreen.Y * cPV_RegionUnderMouse.Zoom));

            if (cPV_RegionUnderMouse.ptFocus.X < 0)
                cPV_RegionUnderMouse.ptFocus = new Point(0, cPV_RegionUnderMouse.ptFocus.Y);
            else if (cPV_RegionUnderMouse.ptFocus.X > cPV_RegionUnderMouse.bmpSource.Width)
                cPV_RegionUnderMouse.ptFocus =new Point( cPV_RegionUnderMouse.bmpSource.Width, cPV_RegionUnderMouse.ptFocus.Y);
            if (cPV_RegionUnderMouse.ptFocus.Y < 0)
                cPV_RegionUnderMouse.ptFocus = new Point(cPV_RegionUnderMouse.ptFocus.X, 0);
            else if (cPV_RegionUnderMouse.ptFocus.Y > cPV_RegionUnderMouse.bmpSource.Height)
                cPV_RegionUnderMouse.ptFocus = new Point(cPV_RegionUnderMouse.ptFocus.X, cPV_RegionUnderMouse.bmpSource.Height);

            switch (cPV_RegionUnderMouse.eMouseWheelEffects)
            {
                case classPictureViewer_Region.enuMouseWheelEffects.PicView:
                    {
                        if (e.Delta < 0)
                            cPV_RegionUnderMouse.Zoom *= 1.1;
                        else
                            cPV_RegionUnderMouse.Zoom /= 1.1;

                        double dblMinZoom = 0.05;
                        double dblMaxZoom = 100.0;

                        if (cPV_RegionUnderMouse.Zoom < dblMinZoom)
                            cPV_RegionUnderMouse.Zoom = dblMinZoom;
                        if (cPV_RegionUnderMouse.Zoom > dblMaxZoom)
                            cPV_RegionUnderMouse.Zoom = dblMaxZoom;

                    }
                    break;

                case classPictureViewer_Region.enuMouseWheelEffects.Scroll_Vertical:
                    {
                        int intHalfAreaHeight = cPV_RegionUnderMouse.recArea.Height / 2;
                        cPV_RegionUnderMouse.ptFocus= new Point(cPV_RegionUnderMouse.ptFocus.X, cPV_RegionUnderMouse.ptFocus.Y -(int)Math.Ceiling((double)( (e.Delta / Math.Abs(e.Delta) * cPV_RegionUnderMouse.recArea.Height / 10))));
                        int intTestValue_Least = (int)Math.Ceiling((double)intHalfAreaHeight * cPV_RegionUnderMouse.Zoom);
                        int intTestValue_Most = (int)Math.Floor(cPV_RegionUnderMouse.bmpSource.Height - (double)intHalfAreaHeight * cPV_RegionUnderMouse.Zoom);
                        if (cPV_RegionUnderMouse.ptFocus.Y <=intTestValue_Least)
                            cPV_RegionUnderMouse.ptFocus =new Point(cPV_RegionUnderMouse.ptFocus.X,intTestValue_Least);
                        else if (cPV_RegionUnderMouse.ptFocus.Y >= intTestValue_Most)
                            cPV_RegionUnderMouse.ptFocus = new Point(cPV_RegionUnderMouse.ptFocus.X, intTestValue_Most);

                        cPV_RegionUnderMouse.ptFocus =new Point( (int)Math.Ceiling((double)(cPV_RegionUnderMouse.recVisible.Left
                                                                                        + cPV_RegionUnderMouse.recVisible.Width / 2)),
                                                                     cPV_RegionUnderMouse.ptFocus.Y);
                    /* formJigSawPuzzler.instance.Text = "ptFocus(" + cPV_RegionUnderMouse.ptFocus.X.ToString() + ", " + cPV_RegionUnderMouse.ptFocus.Y.ToString() + ")"
                                                            + " TestValue_Least : " + intTestValue_Least.ToString()
                                                            + " testValue_Most : " + intTestValue_Most.ToString();*/

                    /* formJigSawPuzzler.instance.Text
                               = "RecVisible : Left("
                               + cPV_RegionUnderMouse.recVisible.Left.ToString()
                                + ") Top(" + cPV_RegionUnderMouse.recVisible.Top.ToString()
                                + ") Width(" + cPV_RegionUnderMouse.recVisible.Width.ToString()
                                + ") Height(" + cPV_RegionUnderMouse.recVisible.Height.ToString() + ")";*/
                        cPV_RegionUnderMouse.Refreshed = false;
                    }
                    break;

                case classPictureViewer_Region.enuMouseWheelEffects.Scroll_Horizontal:
                    {
                        int intHalfAreaWidth = cPV_RegionUnderMouse.recArea.Width / 2;
                        cPV_RegionUnderMouse.ptFocus = new Point(cPV_RegionUnderMouse.ptFocus.X -(int)Math.Ceiling((double)((e.Delta / Math.Abs(e.Delta) * cPV_RegionUnderMouse.recArea.Width / 10))),
                                                                     cPV_RegionUnderMouse.ptFocus.Y);

                        int intTestValue_Least = (int)Math.Ceiling((double)intHalfAreaWidth * cPV_RegionUnderMouse.Zoom);
                        int intTestValue_Most = (int)Math.Floor(cPV_RegionUnderMouse.bmpSource.Width - (double)intHalfAreaWidth * cPV_RegionUnderMouse.Zoom);

                        if (cPV_RegionUnderMouse.ptFocus.X < intTestValue_Least)
                            cPV_RegionUnderMouse.ptFocus = new Point( intTestValue_Least, cPV_RegionUnderMouse.ptFocus.Y);
                        else if (cPV_RegionUnderMouse.ptFocus.X > intTestValue_Most)
                            cPV_RegionUnderMouse.ptFocus =new Point( intTestValue_Most,
                                                                        cPV_RegionUnderMouse.ptFocus.Y);

                        /*formJigSawPuzzler.instance.Text
                               = "RecVisible : Left("
                               + cPV_RegionUnderMouse.recVisible.Left.ToString()
                                + ") Top(" + cPV_RegionUnderMouse.recVisible.Top.ToString()
                                + ") Width(" + cPV_RegionUnderMouse.recVisible.Width.ToString()
                                + ") Height(" + cPV_RegionUnderMouse.recVisible.Height.ToString() + ")";*/
                        cPV_RegionUnderMouse.Refreshed = false;
                    }
                    break;
            }

            Cursor.Position = new Point(cPV_RegionUnderMouse.recArea.Left + cPV_RegionUnderMouse.ptScreenCenter.X + ptCursorShift.X,
                                        cPV_RegionUnderMouse.recArea.Top + cPV_RegionUnderMouse.ptScreenCenter.Y + ptCursorShift.Y);
            Display();
        }

        public void Display()
        {
            if (Width < 10 || Height < 10) return;
            Bitmap bmpOutput = (Image == null || bolSizeChanged )
                                     ? new Bitmap(Width, Height)
                                     : new Bitmap(Image);
            using (Graphics g = Graphics.FromImage(bmpOutput))
            {
                for (int intRegionCounter = 0; intRegionCounter < lstRegions.Count; intRegionCounter++)
                {
                    classPictureViewer_Region cPV_Region = lstRegions[intRegionCounter];
                    if (!cPV_Region.Refreshed || bolSizeChanged)
                    { 
                        if (cPV_Region.bmpSource != null)
                        {
                            g.FillRectangle(new SolidBrush( BackColor), cPV_Region.recArea);

                            switch (cPV_Region.eMouseWheelEffects)
                            {
                                case classPictureViewer_Region.enuMouseWheelEffects.Scroll_Vertical:
                                    {

                                        cPV_Region.recVisible = new Rectangle(cPV_Region.recVisible.Left,
                                                                              cPV_Region.ptFocus.Y - (int)Math.Ceiling(cPV_Region.ptScreenCenter.Y * cPV_Region.Zoom),
                                                                              (int)(cPV_Region.recArea.Width * cPV_Region.Zoom),
                                                                              (int)(cPV_Region.recArea.Height * cPV_Region.Zoom));
                                    }
                                    break;

                                case classPictureViewer_Region.enuMouseWheelEffects.Scroll_Horizontal:
                                    {

                                        cPV_Region.recVisible = new Rectangle(cPV_Region.ptFocus.X - (int)Math.Ceiling(cPV_Region.ptScreenCenter.X * cPV_Region.Zoom),
                                                                              cPV_Region.recVisible.Top,
                                                                              (int)(cPV_Region.recArea.Width * cPV_Region.Zoom),
                                                                              (int)(cPV_Region.recArea.Height * cPV_Region.Zoom));
                                    }
                                    break;

                                case classPictureViewer_Region.enuMouseWheelEffects.PicView:
                                    {
                                        cPV_Region.recVisible = new Rectangle(cPV_Region.ptFocus.X - (int)Math.Ceiling(cPV_Region.ptScreenCenter.X * cPV_Region.Zoom),
                                                                              cPV_Region.ptFocus.Y - (int)Math.Ceiling(cPV_Region.ptScreenCenter.Y * cPV_Region.Zoom),
                                                                              (int)(cPV_Region.recArea.Width * cPV_Region.Zoom),
                                                                              (int)(cPV_Region.recArea.Height * cPV_Region.Zoom));
                                    }
                                    break;
                            }

                            Rectangle recDest = new Rectangle(cPV_Region.recArea.Left, cPV_Region.recArea.Top, cPV_Region.recArea.Width, cPV_Region.recArea.Height);
                            g.DrawImage(cPV_Region.bmpSource, recDest, cPV_Region.recVisible, GraphicsUnit.Pixel);
                            
                        }
                        cPV_Region.Refreshed = true;
                    }
                }
            }
            bolSizeChanged = false;
            Image = bmpOutput;

            Refresh();
        }
    }


    public class classPictureViewer_Region
    {
        public enum enuMouseWheelEffects { PicView, Scroll_Horizontal, Scroll_Vertical, none, _num};
        enuMouseWheelEffects _eMouseWheelEffects = enuMouseWheelEffects.PicView;
        public enuMouseWheelEffects eMouseWheelEffects
        {
            get { return _eMouseWheelEffects; }
            set { _eMouseWheelEffects = value; }
        }

        PictureViewer picViewer = null;

        string strName = "";
        public string Name
        {
            get { return strName; }
            set { strName = value; }
        }

        public classPictureViewer_Region(ref PictureViewer cPicViewer, string _strName)
        {
            picViewer = cPicViewer;
            strName = _strName;
            _recArea = new Rectangle(0, 0, picViewer.Width, picViewer.Height);
        }
        bool bolRefreshed = false;
        public bool Refreshed
        {
            get { return bolRefreshed; }
            set { bolRefreshed = value; }
        }

        Bitmap _bmpSource = null;
        public Bitmap bmpSource
        {
            get { return _bmpSource; }
            set
            {
                bool bolSameSize = _bmpSource != null 
                                    && value != null 
                                    && _bmpSource.Width == value.Width && _bmpSource.Height == value.Height;
                _bmpSource = value;
                if (_bmpSource != null)
                {
                    if (!bolSameSize)
                    {
                        ptImageCenter = new Point(_bmpSource.Width / 2, _bmpSource.Height / 2);
                        switch (eMouseWheelEffects)
                        {
                            case enuMouseWheelEffects.PicView:
                                {
                                    ptFocus = ptImageCenter;
                                }
                                break;

                            case enuMouseWheelEffects.Scroll_Vertical:
                                {
                                ptFocus = new Point(recArea.Width/2, ptFocus.Y);
                                }
                                break;

                            case enuMouseWheelEffects.Scroll_Horizontal:
                                {
                                    ptFocus = new Point(ptFocus.X, recArea.Height / 2);
                                }
                                break;
                        }
                        
                        //dblZoom = 1.0;
                        double dblSmallestEdge = bmpSource.Width < bmpSource.Height
                                                                 ? bmpSource.Width
                                                                 : bmpSource.Height;
                        dblZoom_Max = dblSmallestEdge / 50;
                    }
                    //_bmpSource.Save(@"c:\debug\bmpTemp.bmp");
                    bolRefreshed = false;
                    picViewer.Display();
                }
            }
        }

        Point _ptFocus = new Point();
        public Point ptFocus
        {
            get { return _ptFocus; }
            set
            {
                _ptFocus = value;
            }
        }

        Point _ptMousePosition = new Point();
        public Point ptMousePosition
        {
            get { return _ptMousePosition; }
            set { _ptMousePosition = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        public Point ptImageCenter = new Point();

        public Point ptScreenCenter = new Point();
        void ScreenAspectRatio_Set()
        {
            ptScreenCenter = new Point(recArea.Width / 2, recArea.Height / 2);
            bolRefreshed = false;
        }

        double dblZoom_Min = 0.1;
        double dblZoom_Max = 100;

        /// <summary>
        /// determines the zoom factor of the image's output
        /// </summary>
        double dblZoom = 1.0;
        public double Zoom
        { 
        get { return dblZoom; }
        set 
            {
                dblZoom = value;
                if (dblZoom > dblZoom_Max)
                    dblZoom = dblZoom_Max ;
                bolRefreshed = false;
            }
        }

        /// <summary>
        /// defines which part of the source bitmap is visible on the screen
        /// </summary>
        public Rectangle recVisible = new Rectangle();

        /// <summary>
        /// defines the space on screen in percentages to vary with change in size of parent PictureBox
        /// </summary>
        RectangleF _recScreenProportion = new RectangleF(0, 0, 100, 100);
        public RectangleF recScreenProportion
        {
            get { return _recScreenProportion; }
            set { _recScreenProportion = value; }
        }

        Rectangle _recArea = new Rectangle(0, 0, 0,0);
        public Rectangle recArea
        { 
            get { return _recArea; }
        
        }
        public void ParentSizeChanged()
        {
            _recArea = new Rectangle((int)(recScreenProportion.Left / 100.0 * picViewer.Width),
                                    (int)(recScreenProportion.Top / 100.0 * picViewer.Height),
                                    (int)(recScreenProportion.Width / 100.0 * picViewer.Width),
                                    (int)(recScreenProportion.Height / 100.0 * picViewer.Height));
            ptScreenCenter = new Point(_recArea.Left + _recArea.Width / 2, _recArea.Top + _recArea.Height / 2);

            
            switch (eMouseWheelEffects)
            {
                case enuMouseWheelEffects.Scroll_Vertical:
                    {
                        Zoom = (double)bmpSource.Width / (double)recArea.Width;
                    }
                    break;

                case enuMouseWheelEffects.Scroll_Horizontal:
                    {
                        Zoom = (double)bmpSource.Height / (double)recArea.Height;
                    }
                    break;
            }

            ScreenAspectRatio_Set();
        }
    }

}
