using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Telerik.WinControls;
using Telerik.WinControls.Primitives;
using Telerik.WinControls.UI;
using Telerik.QuickStart.WinControls;
using Telerik.WinControls.Commands;


namespace Telerik.QuickStart.WinControls
{
    public class RadCarouselElement: RadItem
    {
        private RadItemOwnerCollection items;
        private int selectedIndex = -1;
        bool arranged = false;
        private TextPrimitive textDescriptionElement;
        private TextPrimitive textTitleElement;

        private Size textDisplayOffset = new Size(200, 200);
        private Size maxItemSize = new Size(240, 180);

        private Size calculatedTextDisplayOffset;
        private int caruselYRadius = 60;
        
        private RadButtonElement btnLeft;
        private RadButtonElement btnRight;
        private FillPrimitive spotLight;
        private FillPrimitive backFill;
        private RadCarouselLayout layout;
        private Size itemDisplayOffset;

        public delegate void ExampleStartEventHandler ( object sender, string selectedExampleText );
        public event ExampleStartEventHandler ExampleStart;

        
        protected override void InitializeFields()
        {
            base.InitializeFields();

            this.BypassLayoutPolicies = true;
            items = new RadItemOwnerCollection();
            items.ItemTypes = new Type[] { typeof(RadCarouselItem) };
            items.ItemsChanged += new ItemChangedDelegate(items_ItemsChanged);
        }

        void items_ItemsChanged(RadItemCollection changed, RadItem target, ItemsChangeOperation operation)
        {
            if (operation == ItemsChangeOperation.Inserted ||
                operation == ItemsChangeOperation.Set)
            {
                target.AutoSize = false;

                target.Bounds = Rectangle.Empty;
                target.Visibility = ElementVisibility.Hidden;

                if (target is RadCarouselItem)
                {
                    int targetIndex = changed.IndexOf(target);
                    RadCarouselItem button = target as RadCarouselItem;

                    FillPrimitive fill = button.ButtonFillElement as FillPrimitive;
                    if (fill != null)
                    {
                        CarouselMouseOverBehavior behavior = new CarouselMouseOverBehavior(Color.FromArgb(234, 244, 255));
                        behavior.SetupInitialItemState(fill);
                        fill.AddBehavior(behavior);
                        //fill.PositionOffset = new SizeF(0f, -20f);
                    }

                    target.AddBehavior(new CarouselItemCursorBehavior(this));
                }

                if (operation == ItemsChangeOperation.Inserted)
                {
                    if (this.SelectedIndex == -1)
                        this.SelectedIndex = 0;
                }
            }
            //reset selected index whenever items changed
            else if (operation == ItemsChangeOperation.Cleared || 
                operation == ItemsChangeOperation.Removed)
            {
                this.SelectedIndex = -1;
            }

            arranged = false;
        }
        
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Browsable(true), Category(RadDesignCategory.DataCategory)]
        public RadItemOwnerCollection Items
        {
            get
            {
                return this.items;
            }
        }

        [DefaultValue(-1)]
        public int SelectedIndex
        {
            get
            {
                return selectedIndex;
            }
            set
            {
                if (this.Items.Count > 0 && value > this.Items.Count - 1)
                {
                    throw new IndexOutOfRangeException("SelectedIndex");
                }

                if (selectedIndex != value)
                {
                    selectedIndex = value;
                    
                    this.arranged = false;
                    this.Invalidate(false);
                    //this.Rearange();
                }
            }
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public TextPrimitive TextDescriptionElement
        {
            get
            {
                return this.textDescriptionElement;
            }
        }


        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public TextPrimitive TextTitleElement
        {
            get
            {
                return this.textTitleElement;
            }
        }

        [DefaultValue("200, 200")]
        public Size TextDisplayOffset
        {
            get { return textDisplayOffset; }
            set { textDisplayOffset = value; }
        }

        [DefaultValue("240, 180")]
        public Size MaxItemSize
        {
            get { return maxItemSize; }
            set { maxItemSize = value; }
        }

        [DefaultValue(60)]
        public int CaruselYRadius
        {
            get { return caruselYRadius; }
            set { caruselYRadius = value; }
        }

        //private void ApplyThemeToButton(RadItem item, string themeName)
        //{
        //    DefaultStyleBuilder builder = ThemeResolutionService.GetStyleSheetBuilder(this.ElementTree.Control, item.GetThemeEffectiveType().FullName, string.Empty, themeName)
        //                                  as DefaultStyleBuilder;

        //    if (builder != null)
        //        item.Style = new XmlStyleSheet(builder.Style).GetStyleSheet(); //clone because control might modify it later
        //}

        //protected override void ApplyTheme()
        //{
        //    ApplyThemeToButton(this.btnLeft, "OfficeGlass");
        //    ApplyThemeToButton(this.btnRight, "OfficeGlass");

        //    base.ApplyTheme();
        //}

        private Point CalcPoint(Point center, int radiusX, int radiusY, float z, int angleGrad)
        {
            double rad = Math.PI/180*angleGrad;

            int x = (int)Math.Round(center.X + Math.Sin(rad) * radiusX);
            double fY = center.Y + Math.Cos(rad) * radiusY;

            int y = (int)Math.Round(fY / 2F  + (center.Y + radiusY) * z * 0.65);

            return new Point(x, y);
        }

        private void Rearrange()
        {
            if (this.Size == Size.Empty || this.IsDisposing || this.IsDisposed)
            {
                return;
            }            

            if (this.Items.Count < 2)
            {
                this.BtnLeft.Visibility = ElementVisibility.Collapsed;
                this.BtnRight.Visibility = ElementVisibility.Collapsed;
            }
            else
            {
                this.BtnLeft.Visibility = ElementVisibility.Visible;
                this.BtnRight.Visibility = ElementVisibility.Visible;
            }

            //arrange scene            
            this.backFill.Bounds = new Rectangle(0, 0, 20, this.Size.Height);
            this.spotLight.Bounds = new Rectangle((int)((this.Size.Width - this.Size.Width * 1.3) / 2) , (int)(-this.Size.Height * 1.8 / 2), (int)(this.Size.Width * 1.4), (int)(this.Size.Height * 1.8));

            //arange visible items
            if (this.SelectedIndex < 0 || this.Items.Count == 0)
                return;            

            int visibleItemCount = this.Items.Count;

            //distance between the items (angle degrees)
            float itemDistance = 360f / visibleItemCount;

            int centerIndex = visibleItemCount / 2;

            int itemIndex = this.SelectedIndex;
            int offsetIndex = 0;

            this.calculatedTextDisplayOffset = new Size(
                Math.Max(this.TextDisplayOffset.Width, this.Size.Width - 20),
                this.TextDisplayOffset.Height);

            Point centerPoint = new Point(this.Size.Width / 2 + itemDisplayOffset.Width, (int)(this.Size.Height / 2.5 + this.ItemDisplayOffset.Height));

            //int reflectionHeight = maxItemSize.Height / 3;

            Size calculatedMaxItemSize = maxItemSize;//new Size(maxItemSize.Width, maxItemSize.Height + reflectionHeight);

            int radiusY = caruselYRadius; //bottomBasePoint.Y - centerPoint.Y - TextDisplayOffset.Height/* / 2*/;
            int radiusX = (int)(this.Size.Width / 2.0f) - calculatedMaxItemSize.Width / 2;

            Point bottomBasePoint = new Point(centerPoint.X, centerPoint.Y + radiusY);

            float minScaleF = 0.35f;

            //ArrangeNavigationButtons(radiusX, radiusY, new Point(centerPoint.X, bottomBasePoint.Y));

            //loop through items to rearange
            for (int i = 0; i < visibleItemCount; i++)
            {
                RadItem item = this.Items[itemIndex];

                item.ZIndex = -Math.Abs(offsetIndex);
                float scaleF = 1f - (((1f - minScaleF) / (visibleItemCount / 2f)) * Math.Abs(offsetIndex));

                Point itemPoint = this.CalcPoint(centerPoint, radiusX, radiusY, scaleF, (int)(itemDistance * offsetIndex));

                int itemWidth = (int)(calculatedMaxItemSize.Width * scaleF);
                int itemHeight = (int)(calculatedMaxItemSize.Height * scaleF);

                itemPoint = Point.Add(itemPoint, new Size(-itemWidth / 2, -itemHeight / 2));

                if (item.Bounds == Rectangle.Empty) //set initial item position to control center
                {
                    item.Bounds = new Rectangle(
                         centerPoint.X - calculatedMaxItemSize.Width / 4,
                         centerPoint.Y - calculatedMaxItemSize.Height / 4,
                         calculatedMaxItemSize.Width,
                         calculatedMaxItemSize.Height);

                    item.Visibility = ElementVisibility.Visible;

                    PropertySetting setting = new PropertySetting(RadElement.ScaleTransformProperty, new SizeF(0.5f, 0.5f));
                    setting.ApplyValue(item);
                }

                BeginItemAnimation(i, item, itemPoint, calculatedMaxItemSize, scaleF, visibleItemCount, bottomBasePoint);

                itemIndex++;
                if (itemIndex >= items.Count)
                {
                    itemIndex = 0;
                }

                if (offsetIndex == centerIndex)
                {
                    if (this.Items.Count % 2 == 0)
                        offsetIndex = -centerIndex + 1;
                    else
                        offsetIndex = -centerIndex;
                }
                else
                    offsetIndex++;
            }

            arranged = true;
        }

        public Size ItemDisplayOffset
        {
            get { return this.itemDisplayOffset; }
            set { this.itemDisplayOffset = value; }
        }

        public RadButtonElement BtnLeft
        {
            get { return btnLeft; }
        }

        public RadButtonElement BtnRight
        {
            get { return btnRight; }
        }

        protected override SizeF ArrangeOverride(SizeF finalSize)
        {
            if (finalSize.Width <= 0 || finalSize.Height <= 0)
            {
                return finalSize;
            }

            this.layout.Arrange( new RectangleF(PointF.Empty, finalSize));
            
            this.calculatedTextDisplayOffset = new Size(
                Math.Max(this.TextDisplayOffset.Width, this.Size.Width - 20),
                this.TextDisplayOffset.Height);

            PointF centerPoint = new PointF(finalSize.Width / 2f + itemDisplayOffset.Width, finalSize.Height / 2.5f + this.itemDisplayOffset.Height);

            Size calculatedMaxItemSize = maxItemSize;

            int radiusY = caruselYRadius;
            int radiusX = (int)(this.Size.Width / 2.0f) - calculatedMaxItemSize.Width / 2;

            PointF bottomBasePoint = new PointF(centerPoint.X, centerPoint.Y + radiusY + calculatedTextDisplayOffset.Height - 50);

            ArrangeNavigationButtons(finalSize, radiusX, Point.Round(bottomBasePoint));

            return finalSize;
        }

        private void ArrangeNavigationButtons(SizeF finalSize, int radiusX, Point zeroPoint)
        {
            Size btnSize = new Size(40, 40);

            this.btnLeft.Arrange(new RectangleF(PointF.Empty, btnSize));
            this.btnRight.Arrange(new RectangleF(PointF.Empty, btnSize));

            this.BtnLeft.ZIndex = this.Items.Count + 1;
            this.BtnRight.ZIndex = this.Items.Count + 1;

            int centerOffset = Math.Max((int)(radiusX * 9f / 10f), (int)(finalSize.Width / 3.8f + 10));

            int zeroY = zeroPoint.Y;

            Rectangle rect = new Rectangle( new Point(
                zeroPoint.X - centerOffset - btnSize.Width,
                zeroY),
                btnSize);

            this.btnLeft.Arrange(rect);
            //this.btnLeft.Bounds = rect;

            rect = new Rectangle( new Point(
                zeroPoint.X + centerOffset,
                zeroY),
                btnSize);

            this.btnRight.Arrange(rect);
            //this.btnRight.Bounds = rect;
        }

        private void BeginItemAnimation(int i, RadItem item, Point itemPoint, Size maxItemSize, float scaleF, int visibleItemCount, Point zeroPoint)
        {
            AnimatedPropertySetting setting = new AnimatedPropertySetting(
                RadElement.BoundsProperty,
                new Rectangle(itemPoint.X, itemPoint.Y, maxItemSize.Width, maxItemSize.Height), //end value
                30,  //number of frames
                40); //interval between frames

            AnimatedPropertySetting setting1 = new AnimatedPropertySetting(
                VisualElement.OpacityProperty,
                0.6d,
                Math.Min(1d,  Math.Max(0d, 1d * scaleF)),
                30,
                40);

            AnimatedPropertySetting setting2 = new AnimatedPropertySetting(
                RadElement.ScaleTransformProperty,
                new SizeF(scaleF, scaleF),
                30,
                40);

            setting.ApplyEasingType = RadEasingType.InOutQuad;
            setting1.ApplyEasingType = RadEasingType.InOutQuad;
            setting2.ApplyEasingType = RadEasingType.InOutQuad;

            //first item animation triggers text fade-out animation
            if (i == 0)
            {
                setting.AnimationStarted +=
                    delegate
                        {
                            AnimatedPropertySetting animatedTextAppearance = new AnimatedPropertySetting
                                (
                                VisualElement.OpacityProperty,
                                0d, //end value
                                2, //number of frames
                                25 //interval between frames
                                );

                            animatedTextAppearance.ApplyValue(this.TextTitleElement);
                        };
            }

            //last item animation triggers text fade-in animation
            if (i == visibleItemCount - 1)
            {
                BeginTextAnimation(setting, zeroPoint);
            }

            setting.ApplyValue(item);
            //setting.RemoveAfterApply = true;
            setting1.ApplyValue(item);
            //setting1.RemoveAfterApply = true;
            setting2.ApplyValue(item);
            //setting2.RemoveAfterApply = true;
        }

        private void BeginTextAnimation(AnimatedPropertySetting setting, Point zeroPoint)
        {
            setting.AnimationFinished += delegate
                    {
                        //if (this.SelectedIndex > this.Items.Count - 1)
                        //{
                        //    return;
                        //}

                        // DK_03_09_2007 - fixes some invalid index values
                        if (this.SelectedIndex < 0 ||
                            ((this.Items.Count - 1) < this.SelectedIndex))
                        {
                            return;
                        }

                        this.TextTitleElement.Visibility = ElementVisibility.Visible;

                        if (outerDescription == null)
                        {
                            this.TextDescriptionElement.Bounds = new Rectangle(
                                Math.Max(5, zeroPoint.X - calculatedTextDisplayOffset.Width / 2),
                                zeroPoint.Y + calculatedTextDisplayOffset.Height,
                                calculatedTextDisplayOffset.Width,
                                Math.Max(0, this.Size.Height - (zeroPoint.Y + calculatedTextDisplayOffset.Height)));
                        }

                        int titleTop = zeroPoint.Y + calculatedTextDisplayOffset.Height - 50;

                        this.TextTitleElement.Bounds = new Rectangle(
                            Math.Max(5, zeroPoint.X - calculatedTextDisplayOffset.Width / 2),
                            titleTop,
                            calculatedTextDisplayOffset.Width,
                            Math.Max(0, this.Size.Height - titleTop));

                            this.TextDescriptionElement.Text = ((RadCarouselItem) this.Items[this.SelectedIndex]).Description;
                            this.textTitleElement.Text = ((RadCarouselItem)this.Items[this.SelectedIndex]).Title;                            


                        AnimatedPropertySetting animatedTextAppearance = new AnimatedPropertySetting
                            (
                            VisualElement.OpacityProperty,
                            1d, //end value
                            30, //number of frames
                            40  //interval between frames
                            );

                        animatedTextAppearance.ApplyValue(this.TextTitleElement);
                        animatedTextAppearance.RemoveAfterApply = true;
                    };
        }

        protected override void CreateChildElements()
        {
            backFill = new FillPrimitive();
            backFill.Class = "BackFill";
            backFill.AutoSize = false;

            this.Children.Add(backFill);

            this.spotLight = new FillPrimitive();
            spotLight.AutoSize = false;
            spotLight.Class = "SpotLight";

            this.Children.Add(spotLight);

            layout = new RadCarouselLayout();
            layout.BypassLayoutPolicies = true;
            
            this.textDescriptionElement = new TextPrimitive();
            this.textDescriptionElement.UseMnemonic = false;

            this.textDescriptionElement.ForeColor = Color.White;
            this.textDescriptionElement.Visibility = ElementVisibility.Hidden;
            this.textDescriptionElement.AutoSizeMode = RadAutoSizeMode.FitToAvailableSize;
            this.textDescriptionElement.TextAlignment = ContentAlignment.TopLeft;
            this.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
            this.TextDescriptionElement.AutoSize = false;
            this.TextDescriptionElement.TextWrap = true;

            layout.Children.Add(this.textDescriptionElement);


            this.textTitleElement = new TextPrimitive();
            this.textTitleElement.UseMnemonic = false;
            this.textTitleElement.ForeColor = Color.White;
            this.textTitleElement.Visibility = ElementVisibility.Hidden;
            this.textTitleElement.AutoSizeMode = RadAutoSizeMode.FitToAvailableSize;
            this.textTitleElement.TextAlignment = ContentAlignment.TopLeft;
            this.textTitleElement.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;

            this.textDescriptionElement.BindProperty(VisualElement.OpacityProperty, this.textTitleElement,
                                               VisualElement.OpacityProperty, PropertyBindingOptions.OneWay);

            layout.Children.Add(this.textTitleElement);

            btnLeft = new RadButtonElement();
            SetupNavButton(BtnLeft);
            this.Children.Add(BtnLeft);

            btnRight = new RadButtonElement();
            SetupNavButton(BtnRight);
            this.Children.Add(BtnRight);

            this.Children.Add(layout);

            this.Items.Owner = layout;
        }

        private void SetupNavButton(RadButtonElement button)
        {
            button.AddBehavior(new RadMouseOverCursorBehavior(Cursors.Default, Cursors.Hand));
        }

        private Image ImageFromResource(string resourceName)
        {
            using (Stream stream = this.GetType().Assembly.GetManifestResourceStream(resourceName))
            {
                if (stream != null)
                {
                    return new Bitmap(Image.FromStream(stream));
                }
            }

            return null;
        }

        protected override void OnBubbleEvent(RadElement sender, RoutedEventArgs args)
        {
            if (args.RoutedEvent == RadElement.MouseClickedEvent)
            {
                int newSelectedIndex = -1;

                if (sender == this.BtnLeft)
                {
                    newSelectedIndex = this.SelectedIndex - 1;
                    if (newSelectedIndex < 0)
                        newSelectedIndex = this.Items.Count - 1;
                } 
                else if (sender == this.BtnRight)
                {
                    newSelectedIndex = this.SelectedIndex + 1;
                    if (newSelectedIndex > this.Items.Count - 1)
                        newSelectedIndex = this.Items.Count > 0? 0 : -1;
                }
                else if (sender is RadItem)
                {
                    newSelectedIndex = this.Items.IndexOf(sender as RadItem);
                    
                }

                if (newSelectedIndex >= 0)
                {
                    if (this.SelectedIndex == newSelectedIndex)
                    {
                        this.StartTransition();
                    }
                    this.SelectedIndex = newSelectedIndex;
                }
            }
        }

        private void StartTransition()
        {
            foreach(Control childControl in this.ElementTree.Control.Controls)
            {
                childControl.Visible = false;
            }

            this.textTitleElement.Visibility = ElementVisibility.Hidden;
            this.btnLeft.Visibility = ElementVisibility.Hidden;
            this.btnRight.Visibility = ElementVisibility.Hidden;

            AnimatedPropertySetting setting = new AnimatedPropertySetting(
                VisualElement.OpacityProperty,
                0d,
                20,
                33
                );

            setting.AnimationFinished +=
                delegate
                    {
                        this.ElementTree.Control.Visible = false;

                        this.textTitleElement.Visibility = ElementVisibility.Visible;
                        this.btnLeft.Visibility = ElementVisibility.Visible;
                        this.btnRight.Visibility = ElementVisibility.Visible;

                        this.RaiseSelectedItemClecked();
                        foreach (Control childControl in this.ElementTree.Control.Controls)
                        {
                            childControl.Visible = true;
                        }
                    };

            setting.RestoreValueAfterApply = true;
            setting.RemoveAfterApply = true;
            setting.ApplyEasingType = RadEasingType.InQuad;
            setting.ApplyValue(this.Parent);
        }

        private void RaiseSelectedItemClecked()
        {
            RadCarouselItem item = this.Items[this.selectedIndex] as RadCarouselItem;

            if (item == null)
            {
                return;
            }

            this.OnExampleStart( item.TreeNodeText );
        }

        
        public void OnExampleStart(string treenodeText)
        {
            if( this.ExampleStart!=null )
            {
                this.ExampleStart( this, treenodeText );
            }
        }

        protected override void OnBoundsChanged(RadPropertyChangedEventArgs e)
        {
            this.arranged = false;

            base.OnBoundsChanged(e);
        }

        protected override void PaintOverride(Telerik.WinControls.Paint.IGraphics screenRadGraphics, Rectangle clipRectangle, float angle, SizeF scale, bool useRelativeTransformation)
        {
            base.PaintOverride(screenRadGraphics, clipRectangle, angle, scale, useRelativeTransformation);

            screenRadGraphics.DrawRectangle(new Rectangle(Point.Empty, this.Size), this.BackColor);
        }

        private delegate void CallMethodDelegate();

        protected override void PaintChildren(Telerik.WinControls.Paint.IGraphics graphics, Rectangle clipRectange, float angle, SizeF scale, bool useRelativeTransformation)
        {
            if (!arranged)
            {
                arranged = true;
                this.ElementTree.Control.BeginInvoke(new CallMethodDelegate(Rearrange));
            }            

            base.PaintChildren(graphics, clipRectange, angle, scale, useRelativeTransformation);            
        }

        //used to enable description displayed in another control
        private RadItem outerDescription;

        public void SetOuterDescriptionDisplay(RadItem outerDescriptionDisplay)
        {
            this.outerDescription = outerDescriptionDisplay;

            this.outerDescription.BindProperty(VisualElement.OpacityProperty, this.textTitleElement,
                                               VisualElement.OpacityProperty, PropertyBindingOptions.OneWay);

            this.outerDescription.BindProperty(RadElement.VisibilityProperty, this.textTitleElement,
                                               RadElement.VisibilityProperty, PropertyBindingOptions.OneWay);

            this.outerDescription.BindProperty(RadItem.TextProperty, this.textDescriptionElement,
                                               TextPrimitive.TextProperty, PropertyBindingOptions.OneWay);
        }

        public void RemoveOuterDescriptionDisplay()
        {
            if (this.outerDescription == null)
            {
                return;
            }

            this.outerDescription.UnbindProperty(RadItem.TextProperty);

            this.outerDescription.UnbindProperty(VisualElement.OpacityProperty);

            //workaround a bug caused by UnbindProperty 
            this.outerDescription.Opacity = 0.5d;
            this.outerDescription.Opacity = 1d;
            //this.outerDescription.ResetValue(VisualElement.OpacityProperty);

            this.outerDescription.UnbindProperty(RadElement.VisibilityProperty);
            //this.outerDescription.ResetValue(VisualElement.VisibilityProperty);
            //workaround a bug caused by UnbindProperty
            this.outerDescription.Visibility = ElementVisibility.Hidden;
            this.outerDescription.Visibility = ElementVisibility.Visible;

            this.outerDescription = null;
        }
    }
}
