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

namespace Mix.Tool.ArchiveEditor
{
    /// <summary>
    /// プロジェクトクラス
    /// </summary>
    public class Project
    {
        #region Exception

        /// <summary>
        /// 例外 : 新規作成に失敗
        /// </summary>
        public class NewFailedException : Exception
        {
            public NewFailedException(string message)
                : base(message)
            {
            }
        };

        /// <summary>
        /// 例外 : 読み込みに失敗
        /// </summary>
        public class LoadFailedException : Exception
        {
            public LoadFailedException(string message)
                : base(message)
            {
            }
        };

        #endregion

        #region Private Class

        private class FileNode : IFileNode
        {
            private string name;
            private string fullPath;
            private string type;
            private long size;
            private DateTime updateDate;

            public FileNode(string _name, string _fullPath, long _size, DateTime _updateDate)
            {
                name = _name;
                fullPath = _fullPath;
                type = Path.GetExtension(_fullPath);
                size = _size;
                updateDate = _updateDate;
            }

            public string Name
            {
                get { return name; }
            }

            public string FullPath
            {
                get { return fullPath; }
            }

            public string Type
            {
                get { return type; }
            }

            public long Size
            {
                get { return size; }
            }

            public DateTime UpdateDate
            {
                get { return updateDate; }
            }
        };

        private class FileNodeCollection : IFileNodeCollection
        {
            private List<IFileNode> list = new List<IFileNode>();

            public int Count
            {
                get { return list.Count; }
            }

            public IFileNode this[int index]
            {
                get
                {
                    try
                    {
                        return list[index];
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                }
            }

            public void Add(IFileNode fileNode)
            {
                list.Add(fileNode);
            }

            public System.Collections.Generic.IEnumerator<IFileNode> GetEnumerator()
            {
                return list.GetEnumerator();
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return list.GetEnumerator();
            }
        };

        private class DirectoryNode : IDirectoryNode
        {
            private string name;
            private string fullPath;
            private DateTime updateDate;
            private FileNodeCollection fileNodes = new FileNodeCollection();
            private DirectoryNodeCollection directoryNodes = new DirectoryNodeCollection();

            public DirectoryNode(string _name, string _fullPath, DateTime _updateDate)
            {
                name = _name;
                fullPath = _fullPath;
                updateDate = _updateDate;
            }

            public string Name
            {
                get { return name; }
            }

            public string FullPath
            {
                get { return fullPath; }
            }

            public string Type
            {
                get { return "Directory"; }
            }

            public DateTime UpdateDate
            {
                get { return updateDate; }
            }

            public IFileNodeCollection FileNodes
            {
                get { return fileNodes; }
            }

            public IDirectoryNodeCollection Children
            {
                get { return directoryNodes; }
            }
        };

        private class DirectoryNodeCollection : IDirectoryNodeCollection
        {
            private List<IDirectoryNode> list = new List<IDirectoryNode>();

            public int Count
            {
                get { return list.Count; }
            }

            public IDirectoryNode this[int index]
            {
                get
                {
                    try
                    {
                        return list[index];
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                }
            }

            public void Add(IDirectoryNode directoryNode)
            {
                list.Add(directoryNode);
            }

            public IEnumerator<IDirectoryNode> GetEnumerator()
            {
                return list.GetEnumerator();
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return list.GetEnumerator();
            }
        };

        private class Param
        {
            public string rootDirectoryName = "";
            public string inputDirectoryName = "";
            public string outputFileName = "";
            public int alignment = 1;
        };

        #endregion

        #region Private Value

        private List<Param> paramList = new List<Param>();
        private DirectoryNode rootDirectoryNode = null;
        private string fileName = "";
        private string name = "";
        private bool update = false;

        #endregion

        #region Public Method

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public Project()
        {
            paramList.Add(new Param());
            paramList.Add(new Param());
        }

        /// <summary>
        /// ファイル名の取得
        /// </summary>
        public string FileName
        {
            get { return fileName; }
        }

        /// <summary>
        /// 名前の取得
        /// </summary>
        public string Name
        {
            get { return name; }
        }

        /// <summary>
        /// ルートディレクトリ名の取得
        /// </summary>
        public string RootDirectoryName
        {
            get { return paramList[1].rootDirectoryName; }
        }

        /// <summary>
        /// 入力ディレクトリ名の取得
        /// </summary>
        public string InputDirectoryName
        {
            get { return paramList[1].inputDirectoryName; }
        }

        /// <summary>
        /// 出力ファイル名の取得、または設定
        /// </summary>
        public string OutputFileName
        {
            get { return paramList[1].outputFileName; }
            set
            {
                paramList[1].outputFileName = value;
                UpdateState();
            }
        }

        /// <summary>
        /// アライメントの取得、または設定
        /// </summary>
        public int Alignment
        {
            get { return paramList[1].alignment; }
            set
            {
                paramList[1].alignment = value;
                UpdateState();
            }
        }

        /// <summary>
        /// 更新されているかどうかの取得
        /// </summary>
        public bool Update
        {
            get { return update; }
        }

        /// <summary>
        /// ルートディレクトリノードの取得
        /// </summary>
        public IDirectoryNode RootDirectoryNode
        {
            get { return rootDirectoryNode; }
        }

        /// <summary>
        /// プロジェクトを新規作成
        /// </summary>
        /// <param name="rootDirectoryName">ルートディレクトリ名</param>
        /// <param name="inputDirectoryName">入力ディレクトリ名(アーカイブにするディレクトリ)</param>
        /// <param name="outputFileName">出力ファイル名(アーカイブのファイル名)</param>
        public void New(string rootDirectoryName, string inputDirectoryName, string outputFileName)
        {
            if (Directory.Exists(rootDirectoryName) == false)
            {
                throw new NewFailedException(String.Format(Properties.Resources.Mes_NewProject_RootDirectoryNotFound, rootDirectoryName));
            }

            if (Directory.Exists(inputDirectoryName) == false)
            {
                throw new NewFailedException(String.Format(Properties.Resources.Mes_NewProject_InputDirectoryNotFound, inputDirectoryName));
            }

            if ((inputDirectoryName.Length == 3) &&
                (inputDirectoryName[1] == ':') &&
                ((inputDirectoryName[2] == '\\') || (inputDirectoryName[2] == '/')))
            {
                throw new NewFailedException(String.Format(Properties.Resources.Mes_NewProject_IllegalInputDirectory, inputDirectoryName));
            }

            paramList[1].rootDirectoryName = rootDirectoryName;
            paramList[1].inputDirectoryName = inputDirectoryName;
            paramList[1].outputFileName = outputFileName;

            fileName = "";

            UpdateName();
            UpdateState();

            rootDirectoryNode = LoadDirectory();
        }

        /// <summary>
        /// プロジェクトをロードします
        /// </summary>
        /// <param name="newfileName">プロジェクトのファイル名</param>
        public void Load(string newfileName)
        {
            string tempRootDirectoryName = "";
            string tempInputDirectoryName = "";
            string tempOutputFileName = "";
            int tempAlignment = 4;

            try
            {
                XmlDocument xmlDoc;
                XmlNode xmlRoot;

                xmlDoc = new XmlDocument();
                xmlDoc.Load(newfileName);

                //ルート
                xmlRoot = xmlDoc.DocumentElement;
                if ((xmlRoot != null) &&
                    (xmlRoot.Name == "mafe"))
                {
                    if (xmlRoot.Attributes["version"].Value != Properties.Resources.Project_Version)
                    {
                        throw new LoadFailedException(String.Format(Properties.Resources.Mes_OpenProject_Failed, newfileName));
                    }
                }
                else
                {
                    throw new LoadFailedException(String.Format(Properties.Resources.Mes_OpenProject_Failed, newfileName));
                }

                foreach (XmlNode xmlNode in xmlRoot.ChildNodes)
                {
                    if (xmlNode.Name == "rootDirectoryName")
                    {
                        tempRootDirectoryName = xmlNode.Attributes["value"].Value;
                    }
                    else if (xmlNode.Name == "inputDirectoryName")
                    {
                        tempInputDirectoryName = xmlNode.Attributes["value"].Value;
                    }
                    else if (xmlNode.Name == "outputFileName")
                    {
                        tempOutputFileName = xmlNode.Attributes["value"].Value;
                    }
                    else if (xmlNode.Name == "alignment")
                    {
                        tempAlignment = int.Parse(xmlNode.Attributes["value"].Value);
                    }
                }
            }
            catch (Exception)
            {
                throw new LoadFailedException(String.Format(Properties.Resources.Mes_OpenProject_Failed, newfileName));
            }

            if (Directory.Exists(tempRootDirectoryName) == false)
            {
                throw new LoadFailedException(String.Format(Properties.Resources.Mes_OpenProject_RootDirectoryNotFound, newfileName, tempRootDirectoryName));
            }

            if (Directory.Exists(tempInputDirectoryName) == false)
            {
                throw new LoadFailedException(String.Format(Properties.Resources.Mes_OpenProject_InputDirectoryNotFound, newfileName, tempInputDirectoryName));
            }

            paramList[0].rootDirectoryName = paramList[1].rootDirectoryName = tempRootDirectoryName;
            paramList[0].inputDirectoryName = paramList[1].inputDirectoryName = tempInputDirectoryName;
            paramList[0].outputFileName = paramList[1].outputFileName = tempOutputFileName;
            paramList[0].alignment = paramList[1].alignment = tempAlignment; 
            fileName = newfileName;
            update = false;

            UpdateName();

            rootDirectoryNode = LoadDirectory();
        }

        /// <summary>
        /// プロジェクトをセーブします
        /// </summary>
        /// <returns>成功した場合は true を返します</returns>
        public bool Save()
        {
            try
            {
                XmlDocument xmlDoc;
                XmlAttribute xmlAttr;
                XmlNode xmlRoot;
                XmlNode xmlNode;

                //ドキュメント
                xmlDoc = new XmlDocument();

                //ルート
                xmlRoot = xmlDoc.CreateElement("mafe");
                xmlAttr = xmlDoc.CreateAttribute("version");
                xmlAttr.Value = Properties.Resources.Project_Version;
                xmlRoot.Attributes.Append(xmlAttr);
                xmlDoc.AppendChild(xmlRoot);

                //ルートディレクトリ名
                xmlNode = xmlDoc.CreateElement("rootDirectoryName");
                xmlAttr = xmlDoc.CreateAttribute("value");
                xmlAttr.Value = paramList[1].rootDirectoryName;
                xmlNode.Attributes.Append(xmlAttr);
                xmlRoot.AppendChild(xmlNode);

                //入力ディレクトリ名
                xmlNode = xmlDoc.CreateElement("inputDirectoryName");
                xmlAttr = xmlDoc.CreateAttribute("value");
                xmlAttr.Value = paramList[1].inputDirectoryName;
                xmlNode.Attributes.Append(xmlAttr);
                xmlRoot.AppendChild(xmlNode);

                //出力ファイル名
                xmlNode = xmlDoc.CreateElement("outputFileName");
                xmlAttr = xmlDoc.CreateAttribute("value");
                xmlAttr.Value = paramList[1].outputFileName;
                xmlNode.Attributes.Append(xmlAttr);
                xmlRoot.AppendChild(xmlNode);

                //アライメント
                xmlNode = xmlDoc.CreateElement("alignment");
                xmlAttr = xmlDoc.CreateAttribute("value");
                xmlAttr.Value = paramList[1].alignment.ToString();
                xmlNode.Attributes.Append(xmlAttr);
                xmlRoot.AppendChild(xmlNode);

                xmlDoc.Save(fileName);
            }
            catch (Exception)
            {
                return false;
            }

            paramList[0].inputDirectoryName = paramList[1].inputDirectoryName;
            paramList[0].outputFileName = paramList[1].outputFileName;
            update = false;

            return true;
        }

        /// <summary>
        /// プロジェクトにファイル名を付けてセーブします
        /// </summary>
        /// <param name="newfileName">ファイル名</param>
        /// <returns>成功した場合は true を返します</returns>
        public bool SaveAs(string newfileName)
        {
            fileName = newfileName;

            return Save();
        }

        #endregion

        #region Private Method

        /// <summary>
        /// ディレクトリをロード
        /// </summary>
        /// <returns>ディレクトリノード</returns>
        private DirectoryNode LoadDirectory()
        {
            string rootDirName = paramList[1].rootDirectoryName;
            string inputDirName = paramList[1].inputDirectoryName;
            DirectoryNode node = null;

            if (rootDirName != inputDirName)
            {
                int splitPos;
                string tempDirName;
                string[] dirNames;
                DirectoryNode lastNode = null;

                splitPos = inputDirName.LastIndexOf('\\');
                if (splitPos == -1)
                {
                    splitPos = inputDirName.LastIndexOf('/');
                }

                tempDirName = inputDirName.Substring(0, (splitPos+1));
                tempDirName = tempDirName.Substring(rootDirName.Length);
                dirNames = tempDirName.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);

                if (dirNames.Length > 0)
                {
                    if ((rootDirName[rootDirName.Length - 1] != '\\') &&
                        (rootDirName[rootDirName.Length - 1] != '/'))
                    {
                        rootDirName += "\\";
                    }

                    foreach (string dirName in dirNames)
                    {
                        string dirFullPath = (rootDirName + dirName);
                        DirectoryNode newNode = new DirectoryNode(dirName, dirFullPath, Directory.GetLastWriteTime(dirFullPath));

                        if (lastNode != null)
                        {
                            DirectoryNodeCollection dirNodes = lastNode.Children as DirectoryNodeCollection;

                            dirNodes.Add(newNode);
                            lastNode = newNode;
                        }
                        else
                        {
                            node = lastNode = newNode;
                        }
                    }
                }

                if (lastNode != null)
                {
                    LoadDirectory(lastNode, inputDirName);
                }
                else
                {
                    node = LoadDirectory(null, inputDirName);
                }
            }
            else
            {
                node = LoadDirectory(null, inputDirName);
            }

            return node;
        }

        /// <summary>
        /// ディレクトリをロード
        /// </summary>
        /// <param name="parentNode">親ディレクトリノード</param>
        /// <param name="path">ディレクトリパス</param>
        /// <returns>ディレクトリノード</returns>
        private DirectoryNode LoadDirectory(DirectoryNode parentNode, string path)
        {
            DirectoryNode dirNode;
            FileNodeCollection files;
            DateTime dirUpdateDate;
            string dirName;
            string[] dirPaths;
            string[] filePaths;

            dirName = path.Substring(path.LastIndexOf('\\') + 1);
            dirUpdateDate = Directory.GetLastWriteTime(path);

            dirNode = new DirectoryNode(dirName, path, dirUpdateDate);
            if (parentNode != null)
            {
                DirectoryNodeCollection dirNodes = parentNode.Children as DirectoryNodeCollection;
                dirNodes.Add(dirNode);
            }

            dirPaths = Directory.GetDirectories(path);
            foreach (string dirPath in dirPaths)
            {
                LoadDirectory(dirNode, dirPath);
            }

            files = dirNode.FileNodes as FileNodeCollection;
            filePaths = Directory.GetFiles(path);
            foreach (string filePath in filePaths)
            {
                FileInfo fileInfo = new FileInfo(filePath);
                FileNode fileNode = new FileNode(fileInfo.Name, filePath, fileInfo.Length, fileInfo.LastWriteTime);

                files.Add(fileNode);
            }

            return dirNode;
        }

        /// <summary>
        /// 出力ファイル名を使い、プロジェクトの名前を更新する
        /// </summary>
        private void UpdateName()
        {
            name = Path.GetFileNameWithoutExtension(paramList[1].outputFileName);
        }

        /// <summary>
        /// ステータスを更新
        /// </summary>
        private void UpdateState()
        {
            if ((paramList[0].rootDirectoryName != paramList[1].rootDirectoryName) ||
                (paramList[0].inputDirectoryName != paramList[1].inputDirectoryName) ||
                (paramList[0].outputFileName != paramList[1].outputFileName) ||
                (paramList[0].alignment != paramList[1].alignment))
            {
                update = true;
            }
        }

        #endregion
    }
}
