/* ****************************************************************************
 *
 * 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 Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;
using System.Reflection;
using System.IO;
using System.Collections.ObjectModel;

namespace Microsoft.Scripting.Hosting {
    /// <summary>
    /// Stores information needed to setup a ScriptRuntime
    /// </summary>
    [Serializable]
    public sealed class ScriptRuntimeSetup {
        // host specification:
        private Type _hostType;
        private IList<object> _hostArguments;

        // languages available in the runtime: 
        private IList<LanguageSetup> _languageSetups;

        // DLR options:
        private bool _debugMode;
        private bool _privateBinding;

        // common language options:
        private IDictionary<string, object> _options;
        
        // true if the ScriptRuntimeSetup is no longer mutable because it's been
        // used to start a ScriptRuntime
        private bool _frozen;

        public ScriptRuntimeSetup() {
            _languageSetups = new List<LanguageSetup>();
            _options = new Dictionary<string, object>();
            _hostType = typeof(ScriptHost);
            _hostArguments = ArrayUtils.EmptyObjects;
        }

        /// <summary>
        /// The list of language setup information for languages to load into
        /// the runtime
        /// </summary>
        public IList<LanguageSetup> LanguageSetups {
            get { return _languageSetups; }
        }

        /// <summary>
        /// Indicates that the script runtime is in debug mode.
        /// This means:
        /// 
        /// 1) Symbols are emitted for debuggable methods (methods associated with SourceUnit).
        /// 2) Debuggable methods are emitted to non-collectable types (this is due to CLR limitations on dynamic method debugging).
        /// 3) JIT optimization is disabled for all methods
        /// 4) Languages may disable optimizations based on this value.
        /// </summary>
        public bool DebugMode {
            get { return _debugMode; }
            set {
                CheckFrozen();
                _debugMode = value; 
            }
        }

        /// <summary>
        /// Ignore CLR visibility checks
        /// </summary>
        public bool PrivateBinding {
            get { return _privateBinding; }
            set {
                CheckFrozen();
                _privateBinding = value; 
            }
        }

        /// <summary>
        /// Can be any derived class of ScriptHost. When set, it allows the
        /// host to override certain methods to control behavior of the runtime
        /// </summary>
        public Type HostType {
            get { return _hostType; }
            set {
                ContractUtils.RequiresNotNull(value, "value");
                ContractUtils.Requires(typeof(ScriptHost).IsAssignableFrom(value), "value", "Must be ScriptHost or a derived type of ScriptHost");
                CheckFrozen();
                _hostType = value;
            }
        }

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

        /// <summary>
        /// Arguments passed to the host type when it is constructed
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public IList<object> HostArguments {
            get {
                return _hostArguments;
            }
            set {
                ContractUtils.RequiresNotNull(value, "value");
                CheckFrozen();
                _hostArguments = value;
            }
        }

        internal DlrConfiguration ToConfiguration() {
            ContractUtils.Requires(_languageSetups.Count > 0, "ScriptRuntimeSetup must have at least one LanguageSetup");

            // prepare
            ReadOnlyCollection<LanguageSetup> setups = new ReadOnlyCollection<LanguageSetup>(ArrayUtils.MakeArray(_languageSetups));
            var hostArguments = new ReadOnlyCollection<object>(ArrayUtils.MakeArray(_hostArguments));
            var options = new ReadOnlyDictionary<string, object>(new Dictionary<string, object>(_options));            
            var config = new DlrConfiguration(_debugMode, _privateBinding, options);

            // validate
            foreach (var language in setups) {
                config.AddLanguage(
                    language.TypeName,
                    language.DisplayName,
                    language.Names,
                    language.FileExtensions,
                    language.Options
                );
            }

            // commit
            _languageSetups = setups;
            _options = options;
            _hostArguments = hostArguments;

            Freeze(setups);

            return config;
        }

        private void Freeze(ReadOnlyCollection<LanguageSetup> setups) {
            foreach (var language in setups) {
                language.Freeze();
            }

            _frozen = true;
        }

        private void CheckFrozen() {
            if (_frozen) {
                throw new InvalidOperationException("Cannot modify ScriptRuntimeSetup after it has been used to create a ScriptRuntime");
            }            
        }
        
        /// <summary>
        /// Reads setup from .NET configuration system (.config files).
        /// If there is no configuration available returns an empty setup.
        /// </summary>
        public static ScriptRuntimeSetup ReadConfiguration() {
#if SILVERLIGHT
            return new ScriptRuntimeSetup();
#else
            var setup = new ScriptRuntimeSetup();
            Configuration.Section.LoadRuntimeSetup(setup, null);
            return setup;
#endif
        }

#if !SILVERLIGHT
        /// <summary>
        /// Reads setup from a specified XML stream.
        /// </summary>
        public static ScriptRuntimeSetup ReadConfiguration(Stream configFileStream) {
            ContractUtils.RequiresNotNull(configFileStream, "configFileStream");
            var setup = new ScriptRuntimeSetup();
            Configuration.Section.LoadRuntimeSetup(setup, configFileStream);
            return setup;
        }

        /// <summary>
        /// Reads setup from a specified XML file.
        /// </summary>
        public static ScriptRuntimeSetup ReadConfiguration(string configFilePath) {
            ContractUtils.RequiresNotNull(configFilePath, "configFilePath");

            using (var stream = File.OpenRead(configFilePath)) {
                return ReadConfiguration(stream);
            }
        }
#endif        
    }
}
