using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using RainmeterStudio.Core.Documents;
using RainmeterStudio.Core.Model;
using RainmeterStudio.Core.Model.Events;
using RainmeterStudio.Core.Storage;
namespace RainmeterStudio.Business
{
    /// 
    /// Document manager
    /// 
    public class DocumentManager
    {
        #region Events
        /// 
        /// Triggered when a document is opened
        /// 
        public event EventHandler DocumentOpened;
        /// 
        /// Triggered when a document is closed
        /// 
        public event EventHandler DocumentClosed;
        #endregion
        #region Properties
        /// 
        /// Gets a list of factories
        /// 
        public IEnumerable Factories { get { return _factories; } }
        /// 
        /// Gets a list of editors
        /// 
        public IEnumerable Editors { get { return _editors; } }
        /// 
        /// Gets a list of storages
        /// 
        public IEnumerable Storages { get { return _storages; } }
        /// 
        /// Gets a list of document templates
        /// 
        public IEnumerable DocumentTemplates { get { return _templates; } }
        #endregion
        #region Private fields
        private List _factories = new List();
        private List _editors = new List();
        private List _storages = new List();
        private List _templates = new List();
        #endregion
        #region Initialization
        /// 
        /// Initializes this document manager
        /// 
        public DocumentManager()
        {
        }
        /// 
        /// Registers a document editor factory
        /// 
        /// Document editor factory
        public void RegisterEditorFactory(IDocumentEditorFactory factory)
        {
            _factories.Add(factory);
        }
        /// 
        /// Registers a document storage
        /// 
        /// The storage
        public void RegisterStorage(IDocumentStorage storage)
        {
            _storages.Add(storage);
        }
        /// 
        /// Registers a document template
        /// 
        /// The document template
        public void RegisterTemplate(IDocumentTemplate template)
        {
            _templates.Add(template);
        }
        #endregion
        #region Document operations
        /// 
        /// Creates a new document in the specified path, with the specified format, and opens it
        /// 
        /// 
        /// 
        public IDocumentEditor Create(IDocumentTemplate format)
        {
            // Create document
            var document = format.CreateDocument();
            document.IsDirty = true;
            // Find and create editor
            IDocumentEditor editor = CreateEditor(document);
            // Trigger event
            if (DocumentOpened != null)
                DocumentOpened(this, new DocumentOpenedEventArgs(editor));
            return editor;
        }
        /// 
        /// Opens the specified document
        /// 
        /// The path to the file to open
        public IDocumentEditor Open(string path)
        {
            // Try to open
            IDocument document = Read(path);
            // Create factory
            var editor = CreateEditor(document);
            // Trigger event
            if (DocumentOpened != null)
                DocumentOpened(this, new DocumentOpenedEventArgs(editor));
            return editor;
        }
        /// 
        /// Saves a document
        /// 
        /// The document
        public void Save(IDocument document)
        {
            // Find a storage
            var storage = FindStorage(document);
            if (document.Reference == null)
                throw new ArgumentException("Reference cannot be empty");
            // Save
            storage.Write(document.Reference.Path, document);
            // Clear dirty flag
            document.IsDirty = false;
        }
        /// 
        /// Saves the document as
        /// 
        /// Path
        /// Document
        public void SaveAs(string path, IDocument document)
        {
            // Find a storage
            var storage = FindStorage(document);
            // Save
            storage.Write(path, document);
            // Update reference
            document.Reference = new Reference(Path.GetFileName(path), path);
            // Clear dirty flag
            document.IsDirty = false;
        }
        /// 
        /// Saves a copy of the document
        /// 
        /// Path
        /// Document
        public void SaveACopy(string path, IDocument document)
        {
            // Find a storage
            var storage = FindStorage(document);
            // Save
            storage.Write(path, document);
        }
        /// 
        /// Closes a document editor
        /// 
        /// 
        public void Close(IDocumentEditor editor)
        {
            // Remove from list of opened editors
            _editors.Remove(editor);
            // Close event
            if (DocumentClosed != null)
                DocumentClosed(this, new DocumentClosedEventArgs(editor));
        }
        #endregion
        #region Private functions
        /// 
        /// Attempts to create an editor for the document
        /// 
        /// The document
        /// Thrown if failed to create editor
        /// The editor
        private IDocumentEditor CreateEditor(IDocument document)
        {
            IDocumentEditor editor = null;
            foreach (var factory in Factories)
                if (factory.CanEdit(document.GetType()))
                {
                    editor = factory.CreateEditor(document);
                    break;
                }
            if (editor == null)
                throw new ArgumentException("Failed to create editor.");
            _editors.Add(editor);
            return editor;
        }
        /// 
        /// Attempts to read a document
        /// 
        /// Path of file
        /// Thrown when failed to open the document
        /// 
        private IDocument Read(string path)
        {
            IDocument document = null;
            foreach (var storage in Storages)
                if (storage.CanRead(path))
                {
                    document = storage.Read(path);
                    break;
                }
            // Failed to open
            if (document == null)
                throw new ArgumentException("Failed to open document.");
            return document;
        }
        /// 
        /// Attempts to find a storage for the specified document
        /// 
        /// 
        /// 
        private IDocumentStorage FindStorage(IDocument document)
        {
            IDocumentStorage storage = null;
            foreach (var s in Storages)
                if (s.CanWrite(document.GetType()))
                {
                    storage = s;
                    break;
                }
            if (storage == null)
                throw new ArgumentException("Failed to find storage object.");
            return storage;
        }
        #endregion
    }
}