/* **********************************************************************************
 *
 * 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.Generic;
using System.Collections;
using System.Text;
using System.Threading;
using System.Diagnostics;

using IronPython.Runtime;


[assembly: PythonModule("thread", typeof(IronPython.Modules.PythonThread))]
namespace IronPython.Modules {

    public static class PythonThread {
        #region Public API Surface
        public static object LockType = Ops.GetDynamicTypeFromType(typeof(Lock));
        public static object error = ExceptionConverter.GetPythonExceptionByName("ThreadError");


        [PythonName("start_new_thread")]
        public static object StartNewThread(object function, object args, [ParamDict]object kwDict) {
            Tuple tupArgs = args as Tuple;
            if (tupArgs == null) throw Ops.TypeError("2nd arg must be a tuple");
            
            Thread t = new Thread(new ThreadObj(function, tupArgs, kwDict).Start);
            t.Start();

            return t.ManagedThreadId;
        }

        [PythonName("start_new_thread")]
        public static object StartNewThread(object function, object args) {
            Tuple tupArgs = args as Tuple;
            if (tupArgs == null) throw Ops.TypeError("2nd arg must be a tuple");

            Thread t = new Thread(new ThreadObj(function, tupArgs, null).Start);
            t.Start();

            return t.ManagedThreadId;
        }

        [PythonName("interrupt_main")]
        public static void InterruptMain() {
            throw Ops.NotImplementedError("interrupt_main not implemented");
            //throw new PythonKeyboardInterrupt();
        }

        [PythonName("exit")]
        public static void Exit() {
            throw new PythonSystemExit();
        }

        [PythonName("allocate_lock")]
        public static object AllocateLock() {
            return new Lock();
        }

        [PythonName("get_ident")]
        public static object GetIdentity() {
            return Thread.CurrentThread.ManagedThreadId;
        }
        #endregion

        [PythonType("lock")]
        public class Lock {
            AutoResetEvent blockEvent;
            Thread curHolder;

            [PythonName("acquire")]
            public object Acquire() {
                return (Acquire(Ops.TRUE));
            }

            [PythonName("acquire")]
            public object Acquire(object waitflag) {
                bool fWait = Ops.IsTrue(waitflag);
                for (; ; ) {
                    if (Interlocked.CompareExchange<Thread>(ref curHolder, Thread.CurrentThread, null) == null) {
                        return Ops.TRUE;
                    }
                    if (!fWait) {
                        return Ops.FALSE;
                    }
                    if (blockEvent == null) {
                        // try again in case someone released us, checked the block
                        // event and discovered it was null so they didn't set it.
                        CreateBlockEvent();
                        continue;
                    }
                    blockEvent.WaitOne();
                }
            }

            [PythonName("release")]
            public void Release(params object[] param) {
                Release();
            }

            [PythonName("release")]
            public void Release() {
                if (Interlocked.Exchange<Thread>(ref curHolder, null) == null) {
                    Ops.Raise(error, "lock isn't held", null);
                }
                if (blockEvent != null) {
                    // if this isn't set yet we race, it's handled in Acquire()
                    blockEvent.Set();
                }
            }

            [PythonName("locked")]
            public object IsLocked() {
                return (Ops.Bool2Object(curHolder != null));
            }

            void CreateBlockEvent() {
                AutoResetEvent are = new AutoResetEvent(false);
                if (Interlocked.CompareExchange<AutoResetEvent>(ref blockEvent, are, null) != null) {
                    are.Close();
                }
            }
        }

        #region Internal Implementation details
        private class ThreadObj {
            object func, kwargs;
            Tuple args;
            public ThreadObj(object function, Tuple args, object kwargs) {
                Debug.Assert(args != null);
                func = function;
                this.kwargs = kwargs;                
                this.args = args;                
            }

            public void Start() {
                try {
                    if (kwargs != null) {
                        Ops.CallWithArgsTupleAndKeywordDict(func, new object[0], new string[0], args, kwargs);
                    } else {
                        Ops.CallWithArgsTuple(func, new object[0], args);
                    } 
                } catch (Exception e) {
                    Ops.Print("Unhandled exception on thread");
                    Ops.Print(e);
                }
            }
        }
        #endregion
    }
}
