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

using System;
using System.Collections;
using System.Collections.Generic;

using System.Diagnostics;
using System.Reflection;
using System.Text;

using System.Threading;

using IronPython.Compiler;

namespace IronPython.Runtime {

    [PythonType(typeof(PythonModule))]
    public class SystemState : ICustomAttributes {
        internal Encoding DefaultEncoding = Encoding.ASCII;
        private static Dict originalDictionary;

        private IDictionary<SymbolId,object> __dict__ = new FieldIdDict();
        private object exception_type, exception_value, exception_traceback;

        public SystemState() {
            Initialize();

            originalDictionary = TypeCache.SystemState.GetAttrDict(DefaultContext.Default, this).Clone() as Dict;
        }

        /// <summary>
        /// Performs sys's initialization - in it's own function so we can do reload(sys)
        /// </summary>
        public void Initialize() {
            __dict__[SymbolTable.Name] = "sys";            

            stdin = __stdin__;
            stdout = __stdout__;
            stderr = __stderr__;

            // removed from dictionary after the first call to set it.
            MethodInfo mi = typeof(SystemState).GetMethod("setdefaultencodingImpl", 
                System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);

            __dict__[SymbolTable.SetDefaultEncoding] = BuiltinFunction.Make("setdefaultencoding", mi, new MethodInfo[] { mi }, FunctionType.PythonVisible);

            copyright = "Copyright (c) Microsoft Corporation. All rights reserved.";

            warnoptions = List.Make();

            hexversion = 0x02040000;

            maxint = Int32.MaxValue;

            maxunicode = (int)ushort.MaxValue;

            platform = Ops.ToPython("cli");

            version_info = Tuple.MakeTuple(2, 4);

            if (originalDictionary != null) {
                // restore our dictionary to the original form.  Note if the user
                // changed individual fields (e.g. ps1) we don't update their values.
                // but that's ok, neither does CPython.  Also if they added fields we
                // don't clear them, also by design.
                Dict curDict = TypeCache.SystemState.GetAttrDict(DefaultContext.Default, this);

                foreach(KeyValuePair<object, object> kvp in originalDictionary){
                    curDict[kvp.Key] = kvp.Value;
                }
            }
        }

        public override string ToString() {
            return "<module 'sys' (built-in)>";
        }

        [PythonName("argv")]
        public object argv = Ops.MakeList();

        public string byteorder = BitConverter.IsLittleEndian ? "little" : "big";

        [PythonName("builtin_module_names")]
        public Tuple builtin_module_names;

        [PythonName("copyright")]
        public string copyright;

        [PythonName("warnoptions")]
        public List warnoptions;

        // as of 1.5 preferred access is exc_info, these may be null.
        public object exc_type {
            [PythonName("exc_type")]
            get {
                return exception_type;
            }
            set {
                exception_type = value;
            }
        }

        public object exc_value {
            [PythonName("exc_value")]
            get {
                return exception_value;
            }
            set {
                exception_value = value;
            }
        }

        public object exc_traceback {
            [PythonName("exc_traceback")]
            get {
                return exception_traceback;
            }
            set {
                exception_traceback = value;
            }
        }

        [ThreadStatic]
        internal static Exception RawException;

        [ThreadStatic]
        internal static TraceBack RawTraceBack;

        internal void SetRawException(Exception raw) {
            RawException = raw;

            // force update of non-thread static exception info...
            exc_info();
        }

        internal void ClearException() {
            SystemState.RawException = null;
            SystemState.RawTraceBack = null;
        }

        [PythonName("exc_info")]
        public Tuple exc_info() {
            if (RawException == null)  return Tuple.MakeTuple(null, null, null);
            object pyExcep = ExceptionConverter.ToPython(RawException);

            if(Options.TracebackSupport && RawTraceBack != null){
                RawTraceBack.UpdateFromStackTrace(new System.Diagnostics.StackTrace(RawException, true));
            }

            exc_traceback = RawTraceBack;

            if (pyExcep is StringException) {
                // string exceptions are special...  there tuple looks
                // like string, argument, traceback instead of
                //      type,   instance, traceback
                StringException se = RawException as StringException;
                Debug.Assert(se != null);

                exc_type = pyExcep;
                exc_value = se.Value;
                
                return Ops.MakeTuple(
                    pyExcep,
                    se.Value,
                    RawTraceBack);
            } else {
                object excType = Ops.GetAttr(DefaultContext.Default, pyExcep, SymbolTable.Class);
                exc_type = excType;
                exc_value = pyExcep;

                return Ops.MakeTuple(
                    excType,
                    pyExcep,
                    RawTraceBack);
            }            
        }

        [PythonName("exec_prefix")]
        public string exec_prefix;
        [PythonName("executable")]
        public object executable;

        [PythonName("exit")]
        public void exit() {
            throw ExceptionConverter.CreateThrowable(ExceptionConverter.GetPythonExceptionByName("SystemExit"));
        }

        [PythonName("exit")]
        public void exit(object code) {
            // throw as a python exception here to get the args set.
            throw ExceptionConverter.CreateThrowable(ExceptionConverter.GetPythonExceptionByName("SystemExit"), code);
        }

        [PythonName("getdefaultencoding")]
        public string getdefaultencoding() {
            return DefaultEncoding.WebName.ToLower().Replace('-','_');
        }

        [PythonName("getfilesystemencoding")]
        public object getfilesystemencoding() {
            return null;
        }

        [PythonName("_getframe")]
        public object _getframe() {
            throw Ops.ValueError("_getframe is not implemented");
        }

        [PythonName("_getframe")]
        public object _getframe(int depth) {
            throw Ops.ValueError("_getframe is not implemented");
        }

        [PythonName("hexversion")]
        public int hexversion;

        [PythonName("maxint")]
        public int maxint;
        [PythonName("maxunicode")]
        public object maxunicode;

        [PythonName("modules")]
        public IDictionary<object, object> modules = new Dict();

        [PythonName("path")]
        public List path = List.Make();

        [PythonName("platform")]
        public object platform;

        [PythonName("prefix")]
        public string prefix;

        [PythonName("ps1")]
        public object ps1 = Ops.ToPython(">>> ");
        [PythonName("ps2")]
        public object ps2 = Ops.ToPython("... ");

        private static object setdefaultencodingImpl(object name) {
            if(name == null) throw Ops.TypeError("name cannot be None");
            string strName = name as string;
            if (strName == null) throw Ops.TypeError("name must be a string");

            Encoding enc;
            if(!StringOps.TryGetEncoding(strName, out enc)){
                throw Ops.LookupError("'{0}' does not match any available encodings", strName);
            }

            Ops.State.DefaultEncoding = enc;
            Ops.State.__dict__.Remove(SymbolTable.SetDefaultEncoding); 
            return null;
        }
        
        [PythonName("setrecursionlimit")]
        public void SetRecursionLimit(int limit) {
            PythonFunction.EnforceRecursion = (limit != Int32.MaxValue);
            PythonFunction.MaximumDepth = limit;
        }

        [PythonName("getrecursionlimit")]
        public object GetRecursionLimit() {
            return Ops.Int2Object(PythonFunction.MaximumDepth);
        }

        [PythonName("stdin")]
        public object stdin;
        [PythonName("stdout")]
        public object stdout;
        [PythonName("stderr")]
        public object stderr;

        [PythonName("__stdin__")]
        public object __stdin__ = new PythonFile(
            Console.OpenStandardInput(),
            Console.InputEncoding, 
            "<stdin>", 
            "r");
        [PythonName("__stdout__")]
        public object __stdout__ = new PythonFile(
            Options.UnbufferedStdOutAndError ? Console.OpenStandardOutput(0) : Console.OpenStandardOutput(),
            Console.OutputEncoding, 
            "<stdout>", 
            "w");
        [PythonName("__stderr__")]
        public object __stderr__ = new PythonFile(
            Options.UnbufferedStdOutAndError ? Console.OpenStandardError(0) : Console.OpenStandardError(), 
            Console.OutputEncoding, 
            "<stderr>", 
            "w");

        [PythonName("version")]
        public string version;

        [PythonName("version_info")]
        public object version_info;

        #region ICustomAttributes Members

        public bool TryGetAttr(ICallerContext context, SymbolId name, out object value) {
            if (TypeCache.SystemState.TryGetAttr(context, this, name, out value)) return true;
            return __dict__.TryGetValue(name, out value);
        }

        public void SetAttr(ICallerContext context, SymbolId name, object value) {
            object dummy;
            if (TypeCache.SystemState.TryGetAttr(context, this, name, out dummy)) {
                TypeCache.SystemState.SetAttr(context, this, name, value);
            } else {
                __dict__[name] = value;
            }
        }

        public void DeleteAttr(ICallerContext context, SymbolId name) {
            object dummy;
            if (TypeCache.SystemState.TryGetAttr(context, this, name, out dummy)) {
                try {
                    TypeCache.SystemState.SetAttr(context, this, name, new Uninitialized((string)SymbolTable.IdToString(name)));
                } catch {
                    throw new NotImplementedException("deleting from sys");
                }
            } else {
                __dict__.Remove(name);
            }
        }
                
        public List GetAttrNames(ICallerContext context) {
            List ret = new List(((IDictionary<object,object>)__dict__).Keys);
            ret.Extend(TypeCache.SystemState.GetAttrNames(context, this));
            return ret;
        }

        public IDictionary<object, object> GetAttrDict(ICallerContext context) {
            Dict res = new Dict(__dict__);
            Dict append = TypeCache.SystemState.GetAttrDict(context, this);
            foreach (KeyValuePair<object, object> pair in append) {
                res.Add(pair);
            }
            return res;
        }

        #endregion

        [PythonName("LoadAssemblyByName")]
        public void LoadAssemblyByName(string name) {
            throw Ops.Warning("sys.LoadAssemblyByName has been deprecated and will be removed in the next release. Please use clr.AddReference* methods instead.");
        }

        [PythonName("LoadAssemblyFromFile")]
        public void LoadAssemblyFromFile(string filename) {
            throw Ops.Warning("sys.LoadAssemblyFromFile has been deprecated and will be removed in the next release. Please use clr.AddReference* methods instead.");
        }        
    }
}
