﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace xmlTools
{
    class XmlTools
    {
        private XmlDocument xdoc;
        private string posElement, posParentElement;
        private bool noBackups;

        /// Our constructor
        /// <summary>
        /// </summary>
        /// <param name="file"></param>
        /// <param name="posElement"></param>
        /// <param name="posParentElement"></param>
        public XmlTools(string posElement, string posParentElement = "", bool noBackups = false)
        {
            this.posElement = posElement;
            this.posParentElement = posParentElement;
            this.noBackups = noBackups;
        }

        /// <summary>
        /// Replace all values of a element by another value
        /// </summary>
        /// <param name="file"></param>
        /// <param name="value"></param>
        public void replaceAll(string file, string value, string valuePositions = "")
        {
            if (!this.noBackups)
            {
                Util.backupFile(file);
            }
            loadXmlFile(file);

            List<XmlNode> myElements = new List<XmlNode>();
            Util.getAllSpecificElements(xdoc.DocumentElement, ref myElements, this.posElement, this.posParentElement); //Returns all after "Oni" element

            if (!String.IsNullOrEmpty(valuePositions))
            {
                checkValidSpecificPositions(valuePositions, value, myElements[0].InnerText);
            }

            foreach (XmlNode element in myElements)
            {
                if (element.Name == posElement)
                {
                    if (!String.IsNullOrEmpty(posParentElement) && element.ParentNode.Name != posParentElement)
                    {
                        continue;
                    }
                    if (String.IsNullOrEmpty(valuePositions)) //replace every value for the new
                    {
                        element.InnerText = value;
                    }
                    else //replace only the specified positions by the specified values of each dimension
                    {
                        element.InnerText = replaceSpecificPositions(element.InnerText, value, valuePositions);
                    }

                }
            }

            saveXmlFile(file);
        }

        /// <summary>
        /// Add values in an element
        /// </summary>
        /// <param name="file"></param>
        /// <param name="value"></param>
        public void addValues(string file, string values)
        {
            if (!this.noBackups)
            {
                Util.backupFile(file);
            }
            loadXmlFile(file);

            XmlTextValue myInputValues = new XmlTextValue(values);

            List<XmlNode> myElements = new List<XmlNode>();
            Util.getAllSpecificElements(xdoc.DocumentElement, ref myElements, this.posElement, this.posParentElement); //Returns all after "Oni" element

            foreach (XmlNode element in myElements)
            {
                if (element.Name == posElement)
                {
                    if (!String.IsNullOrEmpty(posParentElement) && element.ParentNode.Name != posParentElement)
                    {
                        continue;
                    }
                    XmlTextValue myXmlSubValues = new XmlTextValue(element.InnerText);

                    foreach (String myInputValue in myInputValues.myValues)
                    {
                        bool alreadyExists = false;
                        foreach (String myXmlSubValue in myXmlSubValues.myValues)
                        {
                            //It already exists in the xml??
                            if (myInputValue == myXmlSubValue)
                            {
                                alreadyExists = true;
                                break;
                            }
                        }
                        //if it doesn't exists already let's add it
                        if (!alreadyExists)
                        {
                            element.InnerText += " " + myInputValue;
                        }
                    }
                }
            }

            saveXmlFile(file);
        }

        /// <summary>
        /// Replaces a value by another
        /// </summary>
        /// <param name="currentFile"></param>
        /// <param name="replaceValue"></param>
        public void replaceValue(string file, string oldValue, string newValue)
        {
            if (!this.noBackups)
            {
                Util.backupFile(file);
            }
            loadXmlFile(file);

            List<XmlNode> myElements = new List<XmlNode>();
            Util.getAllSpecificElements(xdoc.DocumentElement, ref myElements, this.posElement, this.posParentElement); //Returns all after "Oni" element

            foreach (XmlNode element in myElements)
            {
                if (element.Name == posElement)
                {
                    if (!String.IsNullOrEmpty(posParentElement) && element.ParentNode.Name != posParentElement)
                    {
                        continue;
                    }
                    XmlTextValue myXmlSubValues = new XmlTextValue(element.InnerText);

                    for (int i = 0; i < myXmlSubValues.myValues.Count; i++)
                    {
                        //Found a match with the old value?
                        if (myXmlSubValues.myValues[i] == oldValue)
                        {
                            //replace with the new match
                            myXmlSubValues.myValues[i] = newValue;
                        }
                    }
                    element.InnerText = myXmlSubValues.ToString();
                }
            }

            saveXmlFile(file);
        }

        /// <summary>
        /// Remove values in an element
        /// </summary>
        /// <param name="file"></param>
        /// <param name="value"></param>
        public void removeValues(string file, string values)
        {
            if (!this.noBackups)
            {
                Util.backupFile(file);
            }
            loadXmlFile(file);

            XmlTextValue myInputValues = new XmlTextValue(values);

            List<XmlNode> myElements = new List<XmlNode>();
            Util.getAllSpecificElements(xdoc.DocumentElement, ref myElements, this.posElement, this.posParentElement); //Returns all after "Oni" element

            foreach (XmlNode element in myElements)
            {
                if (element.Name == posElement)
                {
                    if (!String.IsNullOrEmpty(posParentElement) && element.ParentNode.Name != posParentElement)
                    {
                        continue;
                    }
                    XmlTextValue myXmlSubValues = new XmlTextValue(element.InnerText);

                    foreach (String myInputValue in myInputValues.myValues)
                    {
                        for (int i = 0; i < myXmlSubValues.myValues.Count; i++)
                        {
                            //It already exists in the xml?
                            if (myInputValue == myXmlSubValues.myValues[i])
                            {
                                //remove it
                                myXmlSubValues.myValues.RemoveAt(i);
                                break;
                            }
                        }
                    }
                    element.InnerText = myXmlSubValues.myValues.ToString();
                }
            }

            saveXmlFile(file);
        }

        /// <summary>
        /// Change a value of element (updating all the chain)
        /// </summary>
        /// <param name="file"></param>
        /// <param name="newValue"></param>
        /// <param name="valueRelation"></param>
        public void changeValue(string file, string newValue, string valueRelation = "", string valuePositions = "")
        {
            if (!this.noBackups)
            {
                Util.backupFile(file);
            }
            loadXmlFile(file);

            XmlNumberValue xmlLastPos = null, newXmlLastPos = null;

            List<XmlNode> myElements = new List<XmlNode>();
            Util.getAllSpecificElements(xdoc.DocumentElement, ref myElements, this.posElement, this.posParentElement); //Returns all after "Oni" element

            if (String.IsNullOrEmpty(valuePositions))
            {
                newXmlLastPos = new XmlNumberValue(newValue);
            }
            else //If specific positions to be changed are specified, just associate the newLastPos with the first replaced in the specific positions by the specific values
            {
                newXmlLastPos = new XmlNumberValue(replaceSpecificPositions(myElements[0].InnerText, newValue, valuePositions));
            }

            if (!String.IsNullOrEmpty(valuePositions))
            {
                checkValidSpecificPositions(valuePositions, newValue, myElements[0].InnerText);
            }

            foreach (XmlNode element in myElements)
            {
                if (element.Name == this.posElement && (String.IsNullOrEmpty(this.posParentElement) || this.posParentElement == element.ParentNode.Name))
                {
                    XmlNumberValue xmlCurrValue = new XmlNumberValue(element.InnerText);

                    if (xmlLastPos!=null)
                    {
                        newXmlLastPos = XmlNumberValue.sum(newXmlLastPos, XmlNumberValue.difference(xmlCurrValue, xmlLastPos));
                        element.InnerText = newXmlLastPos.ToString();
                        xmlLastPos = xmlCurrValue;
                    }
                    else
                    { // first time just assign to last value
                        if (!String.IsNullOrEmpty(valueRelation))
                        {
                            newXmlLastPos = XmlNumberValue.difference(newXmlLastPos, XmlNumberValue.difference(new XmlNumberValue(valueRelation), xmlCurrValue));
                        }
                        xmlLastPos = xmlCurrValue;
                        element.InnerText = newXmlLastPos.ToString();
                    }
                }
            }
            saveXmlFile(file);
        }

        /// <summary>
        /// Invert the values of a element name (inverts all the chain)
        /// </summary>
        /// <param name="file"></param>
        public void invert(string file)
        {
            if (!this.noBackups)
            {
                Util.backupFile(file);
            }
            loadXmlFile(file);

            //Inverting the element order
            List<String> invertedOrder = new List<String>();

            List<XmlNode> myElements = new List<XmlNode>();
            Util.getAllSpecificElements(this.xdoc.DocumentElement, ref myElements, this.posElement, this.posParentElement);
            //Read all and save to the list
            foreach (XmlNode element in myElements) //Returns all after "Oni" element
            {
                invertedOrder.Add(element.InnerText);
            }

            //Let's start taking from the list to new xml file (inverted order)

            int count = 0;

            foreach (XmlNode element in myElements) //Returns all after "Oni" element
            {
                if (element.Name == posElement)
                {
                    element.InnerText = invertedOrder[invertedOrder.Count - 1 - count];
                    count++;
                }
            }
            saveXmlFile(file);
        }

        private void loadXmlFile(string file)
        {
            this.xdoc = new XmlDocument();
            this.xdoc.Load(file);
        }

        private void saveXmlFile(string file)
        {
            this.xdoc.Save(file);
        }

        private void checkValidSpecificPositions(string valuePositions, string value, string firstElement)
        {
            XmlNumberValue testValuePositions = new XmlNumberValue(valuePositions);
            XmlNumberValue testValue = new XmlNumberValue(value);
            XmlNumberValue testFirstRealValue = new XmlNumberValue(firstElement);

            if (testValuePositions.myValues.Count != testValue.myValues.Count) //Check if there are the same number of positions for the same number of dimensions to replace
            {
                Program.printAppError(Program.appErrors.NUMBER_VALUES_TO_REPLACE_NE_AVAILABLE_VALUES, "The number of values to replace must be the same of the number of specified positions.", true);
            }
            foreach (int pos in testValuePositions.myValues) //converts automatically from double to int
            {
                if (pos > testFirstRealValue.myValues.Count - 1 || pos < 0) //Are positions valid for the current values? //-1 because starts at 0
                {
                    Program.printAppError(Program.appErrors.INVALID_POSITIONS_RANGE, "The positions values are not in the range of the value to replace (pos index < 0 or > newValueIndexesNumber).", true);
                }
            }
        }

        private string replaceSpecificPositions(string currXmlValue, string values, string valuePositions)
        {
            XmlNumberValue currentValue = new XmlNumberValue(currXmlValue);
            XmlNumberValue positionsValues = new XmlNumberValue(valuePositions);
            XmlNumberValue newValues = new XmlNumberValue(values);

            for (int i = 0; i < positionsValues.myValues.Count; i++)
            {
                int posToChange = (int)positionsValues.myValues[i];
                currentValue.myValues[posToChange] = newValues.myValues[i];
            }

            return currentValue.ToString();
        }
    }
}
