// Copyright (C) 2010 panacoran <panacoran@users.sourceforge.jp>
// 
// This program is part of Protra.
//
// Protra is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
// 
// $Id: ConfigBase.cs 366 2010-04-21 10:14:02Z panacoran $

using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using System.Xml.Serialization;
using Protra.Lib.Dialogs;

namespace Protra.Lib.Config
{
    /// <summary>
    /// 設定ファイルを読み書きする抽象クラス。
    /// </summary>
    abstract public class ConfigBase
    {
        /// <summary>
        /// 古い設定ファイルを読み込む。
        /// </summary>
        abstract protected void ReadOldConfig();

        /// <summary>
        /// 設定ファイルの名前を取得する。
        /// </summary>
        abstract protected string ConfigName
        {
            get;
        }

        private string filename;
        private XmlSerializer serializer;
        FileSystemWatcher watcher;

        /// <summary>
        /// コンストラクタ。
        /// </summary>
        public ConfigBase()
        {
            filename = Path.Combine(Global.DirConf, ConfigName + ".xml");
            serializer = new XmlSerializer(GetType());
            if (!Directory.Exists(Global.DirConf))
                Directory.CreateDirectory(Global.DirConf);
        }

        private MethodInvoker handler;

        /// <summary>
        /// ファイルの変更を監視する設定をする。
        /// </summary>
        /// <param name="handler">ファイルが変更されたときに呼ばれるハンドラ</param>
        public void SetWatcher(MethodInvoker handler)
        {
            watcher = new FileSystemWatcher(Global.DirConf);
            watcher.Changed += new FileSystemEventHandler(watcher_Changed);
            watcher.NotifyFilter = NotifyFilters.LastWrite;
            watcher.Filter = ConfigName + ".xml";
            watcher.NotifyFilter = NotifyFilters.LastWrite;
            if (handler != null)
            {
                watcher.SynchronizingObject = (Form)handler.Target;
                this.handler = handler;
            }
            watcher.EnableRaisingEvents = true;
        }

        void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            Load();
            if (handler != null)
                handler();
        }

        /// <summary>
        /// 設定ファイルを読み込む。
        /// </summary>
        public virtual void Load()
        {
            IOException ioex = null;
            for (var i = 0; i < 10; i++)
            {
                try
                {
                    using (StreamReader reader = new StreamReader(
                        new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None)))
                    {
                        Object tmp = serializer.Deserialize(reader);
                        foreach (PropertyInfo property in this.GetType().GetProperties())
                            property.SetValue(this, property.GetValue(tmp, null), null);
                    }
                    return;
                }
                catch (FileNotFoundException)
                {
                    ReadOldConfig();
                    Save();
                    return;
                }
                catch (IOException e)
                {
                    ioex = e;
                    Thread.Sleep(200);
                }
                catch (InvalidOperationException e)
                {
                    using (var dialog = new ApplicationError())
                    {
                        dialog.ErrorMessage = "設定ファイルが壊れています。\n" + filename + "\n" + e.Message;
                        dialog.ShowDialog();
                    }
                    return;
                }
            }
            using (var dialog = new ApplicationError())
            {
                dialog.ErrorMessage = "設定ファイルの読み込みに失敗しました。\n" +
                    (ioex != null ? ioex.Message : filename);
                dialog.ShowDialog();
            }
        }

        /// <summary>
        /// 設定ファイルを書き込む。
        /// </summary>
        public virtual void Save()
        {
            try
            {
                if (watcher != null)
                    watcher.EnableRaisingEvents = false;
                using (var fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
                {
                    fs.SetLength(0);
                    using (StreamWriter writer = new StreamWriter(fs))
                        serializer.Serialize(writer, this);
                }
            }
            catch
            {
                using (ApplicationError dialog = new ApplicationError())
                {
                    dialog.ErrorMessage = "設定ファイルの書き込みに失敗しました。\n" + filename;
                    dialog.Mode = ApplicationError.ErrorType.Recoverable;
                    dialog.ShowDialog();
                }
            }
            finally
            {
                if (watcher != null)
                    watcher.EnableRaisingEvents = true;
            }
        }
    }
}
