using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

using MindFusion.Diagramming;


namespace AnimatedFold
{
	public partial class MainForm : Form
	{
		public MainForm()
		{
			InitializeComponent();
		}

		private void Form1_Load(object sender, EventArgs e)
		{
			var ctr = diagram.Factory.CreateContainerNode(20, 20, 100, 100, true);
			var node1 = diagram.Factory.CreateShapeNode(30, 35, 15, 15);
			var node2 = diagram.Factory.CreateShapeNode(80, 45, 15, 15);
			diagram.Factory.CreateDiagramLink(node1, node2);

			ctr.Add(node1);
			ctr.Add(node2);

			var ctr2 = diagram.Factory.CreateContainerNode(20, 20, 100, 100, true);
			ctr2.Add(ctr);
		}

		List<DiagramItem> GetDescendents(ContainerNode container)
		{
			var nodes = diagram.Nodes.Where(
				container.ContainsRecursively);

			var links = diagram.Links.Where(l =>
				nodes.Contains(l.Origin) ||
				nodes.Contains(l.Destination));

			return
				nodes.Cast<DiagramItem>().Concat(
				links.Cast<DiagramItem>()).ToList();
		}

		void OnContainerFolded(object sender, NodeEventArgs e)
		{
			var container = (ContainerNode)e.Node;
			StartAnimation(container, true);
		}

		void OnContainerUnfolded(object sender, NodeEventArgs e)
		{
			var container = (ContainerNode)e.Node;
			StartAnimation(container, false);
		}

		void OnDrawForeground(object sender, DiagramEventArgs e)
		{
			if (animatedItems != null && frameCounter > 0)
			{
				var options = new RenderOptions();
				var g = e.Graphics;

				// apply scale corresponding to current frame
				var scale = (float)frameCounter / maxFrames;
				g.TranslateTransform(scaleCenter.X, scaleCenter.Y);
				g.ScaleTransform(scale, scale);
				g.TranslateTransform(-scaleCenter.X, -scaleCenter.Y);

				// draw container background
				g.FillRectangle(Brushes.White, ctrBounds);
				g.DrawRectangle(Pens.Black, ctrBounds);

				// draw contained items
				foreach (var item in animatedItems)
					item.Draw(e.Graphics, options);
			}
		}

		void StartAnimation(ContainerNode container, bool fold)
		{
			var bounds = container.Bounds;
			var scaleCenter = new PointF(
				(bounds.Left + bounds.Right) / 2, bounds.Top);

			// collect items that will be unfolded
			animatedItems = GetDescendents(container);

			// animation will also draw this rectangle as background
			ctrBounds = bounds;
			ctrBounds.Size = container.UnfoldedSize;
			ctrBounds.Y += container.CaptionHeight;
			ctrBounds.Height -= container.CaptionHeight;

			// start animation timers
			Animate(scaleCenter, fold);

			if (!fold)
			{
				// temporarily fold back when animating unfold operation
				// so that contained items stay invisible
				container.Folded = true;
				toUnfold = container;
			}
		}

		void Animate(PointF scaleCenter, bool scaleDown)
		{
			if (scaleDown)
			{
				frameCounter = maxFrames;
				frameIncrement = -1;
			}
			else
			{
				frameCounter = 0;
				frameIncrement = +1;
			}
			this.scaleCenter = scaleCenter;

			animationTimer = new Timer();
			animationTimer.Tick += OnAnimationTimer;
			animationTimer.Interval = duration / maxFrames;
			animationTimer.Start();
		}

		void OnAnimationTimer(object sender, EventArgs e)
		{
			frameCounter += frameIncrement;
			diagramView.Invalidate();
			if (frameCounter == 0 || frameCounter == maxFrames)
			{
				animationTimer.Stop();
				animationTimer.Dispose();
				animationTimer = null;
				animatedItems = null;

				if (toUnfold != null)
				{
					toUnfold.Folded = false;
					toUnfold = null;
				}
			}
		}

		List<DiagramItem> animatedItems;
		int frameCounter;
		int frameIncrement;
		int maxFrames = 5;
		int duration = 500;
		Timer animationTimer;
		PointF scaleCenter;
		ContainerNode toUnfold;
		RectangleF ctrBounds;
	}
}