/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;

using Microsoft.Scripting.Utils;

namespace Microsoft.Scripting.Hosting {
    /// <summary>
    /// Stores information needed to setup a language
    /// </summary>
    [Serializable]
    public sealed class LanguageSetup {
        private string _typeName;
        private string _displayName;
        private IList<string> _names;
        private IList<string> _fileExtensions;
        private IDictionary<string, object> _options;
        private bool _frozen;
        private bool? _interpretedMode, _exceptionDetail, _perfStats, _noAdaptiveCompilation;

        /// <summary>
        /// Creates a new LanguageSetup
        /// </summary>
        /// <param name="typeName">assembly qualified type name of the language
        /// provider</param>
        public LanguageSetup(string typeName)
            : this(typeName, "", ArrayUtils.EmptyStrings, ArrayUtils.EmptyStrings) {
        }

        /// <summary>
        /// Creates a new LanguageSetup with the provided options
        /// TODO: remove this overload?
        /// </summary>
        public LanguageSetup(string typeName, string displayName)
            : this(typeName, displayName, ArrayUtils.EmptyStrings, ArrayUtils.EmptyStrings) {
        }

        /// <summary>
        /// Creates a new LanguageSetup with the provided options
        /// </summary>
        public LanguageSetup(string typeName, string displayName, IEnumerable<string> names, IEnumerable<string> fileExtensions) {
            ContractUtils.RequiresNotEmpty(typeName, "typeName");
            ContractUtils.RequiresNotNull(displayName, "displayName");
            ContractUtils.RequiresNotNull(names, "names");
            ContractUtils.RequiresNotNull(fileExtensions, "fileExtensions");

            _typeName = typeName;
            _displayName = displayName;
            _names = new List<string>(names);
            _fileExtensions = new List<string>(fileExtensions);
            _options = new Dictionary<string, object>();
        }

        /// <summary>
        /// Gets an option as a strongly typed value.
        /// </summary>
        public T GetOption<T>(string name, T defaultValue) {
            object value;
            if (_options != null && _options.TryGetValue(name, out value)) {
                if (value is T) {
                    return (T)value;
                }
                return (T)Convert.ChangeType(value, typeof(T), Thread.CurrentThread.CurrentCulture);
            }
            return defaultValue;
        }

        /// <summary>
        /// The assembly qualified type name of the language provider
        /// </summary>
        public string TypeName {
            get { return _typeName; }
            set {
                ContractUtils.RequiresNotEmpty(value, "value");
                CheckFrozen();
                _typeName = value;
            }
        }

        /// <summary>
        /// Display name of the language. If empty, it will be set to the first
        /// name in the Names list.
        /// </summary>
        public string DisplayName {
            get { return _displayName; }
            set {
                ContractUtils.RequiresNotNull(value, "value");
                CheckFrozen();
                _displayName = value;
            }
        }

        /// <remarks>
        /// Case-insensitive language names.
        /// </remarks>
        public IList<string> Names {
            get { return _names; }
        }

        /// <remarks>
        /// Case-insensitive file extension, optionally starts with a dot.
        /// </remarks>
        public IList<string> FileExtensions {
            get { return _fileExtensions; }
        }

        /// <remarks>
        /// Option names are case-sensitive.
        /// </remarks>
        public IDictionary<string, object> Options {
            get { return _options; }
        }

        public bool InterpretedMode {
            get { return GetCachedOption("InterpretedMode", ref _interpretedMode); }
            set { 
                CheckFrozen();
                Options["InterpretedMode"] = value; 
            }
        }

        public bool NoAdaptiveCompilation {
            get { return GetCachedOption("NoAdaptiveCompilation", ref _noAdaptiveCompilation); }
            set {
                CheckFrozen();
                Options["NoAdaptiveCompilation"] = value;
            }
        }

        public bool ExceptionDetail {
            get { return GetCachedOption("ExceptionDetail", ref _exceptionDetail); }
            set {
                CheckFrozen();
                Options["ExceptionDetail"] = value;
            }
        }

        public bool PerfStats {
            get { return GetCachedOption("PerfStats", ref _perfStats); }
            set {
                CheckFrozen();
                Options["PerfStats"] = value;
            }
        }

        private bool GetCachedOption(string name, ref bool? storage) {
            if (storage.HasValue) {
                return storage.Value;
            }

            if (_frozen) {
                storage = GetOption<bool>(name, false);
                return storage.Value;
            }

            return GetOption<bool>(name, false);
        }

        internal void Freeze() {
            _frozen = true;

            _names = new ReadOnlyCollection<string>(ArrayUtils.MakeArray(_names));
            _fileExtensions = new ReadOnlyCollection<string>(ArrayUtils.MakeArray(_fileExtensions));
            _options = new ReadOnlyDictionary<string, object>(new Dictionary<string, object>(_options));
        }

        private void CheckFrozen() {
            if (_frozen) {
                throw new InvalidOperationException("Cannot modify LanguageSetup after it has been used to create a ScriptRuntime");
            }
        }        
    }
}
