using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Drawing;

namespace FamilyTree
{
    public class DrawableTreeNode<T> where T : IDrawable
    {
        // The data.
        public T Data;

        // Child nodes in the tree.
        public List<DrawableTreeNode<T>> Children = new List<DrawableTreeNode<T>>();

        // Space to skip horizontally between siblings
        // and vertically between generations.
        private const float Hoffset = 10;
        private const float Voffset = 30;

        // The node's location after arranging.
        private PointF Center;

        // Constructor.
        public DrawableTreeNode(T data)
        {
            Data = data;
        }

        // Add a DrawableTreeNode to out Children list.
        public void AddChild(DrawableTreeNode<T> child)
        {
            Children.Add(child);
        }

        // Set the node's size and the size of its children.
        public void SetSizes(Graphics gr, Font font)
        {
            // Set the node's size.
            Data.SetSize(gr, font);

            // Set the children's sizes.
            foreach (DrawableTreeNode<T> child in Children)
                child.SetSizes(gr, font);
        }

        // Arrange the node and its children in the allowed area.
        // Set xmin to indicate the right edge of our subtree.
        // Set ymin to indicate the bottom edge of our subtree.
        public void Arrange(ref float xmin, ref float ymin)
        {
            // See how big this node is.
            SizeF mySize = Data.Size;

            // Recursively arrange our children,
            // allowing room for this node.
            float x = xmin;
            float biggestYmin = ymin + mySize.Height;
            float subtreeYmin = ymin + mySize.Height + Voffset;
            foreach (DrawableTreeNode<T> child in Children)
            {
                // Arrange this child's subtree.
                float childYmin = subtreeYmin;
                child.Arrange(ref x, ref childYmin);

                // See if this increases the biggest ymin value.
                if (biggestYmin < childYmin) biggestYmin = childYmin;

                // Allow room before the next sibling.
                x += Hoffset;
            }

            // Remove the spacing after the last child.
            if (Children.Count > 0) x -= Hoffset;

            // See if this node is wider than the subtree under it.
            float subtreeWidth = x - xmin;
            if (mySize.Width > subtreeWidth)
            {
                // Center the subtree under this node.
                // Make the children rearrange themselves
                // moved to center their subtrees.
                x = xmin + (mySize.Width - subtreeWidth) / 2;
                foreach (DrawableTreeNode<T> child in Children)
                {
                    // Arrange this child's subtree.
                    child.Arrange(ref x, ref subtreeYmin);

                    // Allow room before the next sibling.
                    x += Hoffset;
                }

                // The subtree's width is this node's width.
                subtreeWidth = mySize.Width;
            }

            // Set this node's center position.
            Center = new PointF(
                xmin + subtreeWidth / 2,
                ymin + mySize.Height / 2);

            // Increase xmin to allow room for
            // the subtree before returning.
            xmin += subtreeWidth;

            // Set the return value for ymin.
            ymin = biggestYmin;
        }

        // Draw the subtree rooted at this node.
        public void DrawTree(Graphics gr, Pen pen, Brush bgBrush, Brush textBrush, Font font)
        {
            // Draw the links.
            DrawSubtreeLinks(gr, pen);

            // Draw the nodes.
            DrawSubtreeNodes(gr, pen, bgBrush, textBrush, font);
        }

        // Draw the links for the subtree rooted at this node.
        private void DrawSubtreeLinks(Graphics gr, Pen pen)
        {
            // See if we have 1 child.
            if (Children.Count == 1)
            {
                // Just connect the centers.
                gr.DrawLine(pen, Center, Children[0].Center);
            }
            else if (Children.Count > 1)
            {
                // Draw a horizontal line above the children.
                float xmin = Children[0].Center.X;
                float xmax = Children[Children.Count - 1].Center.X;
                SizeF mySize = Data.Size;
                float y = Center.Y + mySize.Height / 2 + Voffset / 2f;
                gr.DrawLine(pen, xmin, y, xmax, y);

                // Draw the vertical line from the parent
                // to the horizontal line.
                gr.DrawLine(pen, Center.X, Center.Y, Center.X, y);

                // Draw lines from the horizontal line to the children.
                foreach (DrawableTreeNode<T> child in Children)
                {
                    gr.DrawLine(pen,
                        child.Center.X, y,
                        child.Center.X, child.Center.Y);
                }
            }

            // Recursively make the children draw their subtrees.
            foreach (DrawableTreeNode<T> child in Children)
            {
                child.DrawSubtreeLinks(gr, pen);
            }
        }

        // Draw the nodes for the subtree rooted at this node.
        private void DrawSubtreeNodes(Graphics gr, Pen pen, Brush bgBrush, Brush textBrush, Font font)
        {
            // Draw this node.
            Data.Draw(Center, gr, pen, bgBrush, textBrush, font);

            // Recursively make the child draw its subtree nodes.
            foreach (DrawableTreeNode<T> child in Children)
            {
                child.DrawSubtreeNodes(gr, pen, bgBrush, textBrush, font);
            }
        }

        // Return the DrawableTreeNode at this point (or null if there isn't one there).
        public DrawableTreeNode<T> NodeAtPoint(PointF point)
        {
            // See if the point is under this node.
            if (Data.IsAtPoint(Center, point)) return this;

            // See if the point is under a node in the subtree.
            foreach (DrawableTreeNode<T> child in Children)
            {
                DrawableTreeNode<T> hitNode = child.NodeAtPoint(point);
                if (hitNode != null) return hitNode;
            }

            return null;
        }

        // Delete a target node from this node's subtree.
        // Return true if we delete the node.
        public bool DeleteNode(DrawableTreeNode<T> target)
        {
            // See if the target is in our subtree.
            foreach (DrawableTreeNode<T> child in Children)
            {
                // See if it's the child.
                if (child == target)
                {
                    // Delete this child.
                    Children.Remove(child);
                    return true;
                }

                // See if it's in the child's subtree.
                if (child.DeleteNode(target)) return true;
            }

            // It's not in our subtree.
            return false;
        }
    }
}
