using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.IO.IsolatedStorage;
using System.Xml.Serialization;
using System.Security.Permissions;

namespace IndianHealthService.BMXNet.WinForm.Configuration
{

    /// <summary>
    /// LocalPersistentStore is the core BMX implementation for the most common
    /// local setting store patterns:
    /// 
    /// 1) In a special folder
    /// 2) In any specified path
    /// 3) In IsolatedStorage
    /// 
    /// Use the object factory static class methods to create the different storage 
    /// mechanisms.
    /// </summary>
    public class LocalPersistentStore : PersistentStore
    {
        private bool _attemptAncestorKeysOnRead = false;

        public bool AttemptAncestorKeysOnRead
        {
            get { return _attemptAncestorKeysOnRead; }
            set { _attemptAncestorKeysOnRead = value; }
        }
        
        public static LocalPersistentStore CreateIn(Environment.SpecialFolder aSpecialFolder, String subDirectoryPath,bool tryParentKeysOnRead)
        {                       
            LocalPersistentStore answer = new LocalPersistentStore();
            answer.SpecialFolder = aSpecialFolder;
            answer.DirectoryPath = subDirectoryPath;
            answer.AttemptAncestorKeysOnRead = tryParentKeysOnRead;
            return answer;
                    
        }

        public static LocalPersistentStore CreateIn(String aDirectoryPath, bool tryParentKeysOnRead)
        {
            LocalPersistentStore answer = new LocalPersistentStore();
            answer.DirectoryPath = aDirectoryPath;
            answer.AttemptAncestorKeysOnRead = tryParentKeysOnRead;
            return answer;
        }

        protected static LocalPersistentStore CreateIn(IsolatedStorageScope aScope, bool tryParentKeysOnRead)
        {
            LocalPersistentStore answer = new LocalPersistentStore();
            answer.IsolatedStorageScope = aScope;
            answer.AttemptAncestorKeysOnRead = tryParentKeysOnRead;
            return answer;
        }

        public static LocalPersistentStore CreateDefaultStorage(bool tryParentKeysOnRead)
        {
            return CreateIn(System.IO.IsolatedStorage.IsolatedStorageScope.User | System.IO.IsolatedStorage.IsolatedStorageScope.Assembly, tryParentKeysOnRead);
        }

        private Environment.SpecialFolder? _specialFolder = null;

        public Environment.SpecialFolder? SpecialFolder
        {
            get { return _specialFolder; }
            set { _specialFolder = value; }
        }

        private String _directoryPath = null;

        public String DirectoryPath
        {
            get { return _directoryPath; }
            set { _directoryPath = value; }
        }


        private IsolatedStorageScope? _isolatedStorageScope = null;

        public IsolatedStorageScope? IsolatedStorageScope
        {
            get { return _isolatedStorageScope; }
            set { _isolatedStorageScope = value; }
        }


        private T ReadFromFileSystem<T>(String aDirectory, params string[] keys)
        {
            String path = Path.Combine(aDirectory, this.TranslateToValidFileName(keys));

            T result = default(T);

            using (Stream stream = File.OpenRead(path))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                result=(T)serializer.Deserialize(stream);
            }
            return result;
        }


        private void WriteToFileSystem<T>(String aDirectory, T anObject, params string[] keys)
        {
            String path = Path.Combine(aDirectory, this.TranslateToValidFileName(keys));

            if (!Directory.Exists(aDirectory))
            {
                Directory.CreateDirectory(aDirectory);
            }

            using (Stream stream = File.Create(path))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                serializer.Serialize(stream, anObject);
            }
        }

        /// <summary>
        /// Remove on non-letters, digits, to _ and add Bmx_ to the front.
        /// </summary>
        /// <param name="aKey"></param>
        /// <returns></returns>
        private string TranslateToValidFileName(params string[] keys)
        {
            StringBuilder builder = new StringBuilder();

            builder.Append("bmx_");

            foreach (String key in keys)
            {
                foreach (char each in key)
                {
                    builder.Append((Char.IsLetterOrDigit(each)) ? each : '_');
                }
                builder.Append("_");
            }

            builder.Append("config.xml");

            return builder.ToString();
        }

       

        /// <summary>
        /// Isolated storage is scoped on user/application isolated storage
        /// </summary>
        /// <param name="aKey"></param>
        /// <returns></returns>
        private T ReadFromIsolatedStorage<T>(params string[] keys)
        {
            T result = default(T);

            String validFileName = this.TranslateToValidFileName(keys) + ".is";

            using (IsolatedStorageFile storage = IsolatedStorageFile.GetStore(this.IsolatedStorageScope.Value,null, null))
            {
                using (Stream stream = new IsolatedStorageFileStream(validFileName, FileMode.Open, FileAccess.Read, storage))
                {
                    XmlSerializer serializer = new XmlSerializer(typeof(T));
                    result = (T)serializer.Deserialize(stream);
                }
            }
            return result;
        }



        /// <summary>
        /// Isolated storage is scoped on user/application isolated storage
        /// </summary>
        /// <param name="aKey"></param>
        /// <param name="someContent"></param>
       private void WriteToIsolatedStorage<T>(T anObject, string[] keys)
        {
            String validFileName = this.TranslateToValidFileName(keys) + ".is";

            using (IsolatedStorageFile storage = IsolatedStorageFile.GetStore(this.IsolatedStorageScope.Value, null,null))
            {
                using (Stream stream = new IsolatedStorageFileStream(validFileName, FileMode.Create, FileAccess.Write, storage))
                {
                    XmlSerializer serializer = new XmlSerializer(typeof(T));
                    serializer.Serialize(stream,anObject);         
                }
            }
        }


        public T Read<T>(params string[] keys)
        {
            String[] keysToUse = keys;

            try
            {
                if (this.IsolatedStorageScope.HasValue)
                {
                    return this.ReadFromIsolatedStorage<T>(keysToUse);
                }
                else if (this.SpecialFolder.HasValue)
                {
                    return this.ReadFromFileSystem<T>(Path.Combine(Environment.GetFolderPath(this.SpecialFolder.Value),this.DirectoryPath), keysToUse);
                }
                else if (this.DirectoryPath != null)
                {
                    return this.ReadFromFileSystem<T>(this.DirectoryPath, keysToUse);
                }
                else
                {
                    return default(T);
                }
            }
            catch 
            {
                if (this.AttemptAncestorKeysOnRead && keysToUse.Length>1) {
                    string[] newKeysToUse=new string[keysToUse.Length-1];
                    Array.Copy(keysToUse, newKeysToUse, keysToUse.Length - 1);
                    return this.Read<T>(newKeysToUse);
                }


                return default(T);
            }
        }


        public void Write<T>(T anObject, params string[] keys)
        {
            if (this.IsolatedStorageScope.HasValue)
            {
                this.WriteToIsolatedStorage<T>(anObject, keys);
            }
            else if (this.SpecialFolder.HasValue)
            {
                this.WriteToFileSystem<T>(Path.Combine(Environment.GetFolderPath(this.SpecialFolder.Value),this.DirectoryPath), anObject, keys);
            }
            else if (this.DirectoryPath != null)
            {
                this.WriteToFileSystem<T>(this.DirectoryPath, anObject, keys);
            }
            else
            {
                throw new Exception("Local persistent store not configured.");
            }
        }

    }
}