//*************************************************************
//  Filename:       XuiWriter.cs
//  Author:         MaesterRowen & Wondro (TeamFSD)
//  Date:           August 10, 2013
//  Description:    Class used to write XUI XML files.
//*************************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Collections;
using System.IO;

namespace XuiWorkshop
{
    class XuiWriter
    {
        XmlTextWriter xml;
        PROCESS_FLAGS processFlag;
        List<XUIELEM_PROP_DEF> ignoredProps; // Used for XuiToolVersion

        public void BuildXui(string savePath, XUIOBJECTDATA baseObject, PROCESS_FLAGS processFlags)
        {
            processFlag = new PROCESS_FLAGS();
            processFlag.useXam = processFlags.useXam;
            processFlag.xuiToolVersion = processFlags.xuiToolVersion;
            processFlag.useAnimations = processFlags.useAnimations;
            processFlag.extFile = processFlags.extFile;

            bool childHasExData = false;

            ignoredProps = new List<XUIELEM_PROP_DEF>();
            ignoredProps = XuiClass.Instance.GetIgnoredPropArray();

            using (xml = new XmlTextWriter(savePath, null))
            {
                xml.WriteStartElement("XuiCanvas");
                xml.WriteAttributeString("version", "000c"); xml.WriteRaw(Environment.NewLine);
                xml.WriteStartElement("Properties"); xml.WriteRaw(Environment.NewLine);         // XuiCanvas <Properties>
                foreach (XUIPROPERTYDATA prop in baseObject.PropertyArray)
                {
                    xml.WriteStartElement(prop.PropDef.PropName);                               // Start Prop Item
                    xml.WriteValue(prop.PropValue.ToString());
                    xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine);                   // End Prop Item
                }
                xml.WriteEndElement();                                                          // End XuiCanvas </Properties>

                // Write all Canvas's Children
                foreach (XUIOBJECTDATA child in baseObject.ChildrenArray)
                {
                    if (RecursiveWrite(child, ref baseObject))
                        childHasExData = true;
                }

                // Animation
                if (processFlag.useAnimations)
                    WriteAnimation(baseObject, childHasExData);

                xml.WriteEndElement();                                                            // End XuiCanvas

                xml.Flush();
                xml.Close();
            }

            // We didnt write any ext data remove the file
            //if (!Global.writeExtFile && processFlag.extFile)
                //File.Delete(savePath);
        }

        // Recursive Methods
        public bool RecursiveWrite(XUIOBJECTDATA obj, ref XUIOBJECTDATA parent)
        {
            bool hasDataChunk = false;
            bool childHasExData = false;

            if (processFlag.useXam)
                xml.WriteStartElement(ConvertPropToXam(obj.ClassName));
            else
                xml.WriteStartElement(obj.ClassName);

            xml.WriteRaw(Environment.NewLine);                                                   // Start Class Item
            xml.WriteStartElement("Properties"); xml.WriteRaw(Environment.NewLine);              // Class <Properties>

            List<XUIPROPERTYDATA> IgnoredPropList = obj.PropertyArray.FindAll(x => Array.IndexOf<XUIELEM_PROP_DEF>(ignoredProps.ToArray(), x.PropDef) > -1);
            List<XUIPROPERTYDATA> nonIgnoredList = obj.PropertyArray.FindAll(x => Array.IndexOf<XUIELEM_PROP_DEF>(ignoredProps.ToArray(), x.PropDef) == -1);

            if (processFlag.xuiToolVersion && IgnoredPropList.Count > 0)
            {
                hasDataChunk = true;
                Global.writeExtFile = true;
            }

            if (processFlag.xuiToolVersion && processFlag.extFile)
            {
                foreach (XUIPROPERTYDATA prop in IgnoredPropList) { WriteProperty(prop); }    // Only write ignored props
                WriteProperty(nonIgnoredList.Find(x => x.PropDef.PropName == "Id"));          // Write Id for identifying objects
            }
            else if (processFlag.xuiToolVersion && !processFlag.extFile)
                foreach (XUIPROPERTYDATA prop in nonIgnoredList) { WriteProperty(prop); }     // Only write safe props
            else if (!processFlag.xuiToolVersion)
                foreach (XUIPROPERTYDATA prop in obj.PropertyArray) { WriteProperty(prop); }  // Write All props


            xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine);                         // End Class </Properties>


            // Write Children
            if (obj.ChildrenArray.Count > 0)
            {
                foreach (XUIOBJECTDATA child in obj.ChildrenArray)
                {
                    if (RecursiveWrite(child, ref obj))
                        childHasExData = true;
                }
            }

            // Animation
            if(processFlag.useAnimations)
                WriteAnimation(obj, childHasExData);

            xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine);                               // End Class Item

            return hasDataChunk;
        }

        public void WriteProperty(XUIPROPERTYDATA prop)
        {
            xml.WriteStartElement(prop.PropDef.PropName);                                                                          // Start Prop Item
            if (prop.Flags == 0x1) xml.WriteAttributeString("index", prop.Index.ToString());

            if (prop.PropType == XUIELEM_PROP_TYPE.XUI_EPT_OBJECT)
            {
                List<XUIPROPERTYDATA> child = (List<XUIPROPERTYDATA>)prop.PropValue;
                RecursivePropWrite(child);
            }
            else
            {
                if (prop.PropDef.PropName == "ImagePath" || prop.PropDef.PropName == "TextureFileName")
                    xml.WriteValue(FilterImagePaths(prop.PropValue.ToString()));
                else
                    xml.WriteValue(prop.PropValue.ToString());
            }

            xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine);                                                              // End Prop Item
        }

        public void RecursivePropWrite(List<XUIPROPERTYDATA> subProps)
        {
            xml.WriteStartElement("Properties"); xml.WriteRaw(Environment.NewLine);                // Start Properties

            foreach (XUIPROPERTYDATA tmpProp in subProps)
            {
                xml.WriteStartElement(tmpProp.PropDef.PropName);  // Start Sub Prop
                if (tmpProp.Flags == 0x1) xml.WriteAttributeString("index", tmpProp.Index.ToString());

                if (tmpProp.PropType == XUIELEM_PROP_TYPE.XUI_EPT_OBJECT)
                {
                    List<XUIPROPERTYDATA> child = (List<XUIPROPERTYDATA>)tmpProp.PropValue;
                    RecursivePropWrite(child);
                }
                else
                    xml.WriteValue(tmpProp.PropValue.ToString());

                xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine);                          // End Sub Prop
            }
            xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine);                              // End Properties
        }


        // Animation Methods
        private void WriteAnimation(XUIOBJECTDATA obj, bool childHasExData)
        {
            // Check if we have timeline information to write
            if (obj.NumNamedFrames > 0 || obj.NumSubTimelines > 0)
            {
                xml.WriteStartElement("Timelines"); xml.WriteRaw(Environment.NewLine);

                // Named Frames if this isn't a ext call
                WriteNamedFrames(obj.NamedFrameArray);

                // Timeline
                foreach (XUISUBTIMELINEDATA subtimeline in obj.SubTimelines)
                {
                    WriteSubTimeline(subtimeline, ref obj, childHasExData);
                }

                xml.WriteEndElement(); // End Timelines
            }
        }

        private void WriteNamedFrames(List<XUINAMEDFRAMEDATA> NamedFrameArray)
        {
            xml.WriteStartElement("NamedFrames"); xml.WriteRaw(Environment.NewLine);
            // Named Frames
            foreach (XUINAMEDFRAMEDATA child in NamedFrameArray)
            {
                xml.WriteStartElement("NamedFrame"); xml.WriteRaw(Environment.NewLine);
                xml.WriteStartElement("Name"); 
                xml.WriteValue(child.Name);
                xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine);// End Name
                xml.WriteStartElement("Time");
                xml.WriteValue(child.Time);
                xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine);// End Time
                xml.WriteStartElement("Command");
                xml.WriteValue(child.Command.ToString(0));
                xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine);// End Command
                if ((int)child.Command >= 2)
                {
                    xml.WriteStartElement("CommandParams");
                    xml.WriteValue(child.Target);
                    xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine);// End CommandParams
                }
                xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine);// End NamedFrame
            }
            xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine);// End NamedFrames
        }

        private void WriteSubTimeline(XUISUBTIMELINEDATA SubTimeLineData, ref XUIOBJECTDATA obj, bool childHasExData)
        {
            xml.WriteStartElement("Timeline"); xml.WriteRaw(Environment.NewLine);

            xml.WriteStartElement("Id"); 
            xml.WriteValue(SubTimeLineData.ElementId);
            xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End Id

            List<int> Index = new List<int>();
            List<XUITIMELINEPROPPATH> IgnoredPropList = new List<XUITIMELINEPROPPATH>();
            List<XUITIMELINEPROPPATH> nonIgnoredList = new List<XUITIMELINEPROPPATH>();

            // Loop through our paths and look to see if it contains an ignored property type
            //  If it does add to array and store its index
            foreach (XUITIMELINEPROPPATH path in SubTimeLineData.PropPathArray)
            {
                foreach (XUIELEM_PROP_DEF subData in path.PropDefArray)
                {
                    int index = Array.IndexOf(ignoredProps.ToArray(), subData);
                    if (index > -1)
                    {
                        IgnoredPropList.Add(path);
                        Index.Add(Array.IndexOf(SubTimeLineData.PropPathArray.ToArray(), path));
                    }
                    else
                    {
                        // Check our current nonIgnored array to make sure this element doesn't already exits
                        //  Cases where you have compount props can cause multiple copies to be saved for each level
                        //  i.e : Fill.FillColor would add 2 copies of its XUITIMELINEPROPPATH to nonIgnored
                        if (Array.IndexOf(nonIgnoredList.ToArray(), path) == -1)
                            nonIgnoredList.Add(path);
                    }
                }
            }

            if (processFlag.xuiToolVersion && processFlag.extFile)  // Write our ignored properties
            {
                foreach (XUITIMELINEPROPPATH propPath in IgnoredPropList) 
                {  
                    xml.WriteStartElement("TimelineProp");
                    xml.WriteAttributeString("index", propPath.Index.ToString());
                    string propName = "";

                    // Build Prop name ex Fill.Gradient.NumStops
                    foreach (XUIELEM_PROP_DEF propDef in propPath.PropDefArray)
                    {
                        propName = propName + propDef.PropName + ".";
                    }
                    propName = propName.Substring(0, propName.Length-1); // Remove trailing '.'
                    xml.WriteValue(propName);
                    xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End TimelineProp 
                }
            }
            else if (processFlag.xuiToolVersion && !processFlag.extFile) // Write our non-ignored properties
            {
                foreach (XUITIMELINEPROPPATH propPath in nonIgnoredList)
                {
                    xml.WriteStartElement("TimelineProp");
                    xml.WriteAttributeString("index", propPath.Index.ToString());
                    string propName = "";

                    // Build Prop name ex Fill.Gradient.NumStops
                    foreach (XUIELEM_PROP_DEF propDef in propPath.PropDefArray)
                    {
                        propName = propName + propDef.PropName + ".";
                    }
                    propName = propName.Substring(0, propName.Length - 1); // Remove trailing '.'
                    xml.WriteValue(propName);
                    xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End TimelineProp 
                }
            }
            else if (!processFlag.xuiToolVersion)  // Write all properties
            {
                foreach (XUITIMELINEPROPPATH propPath in SubTimeLineData.PropPathArray)
                {
                    xml.WriteStartElement("TimelineProp");
                    xml.WriteAttributeString("index", propPath.Index.ToString());
                    string propName = "";

                    // Build Prop name ex Fill.Gradient.NumStops
                    foreach (XUIELEM_PROP_DEF propDef in propPath.PropDefArray)
                    {
                        propName = propName + propDef.PropName + ".";
                    }
                    propName = propName.Substring(0, propName.Length - 1); // Remove trailing '.'
                    xml.WriteValue(propName);
                    xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End TimelineProp
                }
            }

            // Keyframe Data
            WriteKeyframe(SubTimeLineData.KeyframeDataArray, SubTimeLineData.NumPropPaths, Index);

            xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End Timeline
        }

        private void WriteKeyframe(List<XUIKEYFRAMEDATA> KeyframeDataArray, uint numPropPaths, List<int> Index)
        {
            foreach (XUIKEYFRAMEDATA keyFrameData in KeyframeDataArray)
            {
                    xml.WriteStartElement("KeyFrame"); xml.WriteRaw(Environment.NewLine);
                    xml.WriteStartElement("Time");
                    xml.WriteValue(keyFrameData.Frame.ToString());
                    xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End Time

                    xml.WriteStartElement("Interpolation");
                    byte interpType = (byte)keyFrameData.InterpolateType;
                    xml.WriteValue(interpType.ToString());
                    xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End Interpolation

                // Write our ease values if interpolation is 2 (ease)
                if (keyFrameData.Flags == 2)
                {
                    xml.WriteStartElement("EaseIn");
                    xml.WriteValue((int)(sbyte)keyFrameData.EaseIn);
                    xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End easeIn

                    xml.WriteStartElement("EaseOut");
                    xml.WriteValue((int)(sbyte)keyFrameData.EaseOut);
                    xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End easeOut

                    xml.WriteStartElement("EaseScale");
                    xml.WriteValue((int)(sbyte)keyFrameData.EaseScale);
                    xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End easeScale
                }

                // Write Prop Data
                uint propCount = numPropPaths;
                for (uint propIdx = 0; propIdx < propCount; propIdx++)
                {
                    if (processFlag.xuiToolVersion && processFlag.extFile)
                    {
                        if (Index.Count > 0)
                        {
                            if (Array.IndexOf(Index.ToArray(), (int)propIdx) > -1)
                            {
                                xml.WriteStartElement("Prop");
                                XUIPROPERTYDATA propData = keyFrameData.PropList[(int)propIdx];
                                xml.WriteValue(propData.PropValue.ToString());
                                xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End Prop

                                Global.writeExtFile = true;
                            }
                        }
                    }
                    else if (Index.Count > 0 && processFlag.xuiToolVersion && !processFlag.extFile)
                    {
                        if (Array.IndexOf(Index.ToArray(), (int)propIdx) == -1)
                        {
                            xml.WriteStartElement("Prop");
                            XUIPROPERTYDATA propData = keyFrameData.PropList[(int)propIdx];
                            xml.WriteValue(propData.PropValue.ToString());
                            xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End Prop
                        }
                    }
                    else
                    {
                        xml.WriteStartElement("Prop");
                        XUIPROPERTYDATA propData = keyFrameData.PropList[(int)propIdx];
                        xml.WriteValue(propData.PropValue.ToString());
                        xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End Prop
                    }

                }
                
                xml.WriteEndElement(); xml.WriteRaw(Environment.NewLine); // End Keyframe
            }
        }


        // Generic Method
        private string ConvertPropToXam(string className)
        {
            // Check if its a Xui Class if not just return name
            if (className.Substring(0, 3) == "Xui" && (Array.IndexOf(Global.ignoreClasses, className) == -1))
                return ("Xam" + className.Substring(3, className.Length - 3));
            else
                return className;
        }

        private string FilterImagePaths(string path)
        {
            string[] pathType = path.Split(':');

            switch (pathType[0])
            {
                case "xam":
                    if (Array.IndexOf(Log.Instance.sharedresImages.ToArray(), path) == -1)
                        Log.Instance.xamImages.Add(path);
                    return path.Replace("://", "\\");
                case "sharedres":
                    if (Array.IndexOf(Log.Instance.sharedresImages.ToArray(), path) == -1)
                        Log.Instance.sharedresImages.Add(path);
                    return path.Replace("://", "\\");
                case "skin":
                    if (Array.IndexOf(Log.Instance.sharedresImages.ToArray(), path) == -1)
                        Log.Instance.skinImages.Add(path);
                    return path.Replace("://", "\\");
                case "file":
                    if (Array.IndexOf(Log.Instance.customImages.ToArray(), path) == -1)
                        Log.Instance.customImages.Add(path);
                    return path.Replace("file://Plugin:\\HudScene\\Images\\", "images\\");
                default:
                    if (Array.IndexOf(Log.Instance.hudImages.ToArray(), path) == -1)
                        Log.Instance.hudImages.Add(path);
                    return "hud\\" + path;
            }
        }
    }
}
