using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;


using CategoryTheory;

namespace DiagramUI
{
    /// <summary>
    /// Desktop for serialization
    /// </summary>
    public class PureDesktopPeer : PureDesktop
    {
        #region Fields

        /// <summary>
        /// Parent container
        /// </summary>
        private ObjectContainer parent;

        /// <summary>
        /// List of exceptios
        /// </summary>
        private static List<Exception> exceptions;

        /// <summary>
        /// Error handler
        /// </summary>
        private static IErrorHandler errorHandler = null;

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor
        /// </summary>
        public PureDesktopPeer()
        {
        }


        #endregion

        #region IDesktop Members


        public override void Copy(ArrayList objects, ArrayList arrows, bool associated)
        {
            PureObjectLabel.Wrappers = true;
            foreach (IObjectLabel l in objects)
            {
                IObjectLabel lab = new PureObjectLabelPeer(l.Name, l.Kind, l.Type, l.X, l.Y);
                lab.Object = l.Object;
                lab.Desktop = this;
                this.objects.Add(lab);
                components.Add(lab);
                table[l.Name] = lab;
            }
            PureObjectLabel.Wrappers = false;
            foreach (IArrowLabel l in arrows)
            {
                IArrowLabel lab = new PureArrowLabelPeer(l.Name, l.Kind, l.Type, l.X, l.Y);
                lab.Arrow = l.Arrow;
                lab.Desktop = this;
                this.arrows.Add(lab);
                components.Add(lab);
                table[l.Name] = lab;
                lab.Source = Find(objects, this.objects, l.Source, l.Desktop);
                lab.Target = Find(objects, this.objects, l.Target, l.Desktop);
            }
            if (!associated)
            {
                return;
            }
            SetParents(this);
            PureObjectLabel.SetLabels(objects);
            PureArrowLabel.SetLabels(arrows);
            PureObjectLabel.Wrappers = true;
        }



        public override INamedComponent this[string name]
        {
            get
            {
                INamedComponent nc = base[name];
                if (nc != null)
                {
                    return nc;
                }
                nc = GetFromRoot(this, name);
                if (nc != null)
                {
                    return nc;
                }
                nc = GetChild(this, name);
                if (nc != null)
                {
                    return nc;
                }
                return null;
            }
        }

        /// <summary>
        /// Root desktop
        /// </summary>
        public override IDesktop Root
        {
            get
            {
                IDesktop d = parentDesktop;
                if (d == null)
                {
                    return this;
                }
                return d.Root;
            }
        }



        #endregion

        #region Specific members

        static public IErrorHandler ErrorHandler
        {
            get { return errorHandler; }
            set { errorHandler = value; }
        }

        /// <summary>
        /// All components
        /// </summary>
        public override ICollection AllComponents
        {
            get
            {
                return GetAllObjects(this);
            }
        }

        /// <summary>
        /// Checks desktop
        /// </summary>
        /// <param name="desktop">The desktop</param>
        /// <param name="binders">The serialization binders</param>
        /// <returns>List of exceptions or null if right</returns>
        public static List<Exception> Check(IDesktop desktop, SerializationBinder[] binders)
        {
            exceptions = new List<Exception>();
            bool b = false;
            try
            {
                ArrayList objects = desktop.Objects;
                ArrayList arrows = desktop.Arrows;
                PureDesktopPeer d = new PureDesktopPeer();
                d.Copy(objects, arrows, true);
                SetParents(d);
                Stream stream = new MemoryStream();
                d.Save(stream);
                PureDesktopPeer dnew = new PureDesktopPeer();
                b = dnew.Load(stream, null);
            }
            catch (Exception e)
            {
                if (exceptions != null)
                {
                    exceptions.Add(e);
                }
                else
                {
                    if (errorHandler != null)
                    {
                        errorHandler.ShowError(e);
                    }
                }
            }
            List<Exception> res = b ? null : exceptions;
            exceptions = null;
            SetParents(desktop);
            return res;
        }




        /// <summary>
        /// Saves itself
        /// </summary>
        /// <param name="stream">Stream to save</param>
        public void Save(Stream stream)
        {
            SetParents(this);
            PureObjectLabel.Wrappers = true;
            PureObjectLabel.SetType(objects);
            PureArrowLabel.SetType(arrows);
            PureObjectLabel.Wrappers = false;
            foreach (IArrowLabel label in arrows)
            {
                if (objects.Contains(label.Source))
                {
                    label.SourceNumber = objects.IndexOf(label.Source);
                }
                else
                {
                    IObjectLabel ls = label.Source.GetRoot(this) as IObjectLabel;
                    int sn = objects.IndexOf(ls);
                    ObjectContainer scont = ls.Object as ObjectContainer;
                    string ns = scont.GetName(label.Source);
                    label.SourceNumber = new object[] { sn, ns };
                }
                if (objects.Contains(label.Target))
                {
                    label.TargetNumber = objects.IndexOf(label.Target);
                }
                else
                {
                    IObjectLabel lt = label.Target.GetRoot(this) as IObjectLabel;
                    int tn = objects.IndexOf(lt);
                    ObjectContainer tcont = lt.Object as ObjectContainer;
                    string nt = tcont.GetName(label.Target);
                    label.TargetNumber = new object[] { tn, nt };
                }
            }
            BinaryFormatter bformatter = new BinaryFormatter();
            PureObjectLabel.Wrappers = true;
            bformatter.Serialize(stream, Objects);
            bformatter.Serialize(stream, Arrows);
            PureObjectLabel.Wrappers = false;

        }

        /// <summary>
        /// Refreshes itself
        /// </summary>
        public void Refresh()
        {
            MemoryStream stream = new MemoryStream();
            Save(stream);
            objects.Clear();
            arrows.Clear();
            //Load(stream, null);
        }

        public static ICollection GetAllObjects(IDesktop desktop)
        {
            ArrayList l = new ArrayList();
            ICollection components = desktop.Components;
            l.AddRange(components);
            ICollection objs = desktop.Objects;
            foreach (IObjectLabel o in objs)
            {
                if (o.Object is ObjectContainer)
                {
                    ObjectContainer oc = o.Object as ObjectContainer;
                    l.AddRange(oc.AllObjects);
                }
            }
            return l;
        }


        public bool Load(Stream stream, SerializationBinder[] binders)
        {
            if (!loadBinders(stream, binders))
            {
                return false;
            }
            return postDeserialize();
        }



        public static void SetParents(IDesktop desktop)
        {
            ICollection objects = desktop.Objects;
            foreach (IObjectLabel ol in objects)
            {
                if (ol.Object is ObjectContainer)
                {
                    ObjectContainer oc = ol.Object as ObjectContainer;
                    oc.SetParents(desktop);
                }
            }
        }

        public bool PostLoad()
        {
            try
            {
                components.AddRange(objects);
                components.AddRange(arrows);
                PureObjectLabel.SetLabels(objects);
                PureArrowLabel.SetLabels(arrows);
                SetParents(this);
                foreach (INamedComponent nc in components)
                {
                    nc.Desktop = this;
                    table[nc.Name] = nc;
                    if (nc is IObjectLabel)
                    {
                        IObjectLabel ol = nc as IObjectLabel;
                        if (ol.Object is ObjectContainer)
                        {
                            ObjectContainer oc = ol.Object as ObjectContainer;
                            oc.PostLoad();
                        }
                    }
                }
                foreach (IArrowLabel arrow in arrows)
                {
                    IObjectLabel source = null;
                    IObjectLabel target = null;
                    if (arrow.SourceNumber is Int32)
                    {
                        source = objects[(int)arrow.SourceNumber] as IObjectLabel;
                    }
                    else
                    {
                        object[] os = arrow.SourceNumber as object[];
                        IObjectLabel so = objects[(int)os[0]] as IObjectLabel;
                        ObjectContainer sc = so.Object as ObjectContainer;
                        source = sc[os[1] as string] as IObjectLabel;
                    }
                    if (arrow.TargetNumber is Int32)
                    {
                        target = objects[(int)arrow.TargetNumber] as IObjectLabel;
                    }
                    else
                    {
                        object[] ot = arrow.TargetNumber as object[];
                        IObjectLabel to = objects[(int)ot[0]] as IObjectLabel;
                        ObjectContainer tc = to.Object as ObjectContainer;
                        target = tc[ot[1] as string] as IObjectLabel;
                    }
                    arrow.Arrow.Source = source.Object;
                    arrow.Arrow.Target = target.Object;
                    arrow.Source = source;
                    arrow.Target = target;
                    IAssociatedObject ass = arrow.Arrow as IAssociatedObject;
                    ass.Object = arrow;
                }
                return true;
            }
            catch (Exception ex)
            {
                if (exceptions != null)
                {
                    exceptions.Add(ex);
                }
                else
                {
                    if (errorHandler != null)
                    {
                        errorHandler.ShowError(ex);
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Gets full list of desktop components
        /// </summary>
        /// <param name="desktop">The desktop</param>
        /// <param name="list">The list</param>
        static public void GeFullList(IDesktop desktop, List<INamedComponent> list)
        {
            ICollection l = desktop.AllComponents;
            foreach (INamedComponent c in l)
            {
                list.Add(c);
                IAssociatedObject ao = c as IAssociatedObject;
                if (ao.Object is ObjectContainer)
                {
                    ObjectContainer cont = ao.Object as ObjectContainer;
                    IDesktop d = cont.Desktop;
                    GeFullList(d, list);
                }
            }
        }

        public IAlias FindAlias(IAssociatedObject ob, string name)
        {
            INamedComponent c = null;
            IDesktop desktop = c.Desktop;
            IDesktop root = desktop.Root;
            List<INamedComponent> l = new List<INamedComponent>();
            return null;
        }




        internal IDesktop parentDesktop
        {
            get
            {
                if (parent == null)
                {
                    return null;
                }
                IAssociatedObject obj = parent as IAssociatedObject;
                INamedComponent nc = obj.Object as INamedComponent;
                return nc.Desktop;
            }
        }

        internal bool postDeserialize()
        {
            try
            {
                foreach (IArrowLabel arrow in arrows)
                {
                    IAssociatedObject ass = arrow.Arrow as IAssociatedObject;
                    ass.Object = arrow;
                    if (arrow.Arrow is IPostSetArrow)
                    {
                        IPostSetArrow post = arrow.Arrow as IPostSetArrow;
                        post.PostSetArrow();
                    }

                }
                foreach (IObjectLabel lab in objects)
                {
                    object obj = lab.Object;
                    if (obj is IPostSetArrow)
                    {
                        IPostSetArrow post = obj as IPostSetArrow;
                        post.PostSetArrow();
                    }
                }
                foreach (IArrowLabel lab in arrows)
                {
                    object obj = lab.Arrow;
                    if (obj is IPostSerialize)
                    {
                        IPostSerialize post = obj as IPostSerialize;
                        post.PostSerialize();
                    }
                }
                return true;
            }
            catch (Exception ex)
            {
                if (exceptions != null)
                {
                    exceptions.Add(ex);
                }
                else
                {
                    if (errorHandler != null)
                    {
                        errorHandler.ShowError(ex);
                    }
                }
            }
            return false;
        }


        internal void setParent(ObjectContainer parent)
        {
            this.parent = parent;
        }

        internal bool loadBinders(Stream stream, SerializationBinder[] binders)
        {
            if (load(stream, null))
            {
                return true;
            }
            if (binders == null)
            {
                return false;
            }
            ObjectContainer.Binders = binders;
            foreach (SerializationBinder binder in binders)
            {
                if (load(stream, binder))
                {
                    return true;
                }
            }
            return false;
        }




        private bool load(Stream stream, SerializationBinder binder)
        {
            stream.Position = 0;
            BinaryFormatter bformatter = new BinaryFormatter();
            if (binder != null)
            {
                bformatter.Binder = binder;
            }
            try
            {
                PureObjectLabel.Wrappers = true;
                objects = bformatter.Deserialize(stream) as ArrayList;
                arrows = bformatter.Deserialize(stream) as ArrayList;
                if (objects == null)
                {
                    objects = new ArrayList();
                }
                if (arrows == null)
                {
                    arrows = new ArrayList();
                }
                PureObjectLabel.Wrappers = false;
                return PostLoad();
            }
            catch (Exception ex)
            {
                if (exceptions != null)
                {
                    exceptions.Add(ex);
                }
                else
                {
                    if (errorHandler != null)
                    {
                        errorHandler.ShowError(ex);
                    }
                }
            }
            return false;

        }

        static public INamedComponent GetChild(IDesktop desktop, string name)
        {
            ICollection c = desktop.Objects;
            foreach (IObjectLabel l in c)
            {
                object ob = l.Object;
                if (!(ob is ObjectContainer))
                {
                    continue;
                }
                ObjectContainer cont = ob as ObjectContainer;
                ICollection all = cont.AllObjects;
                foreach (INamedComponent nc in all)
                {
                    string n = nc.GetName(desktop);
                    if (n.Equals(name))
                    {
                        return nc;
                    }
                }
            }
            return null;
        }

        static public INamedComponent GetFromRoot(IDesktop desktop, string name)
        {
            if (name.Length >= 3)
            {
                if (name.Substring(0, 3).Equals("../"))
                {
                    PureDesktopPeer d = desktop as PureDesktopPeer;
                    ObjectContainer cont = d.parent;
                    INamedComponent cnc = cont.Object as INamedComponent;
                    INamedComponent nc = cnc.Desktop[name.Substring(3)];
                    if (nc != null)
                    {
                        return nc;
                    }
                }
            }
            return null;
        }

        static public IObjectLabel Find(ArrayList source, ArrayList target, IObjectLabel label, IDesktop desktop)
        {
            if (source.Contains(label))
            {
                int n = source.IndexOf(label);
                return target[n] as IObjectLabel;
            }
            IObjectLabel ol = label.GetRoot(desktop) as IObjectLabel;
            int nt = source.IndexOf(ol);
            ObjectContainer oct = ol.Object as ObjectContainer;
            string tn = oct.GetName(label);
            IObjectLabel ot = target[nt] as IObjectLabel;
            ObjectContainer ct = ot.Object as ObjectContainer;
            return ct[tn] as IObjectLabel;
        }

        #endregion
    }
}
