using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using Orienteering.Standard;

namespace Orienteering.Competition {
    public abstract class Object {
        internal Orienteering.Data.Object data;

        public double X {
            get { return data.X; }
            set { data.X = value; }
        }

        public double Y {
            get { return data.Y; }
            set { data.Y = value; }
        }

        public int Number {
            get { return data.Number; }
            set { data.Number = value; }
        }

        public Stage Stage {
            get { return new Stage(data.Stage); }
            set { data.Stage = value.data; }
        }

        public Object() {
        }

        public Object(Orienteering.Data.Object _data) {
            data = _data;
        }

        public Object(double _x, double _y, int _number, Stage _stage) {
            data = new Orienteering.Data.Object(_x, _y, _number, _stage.data);
        }

        public abstract XmlNode XmlRef(XmlDocument xml);

        public XmlNode Xml(XmlDocument xml) {
            XmlNode res = XmlRef(xml);
            XmlAttribute XAttr = (XmlAttribute)xml.CreateNode("attribute", "X", "");
            XAttr.Value = X.ToString();
            res.Attributes.Append(XAttr);
            XmlAttribute YAttr = (XmlAttribute)xml.CreateNode("attribute", "Y", "");
            YAttr.Value = Y.ToString();
            res.Attributes.Append(YAttr);
            return res;
        }

        public Object(XmlNode xml, Stage _stage) {
            data = new Orienteering.Data.Object();
            Set(xml, _stage);
        }

        protected void Set(XmlNode xml, Stage _stage) {
            foreach (XmlAttribute xmlattr in xml.Attributes) {
                if (xmlattr.Name == "X")
                    X = Convert.ToDouble(xmlattr.Value);
                if (xmlattr.Name == "Y")
                    Y = Convert.ToDouble(xmlattr.Value);
                if (xmlattr.Name == "number")
                    Number = Convert.ToInt32(xmlattr.Value);
            }
            Stage = _stage;
        }
    }

    public class Control : Object {
        public Control (double _x, double _y, int _number, Stage _stage) {
            data = new Orienteering.Data.Control(_x, _y, _number, _stage.data);
        }

        public Control(Orienteering.Data.Control _data) {
            data = _data;
        }

        public override XmlNode XmlRef(XmlDocument xml) {
            XmlNode res = xml.CreateNode("element", "control", "");
            XmlAttribute numberAttr = (XmlAttribute)xml.CreateNode("attribute", "number", "");
            numberAttr.Value = Number.ToString();
            res.Attributes.Append(numberAttr);
            return res;
        }

        public Control(XmlNode xml, Stage _stage) {
            data = new Orienteering.Data.Control();
            Set(xml, _stage);
        }
    }

    public class Start : Object {
        public Start(double _x, double _y, int _number, Stage _stage) {
            data = new Orienteering.Data.Start(_x, _y, _number, _stage.data);
        }

        public Start(Orienteering.Data.Start _data) {
            data = _data;
        }

        public override XmlNode XmlRef(XmlDocument xml) {
            XmlNode res = xml.CreateNode("element", "start", "");
            XmlAttribute numberAttr = (XmlAttribute)xml.CreateNode("attribute", "number", "");
            numberAttr.Value = Number.ToString();
            res.Attributes.Append(numberAttr);
            return res;
        }

        public Start(XmlNode xml, Stage _stage) {
            data = new Orienteering.Data.Start();
            Set(xml, _stage);
        }
    }

    public class Finish : Object {
        public Finish(double _x, double _y, int _number, Stage _stage) {
            data = new Orienteering.Data.Finish(_x, _y, _number, _stage.data);
        }

        public Finish(Orienteering.Data.Finish _data) {
            data = _data;
        }

        public override XmlNode XmlRef(XmlDocument xml) {
            XmlNode res = xml.CreateNode("element", "finish", "");
            XmlAttribute numberAttr = (XmlAttribute)xml.CreateNode("attribute", "number", "");
            numberAttr.Value = Number.ToString();
            res.Attributes.Append(numberAttr);
            return res;
        }

        public Finish(XmlNode xml, Stage _stage) {
            data = new Orienteering.Data.Finish();
            Set(xml, _stage);
        }
    }

    public class Course {
        internal Orienteering.Data.Course data;

        public Course(string _name, Stage _stage) {
            data = new Orienteering.Data.Course(_name, _stage.data);
        }

        public Course(XmlNode xml, Stage _stage, DataBase dataBase) {
            data = new Orienteering.Data.Course();
            foreach (XmlAttribute xmlattr in xml.Attributes)
                if (xmlattr.Name == "name")
                    Name = xmlattr.Value;
            Stage = _stage;

            List<XmlNode> categ = new List<XmlNode>();
            foreach (XmlNode node in xml.ChildNodes) {
                int tmp = 0;
                foreach (XmlAttribute xmlattr in node.Attributes)
                    if (xmlattr.Name == "number")
                        tmp = Convert.ToInt32(xmlattr.Value);
                if (node.Name == "start") 
                    AddObject(new Start(data.Stage.Start(tmp, dataBase.data)));
                if (node.Name == "control")
                    AddObject(new Control(data.Stage.Control(tmp, dataBase.data)));
                if (node.Name == "finish")
                    AddObject(new Finish(data.Stage.Finish(tmp, dataBase.data)));

                if (node.Name == "category")
                    categ.Add(node);
            }

            try {
                Stage.Course(Name, dataBase);
                throw new AlreadyExists();
            }
            catch (DoesntExist) {
                this.Save(dataBase);
            }

            foreach (XmlNode node in categ)
                if (node.Name == "category")
                    new Category(node, this, dataBase);
        }

        public Course(Orienteering.Data.Course _data) {
            data = _data;
        }

        public string Name {
            get { return data.Name; }
            set { data.Name = value; }
        }

        public Stage Stage {
            get { return new Stage(data.Stage); }
            set { data.Stage = value.data; }
        }

        public List<Object> Objects {
            get {
                List<Object> res = new List<Object>();
                foreach (Orienteering.Data.Object obj in data.Objects) {
                    if (obj is Orienteering.Data.Start)
                        res.Add(new Start(obj as Orienteering.Data.Start));
                    if (obj is Orienteering.Data.Control)
                        res.Add(new Control(obj as Orienteering.Data.Control));
                    if (obj is Orienteering.Data.Finish)
                        res.Add(new Finish(obj as Orienteering.Data.Finish));
                }
                return res;
            }
        }

        private double hypot(double a, double b) {
            return Math.Sqrt(a*a+b*b);
        }

        public double Length {
            get {
                List<Object> tmp = Objects;
                double res=0;
                for (int i=0; i+1<tmp.Count; i++)
                    res += hypot(tmp[i].X-tmp[i+1].X, tmp[i].Y-tmp[i+1].Y); 
                return res;
            }
        }

        public List<Category> Categories(DataBase dataBase) {
            List<Category> res = new List<Category>();
            foreach (Orienteering.Data.Category cat in data.Categories(dataBase.data))
                res.Add(new Category(cat));
            return res;
        }

        public Category Category(string name, DataBase dataBase) {
            return new Category(data.Category(name, dataBase.data));
        }

        public void AddObject(Object x) {
            data.AddObject(x.data);
        }

        public void Save(DataBase dataBase) {
            data.Save(dataBase.data);
        }

        public XmlNode Xml(XmlDocument xml, DataBase dataBase) {
            XmlNode res = xml.CreateNode("element", "course", "");
            XmlAttribute nameAttr = (XmlAttribute)xml.CreateNode("attribute", "name", "");
            nameAttr.Value = Name;
            res.Attributes.Append(nameAttr);

            foreach (Object obj in Objects)
                res.AppendChild(obj.XmlRef(xml));
            foreach (Category cat in Categories(dataBase))
                res.AppendChild(cat.Xml(xml));
            return res;
        }
    }
}
