// adopted from: https://github.com/mono/gtk-sharp/commits/2.99.3/sample/PolarFixed.cs
// This is a completely pointless widget, but it shows how to subclass container...

using System;
using System.Collections.Generic;
using Gtk;
using Gdk;

namespace Samples
{
	class PolarFixed : Container
	{
		IList<PolarFixedChild> children;

		public PolarFixed()
		{
			children = new List<PolarFixedChild>();
			HasWindow = false;
		}

		// The child properties object
		public class PolarFixedChild : Container.ContainerChild
		{
			double theta;
			uint r;

			public PolarFixedChild(PolarFixed parent, Widget child, double theta, uint r) : base(parent, child)
			{
				this.theta = theta;
				this.r = r;
			}

			// We call parent.QueueResize() from the property setters here so that you
			// can move the widget around just by changing its child properties (just
			// like with a native container class).

			public double Theta {
				get { return theta; }
				set {
					theta = value;
					parent.QueueResize();
				}
			}

			public uint R {
				get { return r; }
				set {
					r = value;
					parent.QueueResize();
				}
			}
		}

		// Override the child properties accessor to return the right object from
		// "children".
		public override ContainerChild this[Widget w] {
			get {
				foreach (PolarFixedChild pfc in children) {
					if (pfc.Child == w)
						return pfc;
				}

				return null;
			}
		}

		// Indicate the kind of children the container will accept. Most containers
		// will accept any kind of child, so they should return Gtk.Widget.GType.
		// The default is "GLib.GType.None", which technically means that no (new)
		// children can be added to the container, though Container.Add does not
		// enforce this.
		protected override GLib.GType OnChildType()
		{
			return Gtk.Widget.GType;
		}

		// Implement gtk_container_forall(), which is also used by
		// Gtk.Container.Children and Gtk.Container.AllChildren.
		protected override void ForAll(bool include_internals, Callback callback)
		{
			base.ForAll(include_internals, callback);
			foreach (PolarFixedChild pfc in children)
				callback(pfc.Child);
		}

		// Invoked by Container.Add (w). It's good practice to have this do *something*,
		// even if it's not something terribly useful.
		protected override void OnAdded(Widget w)
		{
			Put(w, 0.0, 0);
		}

		// our own adder method
		public void Put(Widget w, double theta, uint r)
		{
			children.Add(new PolarFixedChild(this, w, theta, r));
			w.Parent = this;
			QueueResize();
		}

		public void Move(Widget w, double theta, uint r)
		{
			PolarFixedChild pfc = (PolarFixedChild) this[w];
			if (pfc != null) {
				pfc.Theta = theta;
				pfc.R = r;
			}
		}

		// invoked by Container.Remove (w)
		protected override void OnRemoved(Widget w)
		{
			PolarFixedChild pfc = (PolarFixedChild) this[w];
			if (pfc != null) {
				pfc.Child.Unparent();
				children.Remove(pfc);
				QueueResize();
			}
		}

		// Handle size request
		protected override void OnGetPreferredHeight(out int minimal_height, out int natural_height)
		{
			Requisition req = new Requisition();
			OnSizeRequested(ref req);
			minimal_height = natural_height = req.Height;
		}

		protected override void OnGetPreferredWidth(out int minimal_width, out int natural_width)
		{
			Requisition req = new Requisition();
			OnSizeRequested(ref req);
			minimal_width = natural_width = req.Width;
		}

		void OnSizeRequested(ref Requisition req)
		{
			int child_width, child_minwidth, child_height, child_minheight;
			int x, y;

			req.Width = req.Height = 0;
			foreach (PolarFixedChild pfc in children) {
				// Recursively request the size of each child
				pfc.Child.GetPreferredWidth(out child_minwidth, out child_width);
				pfc.Child.GetPreferredHeight(out child_minheight, out child_height);

				// Figure out where we're going to put it
				x = (int) (Math.Cos(pfc.Theta) * pfc.R) + child_width / 2;
				y = (int) (Math.Sin(pfc.Theta) * pfc.R) + child_height / 2;

				// Update our own size request to fit it
				if (req.Width < 2 * x)
					req.Width = 2 * x;
				if (req.Height < 2 * y)
					req.Height = 2 * y;
			}

			// Take Container.BorderWidth into account
			req.Width += (int) (2 * BorderWidth);
			req.Height += (int) (2 * BorderWidth);
		}

		// Size allocation. Note that the allocation received may be smaller than what we
		// requested. Some containers will take that into account by giving some or all
		// of their children a smaller allocation than they requested. Other containers
		// (like this one) just let their children get placed partly out-of-bounds if they
		// aren't allocated enough room.
		protected override void OnSizeAllocated(Rectangle allocation)
		{
			Requisition childReq, childMinReq;
			int cx, cy, x, y;

			// This sets the "Allocation" property. For widgets that
			// have a GdkWindow, it also calls GdkWindow.MoveResize()
			base.OnSizeAllocated(allocation);

			// Figure out where the center of the grid will be
			cx = allocation.X + (allocation.Width / 2);
			cy = allocation.Y + (allocation.Height / 2);

			foreach (PolarFixedChild pfc in children) {
				pfc.Child.GetPreferredSize(out childMinReq, out childReq);

				x = (int) (Math.Cos(pfc.Theta) * pfc.R) - childReq.Width / 2;
				y = (int) (Math.Sin(pfc.Theta) * pfc.R) + childReq.Height / 2;

				allocation.X = cx + x;
				allocation.Width = childReq.Width;
				allocation.Y = cy - y;
				allocation.Height = childReq.Height;

				pfc.Child.SizeAllocate(allocation);
			}
		}
	}
}