using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading;
//using Sprite;

namespace PicTray
{
    #region "enumerated types"
    public enum enuRotationMode { toSelected, Carousel }
    public enum enuSelectionMode { click, mouseOver, none }
    public enum enuSizeControl { width, height, both, none };
    public enum enuFrontUserSet { none, Tray_Mouse_click, Tray_Mouse_move };
    public enum enuAutoSize { BottomRight, Right, TopRight, Top, TopLeft, Left, BottomLeft, Bottom, Center, none };
    public enum enuMirror { none, horizontal, vertical, both }
    #endregion

    public class classPicTray_V2 :PictureBox
    {
        #region "variables & properties"
        public rotatingTray_PicBox[] Tray;
        rotatingTray_PicBox[] Tray_Reordered;
        public PicTrayOptions Options;
        public enuAutoSize AutoSize = enuAutoSize.none;
        short shoDirSign = 1;
        bool _MakeImagesTransparent;
		bool _bolRoundImages = false;

		public bool RoundImages
		{
			get { return _bolRoundImages; }
			set { _bolRoundImages = value; }
		}

        public Color BackGroundColor
        {
            get { return BackColor; }
            set { BackColor = BackGroundColor; }
        }

        /// <summary>
        /// get or set option to make the white portion of rotating images transparent
        /// </summary>
        public bool MakeImagesTransparent
        {
            get { return _MakeImagesTransparent; }
            set { _MakeImagesTransparent = value; }
        }

        enuSelectionMode selectionMode = enuSelectionMode.none;
        /// <summary>
        /// get or set the user selection mode.
        /// </summary>
        public enuSelectionMode SelectionMode
        {
            get { return selectionMode; }
            set { selectionMode = value; }
        }

        enuMirror mirror = enuMirror.none;
        /// <summary>
        /// get or set the tray's mirror type
        /// </summary>
        public enuMirror Mirror
        {
            get { return mirror; }
            set { mirror = value; }
        }

        enuFrontUserSet frontUserSet = enuFrontUserSet.none;
        /// <summary>
        /// get or set Front angle method used by user
        /// </summary>
        public enuFrontUserSet FrontUserSet
        {
            get { return frontUserSet; }
            set { frontUserSet = value; }
        }

        int intSelectedImageIndex;
        /// <summary>
        /// get information about the image currently selected
        /// </summary>
        public rotatingTray_PicBox SelectedImage
        {
            get { return Tray[intSelectedImageIndex]; }
            set
            {
                intSelectedImageIndex = value.Index;
            }
        }

        double dblFrontAngle = 0;
        /// <summary>
        /// get or set the direction in which the main selected image is displayed
        /// set to a value between [0, 2pi)
        /// </summary>
        public double FrontAngle
        {
            get { return dblFrontAngle; }
            set
            {
                dblFrontAngle = value;
                while (dblFrontAngle < 0)
                    dblFrontAngle += Math.PI * 2.0;
                while (dblFrontAngle >= 2.0 * Math.PI)
                    dblFrontAngle -= Math.PI * 2.0;
            }
        }

        double dblAngle = 0;
        /// <summary>
        /// get or set direction in which the tray is rotated
        /// </summary>
        public double Angle
        {
            get { return dblAngle; }
            set { dblAngle = value; }
        }

        double dblRotationStepSize;
        /// <summary>
        /// get or set speed at which tray rotates can be positive or negative
        /// </summary>
        public double RotationStepSize
        {
            get { return dblRotationStepSize; }
            set { dblRotationStepSize = value; }
        }

        Point ptTrayCenter;
        /// <summary>
        /// get or set central point around which tray rotates
        /// </summary>
        public Point TrayCenter
        {
            get { return ptTrayCenter; }
            set { ptTrayCenter = value; }
        }

        double dblEllipse_Width;
        /// <summary>
        /// get or set the width of the ellipse about which images rotate
        /// </summary>
        public int EllipseRadius_Width
        {
            get { return (int)dblEllipse_Width; }
            set
            {
                if (value > 0)
                    dblEllipse_Width = (double)value;
            }
        }

        double dblEllipse_Height;
        /// <summary>
        /// get or set the Height of the ellipse about which images rotate
        /// </summary>
        public int EllipseRadius_Height
        {
            get { return (int)dblEllipse_Height; }
            set
            {
                if (value > 0)
                    dblEllipse_Height = (double)value;
            }
        }

        double dblEllipseAngle = 0;
        /// <summary>
        /// angle of the ellipse which describes the path of the images, angle = [0, pi/2)
        /// </summary>
        public double EllipseAngle
        {
            get { return dblEllipseAngle; }
            set
            {
                dblEllipseAngle = value;
                while (dblEllipseAngle >= Math.PI * 2)
                    dblEllipseAngle -= Math.PI * 2.0;
                while (dblEllipseAngle < 0)
                    dblEllipseAngle += Math.PI * 2.0;
            }
        }

        /// <summary>
        /// max & min sizes of image at front and back when controlled by 'width'
        /// </summary>
        public classMinMax ImageWidth_MinMax = new classMinMax(10, 200);

        /// <summary>
        /// max & min sizes of image at front and back when controlled by 'height'
        /// </summary>
        public classMinMax ImageHeight_MinMax = new classMinMax(10, 200);

        enuSizeControl sizeControl;
        /// <summary>
        /// get or set limit to max/min image size : width, height, both or none
        /// </summary>
        public enuSizeControl SizeControl
        {
            get {return sizeControl;}
            set {sizeControl = value;}
        }
        
        bool bolPopUpClickPic = false;
        /// <summary>
        /// get or set pop-up form of clicked image
        /// </summary>
        public bool PopUpClickPic
        {
            get { return bolPopUpClickPic; }
            set { bolPopUpClickPic = value; }
        }

        bool bolShowCaptionLabel = false;
        /// <summary>
        /// get or set visible caption for mouse-over image
        /// </summary>
        public bool ShowCaptionLabel
        {
            get { return bolShowCaptionLabel; }
            set 
            {
                lblCaption.Visible = bolShowCaptionLabel = value;
            }
        }
     
        enuRotationMode rotationmode = enuRotationMode.Carousel;
        /// <summary>
        /// set mode which tray rotates : carousel or toSelected
        /// </summary>
        public enuRotationMode RotationMode
        {
            get { return rotationmode; }
            set 
            {
                rotationmode = value;
                tmrRotate.Enabled = true;
            }
        }

        #endregion

        #region "objects"
        public System.Windows.Forms.Timer tmrRotate = new System.Windows.Forms.Timer();
        public System.Windows.Forms.Timer tmrResize = new System.Windows.Forms.Timer();
        public Label lblCaption = new Label();        
        #endregion

        public classPicTray_V2()
        {
            Tray = new rotatingTray_PicBox[0];

            Controls.Add(lblCaption);
            lblCaption.AutoSize = true;

            Options = new PicTrayOptions(this);
            #region "init tray"
            {
                Options.BackColor = Color.FromArgb(255, 0, 0, 0);
                Options.Mirror = PicTray.enuMirror.none;
                Options.PopUpClickPic = false;
                Options.SelectionMode = PicTray.enuSelectionMode.click;
                Options.TrayCenter = new Point(310, 270);
                Options.Width = 720;
                Options.Height = 500;
                Options.Rotation.StepSize = 0.0109955742875643;
                Options.Rotation.Interval = 250;
                Options.Rotation.Mode = PicTray.enuRotationMode.Carousel;
                Options.Ellipse.Angle = 0;
                Options.Ellipse.Radius_Height = 131;
                Options.Ellipse.Radius_Width = 257;
                Options.Front.Angle = 0;
                Options.Front.UserSet = PicTray.enuFrontUserSet.none;
                Options.ImageSize.Height_MinMax = new PicTray.classMinMax(50, 250);
                Options.ImageSize.Width_MinMax = new PicTray.classMinMax(75, 300);
                Options.Caption.Show = true;
                Options.Caption.Font = new Font("Microsoft Sans Serif", (float)8.25, FontStyle.Regular);
                Options.Caption.ForeColor = Color.FromArgb(255, 255, 255, 0);
                Options.Caption.BackColor = Color.FromArgb(255, 0, 0, 0);
            }
            #endregion

            tmrRotate.Tick += new EventHandler(tmrRotate_Tick);
            tmrRotate.Enabled = true;

            tmrResize.Interval = 300;
            tmrResize.Tick += new EventHandler(tmrResize_Tick);

            MouseMove += new MouseEventHandler(classPicTray_V2_MouseMove);
            MouseClick += new MouseEventHandler(classPicTray_V2_MouseClick);

            SizeChanged += new EventHandler(classPicTray_V2_SizeChanged);
        }

        void tmrResize_Tick(object sender, EventArgs e)
        {
            tmrResize.Enabled = false;
            if (AutoSize != enuAutoSize.none)
                setAutoResize();
        }

        void classPicTray_V2_SizeChanged(object sender, EventArgs e)
        {
            tmrResize.Enabled = false;
            tmrResize.Enabled = true;            
        }

        public void setAutoResize()
        {
            int intShiftCenterX = 0;
            double dblEllipseWidthFactor = .4;
            switch (AutoSize)
            {
                case enuAutoSize.TopLeft:
                case enuAutoSize.TopRight:
                case enuAutoSize.BottomLeft:
                case enuAutoSize.BottomRight:
                    Options.Rotation.disable();
                    Options.PopUpClickPic = false;

                    Options.Ellipse.Radius_Height = (int)(Height * .2);
                    Options.Ellipse.Radius_Width = (int)(Width * .4);
                    Options.Ellipse.Angle = -arcTan(Width, Height * .9);

                    // move right when ellipse angle <0.6

                    switch (AutoSize)
                    {
                        case enuAutoSize.BottomRight:
                            mirror = enuMirror.vertical;
                            break;

                        case enuAutoSize.BottomLeft:
                            mirror = enuMirror.both;
                            break;

                        case enuAutoSize.TopLeft:
                            mirror = enuMirror.horizontal;
                            break;

                        case enuAutoSize.TopRight:
                            mirror = enuMirror.none;
                            break;
                    }



                    if (Options.Ellipse.Angle < 5.6)
                    {
                        double dblMax = (Width * .4);
                        if (Options.Ellipse.Angle < 5.2)
                            intShiftCenterX = (int)dblMax;
                        else
                            intShiftCenterX = (int)(dblMax * (5.6 - Options.Ellipse.Angle));
                    }


                    Options.Front.UserSet = PicTray.enuFrontUserSet.none;
                    Options.ImageSize.Height_MinMax = new PicTray.classMinMax(10, (int)(Height * .5));

                    Options.Front.Angle = Options.Ellipse.Angle;

                    Options.TrayCenter = new Point((int)(Width * .5) + intShiftCenterX, (int)(Height * .55));
                    Options.Rotation.enable();
                    break;

                case enuAutoSize.Bottom: // done
                case enuAutoSize.Top: // done
                    if (AutoSize == enuAutoSize.Bottom)
                        mirror = enuMirror.vertical;
                    else
                        mirror = enuMirror.none;

                    Angle = 0;
                    Options.ImageSize.Height_MinMax = new classMinMax(25, (int)(Height * .7));

                    Options.Ellipse.Angle = 0;
                    Options.Ellipse.Radius_Height = (int)(Height * .25);
                    Options.Ellipse.Radius_Width = (int)(Width * .4);

                    Options.TrayCenter = new Point((int)(Width * .5), (int)(Height * .65));
                    Options.Front.Angle = 1.5 * Math.PI;

                    Options.Rotation.enable();
                    break;

                case enuAutoSize.Right:
                case enuAutoSize.Left:
                    if (AutoSize == enuAutoSize.Right)
                        mirror = enuMirror.horizontal;
                    else
                        mirror = enuMirror.none;

                    Angle = 0;

                    Options.ImageSize.Height_MinMax = new classMinMax(25, (int)(Height * .7));

                    Options.Ellipse.Angle = 0;
                    Options.Ellipse.Radius_Height = (int)(Height * .25);
                    Options.Ellipse.Radius_Width = (int)(Width * .4);

                    Options.TrayCenter = new Point((int)(Width * .57), (int)(Height * .5));
                    Options.Front.Angle = Math.PI;

                    Options.Rotation.enable();
                    break;
            }
        }

        /// <summary>
        /// tests if the index value passed is within the Tray's array size
        /// </summary>
        /// <param name="index">index value to be tested</param>
        /// <returns>returns true if the index is a valid one</returns>
        bool IndexIsValidPic(int index)
        {
            return (Tray != null && index >= 0 && index < Tray.Length);
        }

        public void classPicTray_V2_MouseClick(object sender, MouseEventArgs e)
        {
            if (frontUserSet == enuFrontUserSet.Tray_Mouse_click)
                setFrontByMouseEventArgs(e);
            if (selectionMode == enuSelectionMode.click)
                setSelectedImage(getImageIndexAtPoint(e));

            if (IndexIsValidPic(intSelectedImageIndex))
            {
                if (SelectionMode == enuSelectionMode.click)
                {
                    setSelectedImage(getImageIndexAtPoint(e));
                }

                if (bolPopUpClickPic)
                {
                    if (Tray[intSelectedImageIndex].FlipH)
                    {
                        Tray[intSelectedImageIndex].Image.RotateFlip(RotateFlipType.RotateNoneFlipX);
                        Tray[intSelectedImageIndex].FlipH = false;
                    }

                    if (Tray[intSelectedImageIndex].FlipV)
                    {
                        Tray[intSelectedImageIndex].Image.RotateFlip(RotateFlipType.RotateNoneFlipY);
                        Tray[intSelectedImageIndex].FlipV = false;
                    }

                    cLibPicBoxViewer pic = new cLibPicBoxViewer(Tray[intSelectedImageIndex].Image, Tray[intSelectedImageIndex].caption);
                    pic.resizeToFitImage();             
                }

              
            }
        }

        int getImageIndexAtPoint(MouseEventArgs e)
        {
            if (Tray != null && Tray.Length > 0
                && Tray_Reordered != null && Tray_Reordered.Length > 0)
            {
                Point pt = new Point(e.X, e.Y);
                switch (mirror)
                {
                    case enuMirror.horizontal:
                        pt.X = Image.Width - pt.X;
                        break;

                    case enuMirror.vertical:
                        pt.Y = Image.Height - pt.Y;
                        break;

                    case enuMirror.both:
                        pt.X = Image.Width - pt.X;
                        pt.Y = Image.Height - pt.Y;
                        break;
                }

                for (int intCounter = Tray_Reordered.Length - 1; intCounter >= 0; intCounter--)
                {
                    rotatingTray_PicBox thisTrayEle = Tray_Reordered[intCounter];
					if (RoundImages)
					{						
						Point ptImageCenter = new Point(thisTrayEle.pt.X + thisTrayEle.sz.Width / 2, thisTrayEle.pt.Y + thisTrayEle.sz.Height / 2);
						double dblDistance = classMath.distanceBetweenTwoPoints(ptImageCenter, pt);
						if (dblDistance < thisTrayEle.sz.Width / 2)
							return Tray_Reordered[intCounter].Index;
					}
					else
					{
						if (pt.X > thisTrayEle.pt.X
							&& pt.X <= thisTrayEle.pt.X + thisTrayEle.sz.Width
							&& pt.Y > thisTrayEle.pt.Y
								&& pt.Y < thisTrayEle.pt.Y + thisTrayEle.sz.Height)
						{
							return Tray_Reordered[intCounter].Index;
						}
					}
                }
            }
            return -1;
        }

        void classPicTray_V2_MouseMove(object sender, MouseEventArgs e)
        {
            if (frontUserSet == enuFrontUserSet.Tray_Mouse_move)
                setFrontByMouseEventArgs(e);

            if (SelectionMode == enuSelectionMode.mouseOver)
               setSelectedImage( getImageIndexAtPoint(e));
        }

        public void setSelectedImage(int intIndex)
        {
            if (IndexIsValidPic(intIndex))
            {
                intSelectedImageIndex = intIndex;
                if (RotationMode == enuRotationMode.toSelected)
                {
                    shoDirSign =(short)( Tray[intSelectedImageIndex].shoDirToFront );
                    tmrRotate.Enabled = true;
                }
            }
        }

        void setFrontByMouseEventArgs(MouseEventArgs e)
        {
            double dblMouseAngle = arcTan(e.X - ptTrayCenter.X, e.Y - ptTrayCenter.Y);
            double dblNewFrontAngle = dblMouseAngle - dblEllipseAngle;
            double dblDiff = dblNewFrontAngle - FrontAngle;

            if (Math.Abs(dblDiff) > Math.PI * 2.0 / Tray.Length)
            {
                FrontAngle = dblNewFrontAngle;
                if (!tmrRotate.Enabled)
                    rotateTray(0);
            }
        }

        /// <summary>
        /// resets tray values to initial default settings, clears the tray's memory & stops the rotation timer
        /// </summary>
        public void resetDefaultSettings()
        {
            tmrRotate.Interval = 100;
            tmrRotate.Enabled = false;
            Angle = 0;
            FrontAngle = 0;
            dblEllipseAngle = 0;
            dblEllipse_Height = 200;
            dblEllipse_Width = 200;

            ptTrayCenter = new Point(200, 200);

            ImageWidth_MinMax.Min = 75;
            ImageWidth_MinMax.Max = 300;

            ImageHeight_MinMax.Min = 50;
            ImageHeight_MinMax.Max = 250;

            bolPopUpClickPic = false;

        }

        void tmrRotate_Tick(object sender, EventArgs e)
        {
            tmrRotate.Enabled = false;
            
            if (Tray.Length > 0)
            {
                if (intSelectedImageIndex >= 0 && intSelectedImageIndex < Tray.Length)
                {
                    if (rotationmode == enuRotationMode.toSelected)
                    {
                        double dblDiff = Math.Abs(Tray[intSelectedImageIndex].rotatedAngle - dblFrontAngle);
                        while (dblDiff > 2 * Math.PI)
                            dblDiff -= (2 * Math.PI);
                        while (dblDiff < 0)
                            dblDiff+=(2 * Math.PI);
                        if (dblDiff <=Math.Abs( dblRotationStepSize))
                            return;
                    }
                }
                rotateTray();
                tmrRotate.Enabled = true;
                return;
            }
        }

        double cleanAngleToTwoPi(double dblAngle)
        {
            double dblTwoPi = 2.0 * Math.PI;
            while (dblAngle < 0)
                dblAngle += dblTwoPi;
            while (dblAngle > dblTwoPi)
                dblAngle -= dblTwoPi;
            return dblAngle;
        }

        /// <summary>
        /// starts the tray's rotation
        /// </summary>
        public void enableRotate() { tmrRotate.Enabled = true; }

        /// <summary>
        /// stop the tray's rotation
        /// </summary>
        public void disableRotate() { tmrRotate.Enabled = false; }

        /// <summary>
        /// erase all images from tray's memory
        /// </summary>
        public void clearTray()
        {
            //for (int intCounter = 0; intCounter < Tray.Length; intCounter++)
            //    Tray[intCounter].Visible = false;
            Array.Resize<rotatingTray_PicBox>(ref Tray, 0);
            Bitmap bmp = new Bitmap(Width, Height);
            using (Graphics g = Graphics.FromImage(bmp))
                g.FillRectangle(new SolidBrush(BackColor), new Rectangle(0, 0, Width, Height));
            Image = bmp;
            intSelectedImageIndex = -1;
            Refresh();
        }

        /// <summary>
        /// sets the input radian angle to an equivalent value between [FrontAngle, FrontAngle + 2Pi)
        /// </summary>
        /// <param name="angle">angle to be adjusted passed as a reference</param>
        /// <returns>input angle adjusted to fit between [FrontAngle, FrontAngle + 2pi)</returns>
        double cleanAngle(double angle)
        {
            while (angle < dblFrontAngle)
                angle += (Math.PI * 2.0);
            while (angle >= dblFrontAngle + Math.PI * 2.0)
                angle -= (Math.PI * 2.0);
            return angle;
        }

        /// <summary>
        /// sets the reference angle to a value between [FrontAngle, FrontAngle + 2pi)
        /// </summary>
        /// <param name="angle">reference to a double variable</param>
        void cleanAngle(ref double angle)
        {
            while (angle < dblFrontAngle)
                angle += (Math.PI * 2.0);

            while (angle >= dblFrontAngle + Math.PI * 2.0)
                angle -= (Math.PI * 2.0);
        }

        public void addImageToTray(Image imgNew) { addImageToTray(imgNew, ""); }
        public void addImageToTray(Image imgNew, string strCaption)
        {
            rotatingTray_PicBox thisTrayEle = new rotatingTray_PicBox();
            thisTrayEle.aspectRatioYoverX = ((double)imgNew.Height / (double)imgNew.Width);
            thisTrayEle.caption = strCaption;
            thisTrayEle.Image = imgNew;

            Array.Resize<rotatingTray_PicBox>(ref Tray, Tray.Length + 1);
            Tray[Tray.Length - 1] = thisTrayEle;
            setTrayElements();

        }


        public void deleteImageFromTray() { deleteImageFromTray(intSelectedImageIndex); }
        public void deleteImageFromTray(int intImageToDelete)
        {
            if (Tray.Length > 0
                && intImageToDelete < Tray.Length
                && intImageToDelete >= 0)
            {
                for (int intImageCounter = intImageToDelete; intImageCounter < Tray.Length - 1; intImageCounter++)
                    Tray[intImageCounter] = Tray[intImageCounter + 1];
                Array.Resize<rotatingTray_PicBox>(ref Tray, Tray.Length - 1);
                setTrayElements();
            }
        }

        /// <summary>
        /// uniformly distributes images around a circle by setting their respective angles 2pi/n radians apart starting at zero
        /// where n is the number of images on tray
        /// </summary>
        void setTrayElements()
        {
            int intN = Tray.Length;
            double dblDTheta = 2.0 * Math.PI / (double)intN;
            for (int intCounter = 0; intCounter < intN; intCounter++)
                Tray[intCounter].angleOnTray = intCounter * dblDTheta;

            tmrRotate.Enabled = (Tray.Length >0);
        }

        public void rotateTray() { rotateTray(dblRotationStepSize * shoDirSign ); }
        public void rotateTray(double dblRotationAngle)
        {
            dblAngle += dblRotationAngle;
            cleanAngle(ref dblAngle);
            RefreshTrayImage();
        }

        enuSizeControl getImageSizeControl(Image img)
        {
            switch (SizeControl)
            {
                case enuSizeControl.height:
                case enuSizeControl.width:
                case  enuSizeControl.none:
                    return sizeControl;

                case enuSizeControl.both:
                default:
                    Size szWidthLimit = new Size(ImageWidth_MinMax.Min, ImageHeight_MinMax.Max);
                    Size szHeightLimit = new Size(ImageWidth_MinMax.Max, ImageHeight_MinMax.Min);

                    Size szLargestWidthLimitImage = getLargestImageInLimits(img, szWidthLimit);
                    Size szLargestHeightLimitImage = getLargestImageInLimits(img, szHeightLimit);

                    return (szLargestHeightLimitImage.Width  < szLargestWidthLimitImage.Width) ? enuSizeControl.width : enuSizeControl.height;
            }

        }

        Size getLargestImageInLimits(Image img, Size szLimit)
        {
            double dblAspectRatioImage = (double)img.Height / (double)img.Width;
            double dblAspectRatioLimits = (double)szLimit.Height/ (double)szLimit.Width;

            if (dblAspectRatioLimits > dblAspectRatioImage)
            {// limit by width
                return new Size(szLimit.Width,(int)( img.Height / dblAspectRatioImage));
            }
            else
            {// limit by height
                return new Size((int)(img.Width * dblAspectRatioImage), szLimit.Height);
            }
        }

        public void RefreshTrayImage()
        {
            if (Width * Height < 1)
                return;
            
            Bitmap bmp = new Bitmap(Width, Height);
            Point ptSelectLoc = new Point(0, 0);

            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.FillRectangle(new SolidBrush(BackColor), new Rectangle(0, 0, bmp.Width, bmp.Height));

                Tray_Reordered = new rotatingTray_PicBox[Tray.Length];

                /// set img rotated angles 
                ///   - calculate their angular distance from the front
                ///   - insert each into temp pointer array ready to be reordered back to front
                for (int intCounter = 0; intCounter < Tray.Length; intCounter++)
                {
                    Tray[intCounter].rotatedAngle = cleanAngle(dblAngle + Tray[intCounter].angleOnTray);
                    double dblDistance = Tray[intCounter].rotatedAngle - dblFrontAngle - Math.PI;

                    double dblDiff = Tray[intCounter].rotatedAngle - dblFrontAngle;

                    Tray[intCounter].shoDirToFront = (short)(dblDiff > Math.PI ? 1 : -1);
                    // calculate angular distance from front  e.g. = [0, pi)
                    Tray[intCounter].dblAngularDistanceFromFront = Math.Abs(dblDistance);
                    Tray_Reordered[intCounter] = Tray[intCounter];
                    Tray[intCounter].Index = intCounter;
                }

                // reorder temp array from back to front, ranking them from the furthest angular distance to the nearest
                int intBestIndex;
                double dblFurthestAngle;

                for (int intOuterLoop = 0; intOuterLoop < Tray_Reordered.Length - 1; intOuterLoop++)
                {
                    intBestIndex = intOuterLoop;
                    dblFurthestAngle = Tray_Reordered[intBestIndex].dblAngularDistanceFromFront;

                    for (int intInnerLoop = intOuterLoop + 1; intInnerLoop < Tray_Reordered.Length; intInnerLoop++)
                    {
                        if (Tray_Reordered[intInnerLoop].dblAngularDistanceFromFront < dblFurthestAngle)
                        {
                            dblFurthestAngle = Tray_Reordered[intInnerLoop].dblAngularDistanceFromFront;
                            intBestIndex = intInnerLoop;
                        }
                    }

                    if (intBestIndex != intOuterLoop)
                    {
                        rotatingTray_PicBox ptrTemp = Tray_Reordered[intOuterLoop];
                        Tray_Reordered[intOuterLoop] = Tray_Reordered[intBestIndex];
                        Tray_Reordered[intBestIndex] = ptrTemp;
                    }
                }

                // now place them all on the screen from back to front
                ///   - size relative to front angle
                ///   - position on panel
                for (int intCounter = 0; intCounter < Tray_Reordered.Length; intCounter++)
                {
                    // calculate size relative to front where front is 1.0 and back is 0
                    double dblFractionSize = (Tray_Reordered[intCounter].dblAngularDistanceFromFront / Math.PI);
                    //Tray_Reordered[intCounter].aspectRatioYoverX = (double)Tray_Reordered[intCounter].Image.Height / Tray_Reordered[intCounter].Image.Width;

                    enuSizeControl thisSizeControl = getImageSizeControl(Tray_Reordered[intCounter].Image);
                    switch (thisSizeControl)
                    {
                        case enuSizeControl.width:
                            Tray_Reordered[intCounter].sz.Width = ImageWidth_MinMax.Min + (int)((double)(ImageWidth_MinMax.Max - ImageWidth_MinMax.Min) * dblFractionSize);
                            Tray_Reordered[intCounter].sz.Height = (int)((double)Tray_Reordered[intCounter].sz.Width * Tray_Reordered[intCounter].aspectRatioYoverX);
                            break;

                        case enuSizeControl.height:
                            Tray_Reordered[intCounter].sz.Height = ImageHeight_MinMax.Min + (int)((double)(ImageHeight_MinMax.Max - ImageHeight_MinMax.Min) * dblFractionSize);
                            Tray_Reordered[intCounter].sz.Width = (int)((double)Tray_Reordered[intCounter].sz.Height / Tray_Reordered[intCounter].aspectRatioYoverX);
                            break;

                        case enuSizeControl.none:
                            Tray_Reordered[intCounter].sz = Tray_Reordered[intCounter].Image.Size;
                            break;
                    }

                    // position on unturned ellipse about origin
                    Point ptTemp 
                        = new Point((int)(dblEllipse_Width * Math.Cos(Tray_Reordered[intCounter].rotatedAngle))- Tray_Reordered[intCounter].sz.Width / 2,
                                    (int)(dblEllipse_Height * Math.Sin(Tray_Reordered[intCounter].rotatedAngle))- Tray_Reordered[intCounter].sz.Height / 2);

                    // rotate position by Ellipse angle
                    double radius = (double)Math.Sqrt(Math.Pow(ptTemp.X, 2) + Math.Pow(ptTemp.Y, 2));
                    double theta = arcTan(ptTemp.X, ptTemp.Y) + dblEllipseAngle;

                    Tray_Reordered[intCounter].pt = new Point((int)(ptTrayCenter.X + radius * Math.Cos(theta)),
                                                       (int)(ptTrayCenter.Y + radius * Math.Sin(theta)));

                    if (Tray_Reordered[intCounter].Index == intSelectedImageIndex)
                        ptSelectLoc = new Point(Tray_Reordered[intCounter].pt.X - ptTrayCenter.X, Tray_Reordered[intCounter].pt.Y - ptTrayCenter.Y);

                    Rectangle recBmpDest = new Rectangle(Tray_Reordered[intCounter].pt.X, Tray_Reordered[intCounter].pt.Y, Tray_Reordered[intCounter].sz.Width, Tray_Reordered[intCounter].sz.Height);
                    Rectangle recBmpSrc = new Rectangle(0, 0, Tray_Reordered[intCounter].Image.Width, Tray_Reordered[intCounter].Image.Height);

                    if (((mirror == enuMirror.vertical || mirror == enuMirror.both)
                                 && !Tray_Reordered[intCounter].FlipV)
                        ||
                        !(mirror == enuMirror.vertical || mirror == enuMirror.both)
                                 && Tray_Reordered[intCounter].FlipV)
                    {
                        Tray_Reordered[intCounter].Image.RotateFlip(RotateFlipType.RotateNoneFlipY);
                        Tray_Reordered[intCounter].FlipV = !Tray_Reordered[intCounter].FlipV;
                    }

                    if ((((mirror == enuMirror.horizontal || mirror == enuMirror.both)
                                  && !Tray_Reordered[intCounter].FlipH)
                        ||
                        !(mirror == enuMirror.horizontal || mirror == enuMirror.both)
                                  && Tray_Reordered[intCounter].FlipH))
                    {
                        Tray_Reordered[intCounter].Image.RotateFlip(RotateFlipType.RotateNoneFlipX);
                        Tray_Reordered[intCounter].FlipH = !Tray_Reordered[intCounter].FlipH;
                    }
                    try
                    {
                        if (MakeImagesTransparent)
                        {
                            Bitmap bmpImage = new Bitmap(Tray_Reordered[intCounter].Image);
                            bmpImage.MakeTransparent();
                            g.DrawImage(bmpImage, recBmpDest, recBmpSrc, GraphicsUnit.Pixel);
                        }
                        else
                        {
                            g.DrawImage(Tray_Reordered[intCounter].Image, recBmpDest, recBmpSrc, GraphicsUnit.Pixel);
                        }
                    }
                    catch (Exception) { }
                }

                switch (mirror)
                {
                    case enuMirror.horizontal:
                        bmp.RotateFlip(RotateFlipType.RotateNoneFlipX);
                        break;

                    case enuMirror.vertical:
                        bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
                        break;

                    case enuMirror.both:
                        bmp.RotateFlip(RotateFlipType.RotateNoneFlipXY);
                        break;
                }

                Image = bmp;

                if (intSelectedImageIndex >= 0
                      && Tray != null
                      && Tray.Length > 0
                      && intSelectedImageIndex < Tray.Length)
                {
                    if (mirror == enuMirror.vertical || mirror == enuMirror.both)
                        lblCaption.Top = Height - ptTrayCenter.Y - ptSelectLoc.Y - Tray[intSelectedImageIndex].sz.Height - lblCaption.Height;
                    else
                        lblCaption.Top = ptTrayCenter.Y + ptSelectLoc.Y - lblCaption.Height;

                    if (mirror == enuMirror.horizontal || mirror == enuMirror.both)
                        lblCaption.Left = Width - ptTrayCenter.X - ptSelectLoc.X - Tray[intSelectedImageIndex].sz.Width / 2 - lblCaption.Width / 2;
                    else
                        lblCaption.Left = ptTrayCenter.X + ptSelectLoc.X + (Tray[intSelectedImageIndex].sz.Width / 2 - lblCaption.Width / 2);

                    lblCaption.Text = Tray[intSelectedImageIndex].caption;
                    lblCaption.Visible = bolShowCaptionLabel;
                    lblCaption.BringToFront();
                }
                else
                    lblCaption.Visible = false;


                Refresh();
            }
        }

        public double arcTan(double dX, double dY)
        {
            double dblAngle = 0;
            if (dX == 0)
                if (dY < 0)
                    dblAngle = 3.0 * Math.PI / 2.0;
                else
                    dblAngle = Math.PI / 2.0;
            else
            {
                dblAngle = Math.Atan((double)dY / (double)dX);
                //  Q2 Q1
                //  Q3 Q4
                if (dX < 0)
                {
                    if (dY < 0) // Q3
                        dblAngle = Math.PI + dblAngle;
                    else              // Q2
                        dblAngle = Math.PI + dblAngle;
                }
                else if (dX > 0)
                {
                    if (dY < 0) // Q4
                        dblAngle = 2.0 * Math.PI + dblAngle;
                    else              // Q1
                        ;
                }
            }
            return dblAngle;
        }
    }

    public class rotatingTray_PicBox
    {
        public double angleOnTray;
        public double rotatedAngle;         // angle ON tray + angle OF tray
        public double dblAngularDistanceFromFront; 
        public short shoDirToFront;         // Sign or factor to direct rotation
        public double aspectRatioYoverX;
        public string caption;              
        public bool FlipV;                  // original is vertically inverted
        public bool FlipH;                  // original is horizontally inverted
        public Point pt;                    // last location on screen
        public Size sz;                     // last size on screen
        public int Index;                   // index in original array
        public Image Image; 
    }

    public class classMinMax
    {
        int max;
        int min;

        public classMinMax(int intMin, int intMax)
        {
            if (intMin < intMax)
            {
                max = intMax;
                min = intMin;
            }
            else
            {
                min = intMin ;
                max = intMin +1;
            }
        }

        /// <summary>
        /// get or set min value
        /// </summary>
        public int Min
        {
            get { return min; }
            set
            {
                if (value < max)
                    min = value;
            }
        }

        /// <summary>
        /// get or set max value
        /// </summary>
        public int Max
        {
            get { return max; }
            set
            {
                if (value > min)
                    max = value;
            }
        }
    }

    public class PicTrayOptions
    {
        classPicTray_V2 cLibPicTray;
        public classOptions_ImageSize ImageSize;
        public classOptions_Rotation Rotation;
        public classOptions_Ellipse Ellipse;
        public classOptions_Front Front;
        public classOptions_Caption Caption;        

        public PicTrayOptions(classPicTray_V2 callingClass)
        {
            cLibPicTray = callingClass;
            ImageSize = new classOptions_ImageSize(ref cLibPicTray);
            Rotation = new classOptions_Rotation(ref cLibPicTray);
            Ellipse = new classOptions_Ellipse(ref cLibPicTray);
            Front = new classOptions_Front(ref cLibPicTray);
            Caption = new classOptions_Caption(ref cLibPicTray);
        }


        /// <summary>
        /// get or set option to make the white pixels of the rotating images transparent
        /// </summary>
        public bool makeImagesTransparent
        {
            get { return cLibPicTray.MakeImagesTransparent; }
            set { cLibPicTray.MakeImagesTransparent = value; }
        }

        /// <summary>
        /// get or set Tray's height
        /// </summary>
        public int Height
        {
            get { return cLibPicTray.Height; }
            set { cLibPicTray.Height = value; }
        }

        /// <summary>
        /// get or set Tray's width
        /// </summary>
        public int Width
        {
            get { return cLibPicTray.Width; }
            set { cLibPicTray.Width = value; }
        }

        /// <summary>
        /// get or set Tray's backcolor
        /// </summary>
        public Color BackColor
        {
            get { return cLibPicTray.BackColor; }
            set { cLibPicTray.BackColor = value; }
        }

        /// <summary>
        /// get or set the user selection mode.
        /// </summary>
        public enuSelectionMode SelectionMode
        {
            get { return cLibPicTray.SelectionMode; }
            set { cLibPicTray.SelectionMode = value; }
        }

        /// <summary>
        /// get information about the image currently selected
        /// </summary>
        public rotatingTray_PicBox SelectedImage
        {
            get { return cLibPicTray.SelectedImage; }
        }

        /// <summary>
        /// get or set the tray's mirror type
        /// </summary>
        public enuMirror Mirror
        {
            get { return cLibPicTray.Mirror; }
            set { cLibPicTray.Mirror = value; }
        }

        /// <summary>
        /// get or set direction in which the tray is rotated
        /// </summary>
        public double Angle
        {
            get { return cLibPicTray.Angle; }
            set { cLibPicTray.Angle = value; }
        }

        /// <summary>
        /// get or set central point around which tray rotates
        /// </summary>
        public Point TrayCenter
        {
            get { return cLibPicTray.TrayCenter; }
            set { cLibPicTray.TrayCenter = value; }
        }

        /// <summary>
        /// get or set pop-up form of clicked image
        /// </summary>
        public bool PopUpClickPic
        {
            get { return cLibPicTray.PopUpClickPic; }
            set { cLibPicTray.PopUpClickPic = value; }
        }

        
    }

    public class classOptions_Ellipse
    {
        classPicTray_V2 cLibPicTray;

        public classOptions_Ellipse(ref classPicTray_V2 callingClass)
        {
            cLibPicTray = callingClass;
        }

        /// <summary>
        /// get or set the width of the ellipse about which images rotate
        /// </summary>
        public int Radius_Width
        {
            get { return (int)cLibPicTray.EllipseRadius_Width; }
            set { cLibPicTray.EllipseRadius_Width = value; }
        }

        /// <summary>
        /// get or set the Height of the ellipse about which images rotate
        /// </summary>
        public int Radius_Height
        {
            get { return (int)cLibPicTray.EllipseRadius_Height; }
            set { cLibPicTray.EllipseRadius_Height = value; }
        }

        /// <summary>
        /// angle of the ellipse which describes the path of the images, angle = [0, pi/2)
        /// </summary>
        public double Angle
        {
            get { return cLibPicTray.EllipseAngle; }
            set { cLibPicTray.EllipseAngle = value; }
        }
    }


    public class classOptions_ImageSize
    {
        classPicTray_V2 cLibPicTray;

        public classOptions_ImageSize(ref classPicTray_V2 callingClass)
        {
            cLibPicTray = callingClass;
        }

        /// <summary>
        /// max & min sizes of image at front and back when controlled by 'width'
        /// </summary>        
        public classMinMax Width_MinMax
        {
            get { return cLibPicTray.ImageWidth_MinMax; }
            set { cLibPicTray.ImageWidth_MinMax = value; }
        }

        /// <summary>
        /// max & min sizes of image at front and back when controlled by 'height'
        /// </summary>
        public classMinMax Height_MinMax
        {
            get { return cLibPicTray.ImageHeight_MinMax; }
            set { cLibPicTray.ImageHeight_MinMax = value; }
        }

        /// <summary>
        /// get or set the size control option, max/min of either width, height, both or neither
        /// </summary>
        public enuSizeControl Size_Control
        {
            get { return cLibPicTray.SizeControl; }
            set { cLibPicTray.SizeControl = value; }
        }
    }

    public class classOptions_Caption
    {
        classPicTray_V2 cLibPicTray;

        public classOptions_Caption(ref classPicTray_V2 callingClass)
        {
            cLibPicTray = callingClass;
        }

        /// <summary>
        /// get or set visible caption for mouse-over image
        /// </summary>
        public bool Show
        {
            get { return cLibPicTray.ShowCaptionLabel; }
            set { cLibPicTray.ShowCaptionLabel = value; }
        }

        /// <summary>
        /// get or set the forecolor of the captions label
        /// </summary>
        public Color ForeColor
        {
            get { return cLibPicTray.lblCaption.ForeColor; }
            set { cLibPicTray.lblCaption.ForeColor = value; }
        }


        /// <summary>
        /// get or set the Backcolor of the captions label
        /// </summary>
        public Color BackColor
        {
            get { return cLibPicTray.lblCaption.BackColor; }
            set { cLibPicTray.lblCaption.BackColor = value; }
        }

        /// <summary>
        /// get or set the font of the captions label
        /// </summary>
        public Font Font
        {
            get { return cLibPicTray.lblCaption.Font; }
            set { cLibPicTray.lblCaption.Font = value; }
        }

    }

    public class classOptions_Rotation
    {
        classPicTray_V2 cLibPicTray;

        public classOptions_Rotation(ref classPicTray_V2 callingClass)
        {
            cLibPicTray = callingClass;
        }



        /// <summary>
        /// get or set the delay time between turns of tray in milliseconds
        /// </summary>
        public int Interval
        {
            get { return cLibPicTray.tmrRotate.Interval; }
            set { cLibPicTray.tmrRotate.Interval = value; }
        }

        /// <summary>
        /// get or set speed at which tray rotates can be positive or negative
        /// </summary>
        public double StepSize
        {
            get { return cLibPicTray.RotationStepSize; }
            set { cLibPicTray.RotationStepSize = value; }
        }
        /// <summary>
        /// set mode which tray rotates : carousel or toSelected
        /// </summary>
        public enuRotationMode Mode
        {
            get { return cLibPicTray.RotationMode; }
            set { cLibPicTray.RotationMode = value; }
        }

        public void disable() { cLibPicTray.disableRotate(); }

        public void enable() { cLibPicTray.enableRotate(); }

    }

    public class classOptions_Front
    {
        classPicTray_V2 cLibPicTray;

        public classOptions_Front(ref classPicTray_V2 callingClass)
        {
            cLibPicTray = callingClass;
        }

        /// <summary>
        /// get or set Front angle method used by user
        /// </summary>
        public enuFrontUserSet UserSet
        {
            get { return cLibPicTray.FrontUserSet; }
            set { cLibPicTray.FrontUserSet = value; }
        }

        /// <summary>
        /// get or set the direction in which the main selected image is displayed
        /// set to a value between [0, 2pi)
        /// </summary>
        public double Angle
        {
            get { return cLibPicTray.FrontAngle; }
            set
            {
                cLibPicTray.FrontAngle = value;
            }
        }
  
    }

    public class classMath
    {
        public class classMaxMinDouble
        {
            public double Min;
            public double Max;

            public classMaxMinDouble()
            {
                Min = 0.0;
                Max = 0.0;
            }

            public classMaxMinDouble(double min, double max)
            {
                Min = min;
                Max = max;
            }
        }

        public class classRadialCoor
        {
            public double angle;
            public double radius;

            public classRadialCoor()
            {
                angle = 0;
                radius = 0;
            }
            public classRadialCoor(double Angle, double Radius)
            {
                angle = Angle;
                radius = Radius;
            }
        }

        /// <summary>
        /// converts a radial coordinate system into cartesian with origin as initial coordinate
        /// </summary>
        /// <param name="radCoor">radial vector</param>
        /// <returns>cartesian point</returns>
        public static Point getRadLocation(classRadialCoor radCoor) { return getRadLocation(new Point(0, 0), radCoor); }

        /// <summary>
        /// calculates the cartesian location away from input point using radial vector
        /// </summary>
        /// <param name="pt">input point from which </param>
        /// <param name="radCoor">radial vector</param>
        /// <returns>cartesian point</returns>
        public static Point getRadLocation(Point pt, classRadialCoor radCoor)
        { return new Point((int)(pt.X + radCoor.radius * Math.Cos(radCoor.angle)), (int)(pt.Y + radCoor.radius * Math.Sin(radCoor.angle))); }

        /// <summary>
        /// returns the angle in radians between the points ptA and ptB
        /// </summary>
        /// <param name="ptA">first point</param>
        /// <param name="ptB">second point</param>
        /// <returns>double variable equal to angle between origin and point(dX, dY) in radians</returns>
        public static double arcTan(Point ptA, Point ptB) { return arcTan(ptA.X - ptB.X, ptA.Y - ptB.Y); }
        /// <summary>
        /// returns the angle in radians between the origin and the point located at (dX, dY)
        /// </summary>
        /// <param name="dX">x coordinate of vector end</param>
        /// <param name="dY">y coordinate of vector end</param>
        /// <returns>double variable equal to angle between origin and point(dX, dY) in radians</returns>
        public static double arcTan(double dX, double dY)
        {
            double dblAngle = 0;
            if (dX == 0)
                if (dY < 0)
                    dblAngle = 3.0 * Math.PI / 2.0;
                else
                    dblAngle = Math.PI / 2.0;
            else
            {
                dblAngle = Math.Atan((double)dY / (double)dX);
                //  Q2 Q1
                //  Q3 Q4
                if (dX < 0)
                {
                    if (dY < 0) // Q3
                        dblAngle = Math.PI + dblAngle;
                    else              // Q2
                        dblAngle = Math.PI + dblAngle;
                }
                else if (dX > 0)
                {
                    if (dY < 0) // Q4
                        dblAngle = 2.0 * Math.PI + dblAngle;
                    else              // Q1
                        ;
                }
            }
            cleanAngle(ref dblAngle);
            return dblAngle;
        }

        public static void cleanRadians(ref classRadialCoor udrRadCoor) { cleanAngle(ref udrRadCoor.angle); }
        public static double cleanAngle(ref double angle)
        {
            while (angle < 0)
                angle += 2 * Math.PI;

            while (angle > 2 * Math.PI)
                angle -= 2 * Math.PI;

            return angle;
        }

        public static double distanceBetweenTwoPoints(Point pt1, Point pt2) { return Math.Sqrt(Math.Pow(pt1.X - pt2.X, 2) + Math.Pow(pt1.Y - pt2.Y, 2)); }
    }

}