namespace FBPLib
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Threading;
    using System.Text.RegularExpressions;
    using System.Reflection;

    /// <summary> All verbs must extend this class, defining its three abstract methods
    /// <code>openPorts<code>, <code>introspect<code>, and <code>execute</code>.
    /// *
    /// There will be an instance of this class for every node in the Network.
    /// *
    /// </summary>


    public abstract class Component
    {
        /* *
         * Copyright 2007, 2008, J. Paul Morrison.  At your option, you may copy, 
         * distribute, or make derivative works under the terms of the Clarified Artistic License, 
         * based on the Everything Development Company's Artistic License.  A document describing 
         * this License may be found at http://www.jpaulmorrison.com/fbp/artistic2.htm. 
         * THERE IS NO WARRANTY; USE THIS PRODUCT AT YOUR OWN RISK.
         * */
        private string _name;
        private void InitBlock()
        {
            _inputPorts = new Dictionary<string, IInputPort>();
            _outputPorts = new Dictionary<string, OutputPort>();
            _inputPortAttrs = new Dictionary<string, InPort>();
            _outputPortAttrs = new Dictionary<string, OutPort>();
            _status = States.NotStarted;
            _stack = new Stack();
            _packetCount = 0;
            MustRun = false;
            SelfStarting = false;
            _timeout = null;
            //_event_dormant = new AutoResetEvent(false);
            _compLog = new Logger();

        }

        public virtual string Name
        {
            get
            {
                return _name;
            }

            set
            {
                _name = value;
            }

        }
        internal virtual int PacketCount
        {
            get
            {
                return _packetCount;
            }

        }
        internal virtual States Status
        {
            get
            {
                return _status;
            }
            set { _status = value; }

        }
        internal virtual TimeoutHandler Timeout
        {
            get
            {
                return _timeout;
            }
            set { _timeout = value; }

        }
        internal virtual Type Type
        {
            get
            {
                return _type;
            }
            set { _type = value; }

        }

        protected internal System.Threading.Thread _thread;

        // All the input ports are stored here, keyed by name.        
        internal Dictionary<string, IInputPort> _inputPorts;

        // All the output ports are stored here, keyed by name.        
        internal Dictionary<string, OutputPort> _outputPorts;

        // Input port attributes
        internal Dictionary<string, InPort> _inputPortAttrs;

        // Output port attributes
        internal Dictionary<string, OutPort> _outputPortAttrs;

        /// <summary> This is a stack which is made available to each Component.
        /// Rather than declaring a special API, we make it <code>protected</code>
        /// so that the Component can use the regular java.util.Stack API.
        /// *
        /// </summary>

        protected Stack _stack;
        internal enum States
        {
            NotStarted, Active, Dormant, SuspRecv, SuspSend,
            Terminated, LongWait
        }

        States _status;

        // This is the automatic input port named "*IN"
        internal IInputPort _autoInput;

        // This is the automatic output port named "*OUT"
        internal OutputPort _autoOutput;

        // This is a count of packets owned by this Component.
        // Whenever the Component deactivates, the count must be zero.
        internal int _packetCount;

        internal Type _type;

        // internal bool terminating;   no longer needed  
        internal bool MustRun;
        internal bool SelfStarting;

        // internal long packetNumber = 0; //debugging only
        //  internal Hashtable ownedPackets = null; //debugging only

        internal Network _network;

        TimeoutHandler _timeout = null;
        //AutoResetEvent _event_dormant;

        internal Logger _compLog;
        //object _lockObject;

        internal Component _mother;

        public Component()
        {
            InitBlock();

        }
        internal static Component NewComponent(Type type, string name, Network network)
        {
            ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
            Component cp = (Component)ci.Invoke(null);
            cp._type = type;
            cp._name = name;
            cp._network = network;

            Attribute[] attributes = Attribute.GetCustomAttributes(type);
            foreach (object at in attributes)
            {
                if (at is MustRun)
                {
                    MustRun mr = at as MustRun;
                    cp.MustRun = mr.value;
                }
                if (at is OutPort)
                {
                    OutPort op = at as OutPort;
                    cp._outputPortAttrs.Add(op.name, op);
                }
                if (at is InPort)
                {
                    InPort ip = at as InPort;
                    cp._inputPortAttrs.Add(ip.name, ip);
                }
            }
            return cp;
        }

        /// <summary>This method is called from other parts of the system
        /// to activate this Component if it needs to be.
        /// This will start its thread if needed, and if already
        /// started, will Pulse() it.
        /// </summary>
        //UPGRADE_NOTE: Synchronized keyword was removed from method 'activate'. Lock expression was added. 'ms-help://MS.VSCC/commoner/redir/redirect.htm?keyword="jlca1027"'
        internal virtual void Activate()
        {
            lock (this)
            {

                if (isTerminated())
                    return;

                if (_thread == null)
                {

                    _thread = new Thread(new ThreadStart(ThreadMain));
                    _thread.Name = Name;
                }
                if (!_thread.IsAlive)
                    _thread.Start();

                else
                    lock ((_inputPorts as ICollection).SyncRoot)
                    {
                        if (Status == States.Dormant)

                            //_event_dormant.Set();
                            Monitor.Pulse((_inputPorts as ICollection).SyncRoot);
                    }

            }
        }
        /// <summary> This method creates a new Packet using a type and string, say for brackets
        /// </summary>
        /// <returns>javaflow.Packet
        /// </returns>
        /// <param name="s">java.lang.String
        /// 
        /// </param>
        public virtual Packet Create(Packet.Types newType, string s)
        {
            return new Packet(newType, s, this);
        }
        /// <summary> This method creates a Packet pointing at an Object
        /// </summary>
        /// <returns>javaflow.Packet
        /// </returns>
        /// <param name="o">java.lang.Object
        /// 
        /// </param>
        public virtual Packet Create(System.Object o)
        {
            return new Packet(o, this);
        }
        internal void CheckOwner(Packet p)
        {
            if (this != p._owner)
                FlowError.Complain("Packet not owned by current Component");
        }

        /**
         * Pop from stack. Return null if empty.
        **/
        Packet Pop()
        {
            Packet p;
            if (_stack.Count > 0)
                p = (Packet)_stack.Pop();
            else
                p = null;
            return p;
        }

        /**
        * Push onto stack.
        **/
        void Push(Packet p)
        {
            _stack.Push(p);
        }

        /* void TestIIPsClosed()
        {
            foreach (IInputPort i in _inputPorts.Values)
            {
                if (i is InitializationConnection)
                {
                    InitializationConnection ic = i as InitializationConnection;
                    if (!ic.IsClosed()) { FlowError.Complain("IIP " + ic.Name + " not closed"); }
                }
            }
        }
        */

        /// <summary> Verbs override this method with their program.
        /// This method is called from a private thread.
        /// </summary>
        /// <exception cref="">Throwable if there is any error or exception in the program;
        /// this is a kludge until we work out how to integrate exceptions properly.
        /// *
        /// </exception>

        public abstract void Execute();
        /// <summary> Return Network object
        /// </summary>
        /// <returns>Network
        /// 
        /// </returns>
        //internal Network getNetwork() {
        //	return Network;
        //}
        /// <summary> Obtain status of Component
        /// </summary>
        /// <returns>int
        /// 
        /// </returns>
        /// <summary> Verbs override this method to provide information about their
        /// ports to a <cite>Flowbox</cite>.  Although the result is
        /// declared as an array of <code>Object</code>s, it must actually contain a
        /// <code>string</code> describing the verb as a whole, followed by a
        /// sequence of <i>descriptor</i>s with one per port,
        /// where a <i>descriptor</i> is represented by four <code>Object</code>s
        /// in a row.  These are: the port name (as a <code>string</code>);
        /// the port type (one of <code>input</code>, <code>input[]</code>, <code>output</code>,
        /// <code>output[]</code>, or <code>parameter</code>);
        /// the Java type of objects passing through the port (either a <code>Class</code>
        /// or <code>null</code>, which means the same as <code>Object.class</code>);
        /// a human-readable description (a <code>string</code>)
        /// suitable for use in the sentence <cite>"This port contains ..."</cite>.
        /// *
        /// </summary>

        public abstract System.Object[] Introspect();
        /// <summary> This method returns <code> true </code> if the Component has terminated
        /// </summary>
        /// <returns>boolean
        /// 
        /// </returns>
        internal virtual bool isTerminated()
        {
            return _status == States.Terminated;
        }
        /// <summary> Verbs call this method from their <code>openPorts</code> method to
        /// open an IInputPort, either a regular port or a parameter port.
        /// </summary>
        /// <param name="name">the name of the IInputPort
        /// </param>
        /// <returns>the IInputPort, which should be assigned to an
        /// instance variable of the verb
        /// *
        /// </returns>

        protected internal IInputPort OpenInput(string name)
        {
            if (name.StartsWith("*"))
                FlowError.Complain("Attempt to open * port: " + this + ' ' + name);
            //IInputPort port = (IInputPort)(inputPorts[name]);
            if (!_inputPorts.ContainsKey(name))
                return new NullConnection();
            IInputPort port = (IInputPort)(_inputPorts[name]);
            //if (port == null)
            // return NullConnection.singleton;
            return port;
        }
        /// <summary> Verbs call this method from their <code>openPorts</code> method to
        /// open an array of inputPorts.  The size of the Array is determined
        /// by the Network to which this Component belongs, and thus the
        /// verb does not know it until <code>execute</code> time.
        /// </summary>
        /// <param name="the">name of the IInputPort array
        /// </param>
        /// <returns>the IInputPort[] object, which should be assigned to an instance variable.
        /// *
        /// </returns>

        // Array ports are stored individually in the inputPorts
        // and outputPorts variables with names like NAME[X], where
        // X is the index number.

        protected internal IInputPort[] OpenInputArray(string name)
        {

            IInputPort[] array = GetPortArray(_inputPorts, name);
            for (int i = 0; i < array.Length; i++)
                if (array[i] == null)
                {
                    NullConnection nc = new NullConnection();
                    nc.Name = Name + "." + name + "[" + i + "]";
                    array[i] = nc;
                }
            return array;

        }
        /// <summary> Verbs call this method from their <code>openPorts</code> method
        /// to open an output port.
        /// </summary>
        /// <param name="name">the name of the OutputPort
        /// </param>
        /// <returns>the OutputPort, which should be assigned to an instance variable
        /// *
        /// </returns>

        protected internal OutputPort OpenOutput(string name)
        {
            if (name.StartsWith("*"))
                FlowError.Complain("Attempt to open * port");
            OutputPort port = null;
            if (_outputPorts.ContainsKey(name))
            {
                port = (OutputPort)(_outputPorts[name]);
            }
            else
            {
                port = new OutputPort();
                port.Name = Name + "." + name;
            }

            return port;
        }


        /// <summary> Verbs call this method from their <code>openPorts</code> method to
        /// open an array of OutputPorts.  The size of the Array is determined
        /// by the Network to which this Component belongs, and thus the
        /// verb does not know it until <code>execute</code> time.
        /// </summary>
        /// <param name="the">name of the OutputPort array
        /// </param>
        /// <returns>the OutputPort[] object, which should be assigned to an instance variable.
        /// *
        /// </returns>

        // See openInputArray.
        protected internal OutputPort[] OpenOutputArray(string name)
        {

            OutputPort[] array = GetPortArray(_outputPorts, name);
            for (int i = 0; i < array.Length; i++)
                if (array[i] == null)
                {
                    array[i] = new NullOutputPort();
                    array[i].Sender = this;
                    array[i].Name = Name + "." + name + "[" + i + "]";
                }
            return array;
        }
        // Get array of ports in the form name[]
        T[] GetPortArray<T>(Dictionary<string, T> ports, string name)
        {
            T[] ret = new T[0];
            Regex re = new Regex(@"^(\w+)\[(\d+)\]$");

            foreach (KeyValuePair<string, T> kvp in ports)
            {
                Match m = re.Match(kvp.Key);
                if (m.Success)
                {
                    string s = m.Groups[1].Value;
                    if (!s.Equals(name)) continue;
                    int subs = Convert.ToInt32(m.Groups[2].Value);
                    if (subs < 0 || subs >= 1000)
                        FlowError.Complain("bad subscript " + name);
                    if (subs >= ret.Length)
                        Array.Resize(ref ret, subs + 1);
                    ret[subs] = kvp.Value;
                }

            }
            return ret;
        }

        
        /// <summary>Drop Packet and clear owner reference.
        /// Note: Java will not reclaim the space until after all references
        /// to this packet have been cleared - ideally, one should not use this
        /// knowledge, but bright people probably will!
        /// </summary>
        public void Drop(Packet p)
        {
            CheckOwner(p);
            p.ClearOwner();
        }

        /// <summary> Verbs override this method to open their ports.
        /// This method is called from the Network's main thread,
        /// and should only call <code>openInput</code>, <code>openInputArray</code>,
        /// <code>openOutput</code>, <code>openOutputArray<code>, <code>IInputPort.setType</code>,
        /// and <code>OutputPort.setType</code>.
        /// *
        /// </summary>

        public abstract void OpenPorts();


        /// <summary>
        /// Nested class to get state of all ports as an object, thread safe.
        /// </summary>
        /// 
        class InputStates
        {
            // true if all connected input ports are closed and empty
            internal bool allDrained = true;

            // true if any connected input port has data
            internal bool hasData = false;


            // Get state of all ports

            internal InputStates(Dictionary<string, IInputPort> inports)
            {
                lock (inports)
                {
                    foreach (IInputPort inp in inports.Values)
                        if (inp is Connection)
                        {
                            Connection c = inp as Connection;
                            lock (c)
                            {
                                allDrained &= c.IsDrained();
                                hasData |= !c.IsEmpty();
                            }
                        }
                }
            }
        }
        // Use SetPriority in Execute, not OpenPorts!
        public void SetPriority(ThreadPriority p) { _thread.Priority = p; }

        public void LongWaitStart(double dur)
        {
            Timeout = new TimeoutHandler(dur, this);
        }

        public void LongWaitEnd()
        {
            Timeout.Dispose();
        }

        //UPGRADE_TODO: The equivalent of method java.lang.Runnable.run is not an override method. 'ms-help://MS.VSCC/commoner/redir/redirect.htm?keyword="jlca5065"'
        // internal void Run()
        // implement mainline for thread

        // runs and keeps running as long as there are input ports to read
        void ThreadMain()
        {
            // Component Enclosing_Instance = this;
            _status = States.Active;

            _compLog.Trace("{0}: Started", Name);

            if (!_inputPorts.ContainsKey("*IN"))
                _autoInput = null;
            else
                _autoInput = (IInputPort)(_inputPorts["*IN"]);
            if (!_outputPorts.ContainsKey("*OUT"))
                _autoOutput = null;
            else
                _autoOutput = (OutputPort)(_outputPorts["*OUT"]);

            if (_autoInput != null)
            {
                Packet p = _autoInput.Receive();
                Drop(p);
                _autoInput.Close();
            }


            try
            {
                InputStates ist = new InputStates(_inputPorts);

                while (!ist.allDrained || MustRun || SelfStarting)
                {
                    _packetCount = 0;


                    _compLog.Trace("{0}: Activated", Name);

                    Execute(); // do one activation!

                    _compLog.Trace("{0}: Deactivated", Name);

                    if (_packetCount != 0)
                    {
                        Console.Out.WriteLine(Name + " deactivated holding " + _packetCount + " packets");

                        FlowError.Complain(_packetCount + " packets not disposed of during Component activation of " + Name);
                    }
                    foreach (IInputPort port in _inputPorts.Values)
                    {
                        if (port is InitializationConnection)
                        {
                            InitializationConnection icx = port as InitializationConnection;
                            if (!icx.IsClosed())
                                FlowError.Complain("Component deactivated with IIP port not closed: " + icx.Name);
                        }
                    }
                    MustRun = false;
                    if (SelfStarting) break;
                    lock ((_inputPorts as ICollection).SyncRoot)
                    {
                        ist = new InputStates(_inputPorts);
                        if (ist.allDrained)
                            break;
                        if (!ist.hasData)
                        {
                            _status = States.Dormant;

                            _compLog.Trace("{0}: Dormant", Name);
                            //lock (this)
                            //{
                            Monitor.Wait((_inputPorts as ICollection).SyncRoot);
                            //_event_dormant.Reset();
                            //_event_dormant.WaitOne();
                            Status = States.Active;
                            _compLog.Trace("{0}: Active", Name);
                            //}

                            ist = new InputStates(_inputPorts);
                            // if (ist.allDrained) break;  // redundant
                        }
                    }
                } //  while (!ist.allDrained);   


                _compLog.Trace("{0}: Terminating", Name);
            }
            catch (System.Exception t)
            {
                //Console.Out.WriteLine("*** Exception detected in " + Name);
                System.Diagnostics.Trace.Fail("*** Exception detected in " + Name + ": " + t.Message);
            }


            _compLog.Trace("{0}: Terminated", Name);
            if (_autoOutput != null)
            {
                Packet p = Create("");
                _autoOutput.Send(p);
            }
            _status = States.Terminated;

            if (_stack.Count > 0)
                FlowError.Complain("Stack not empty at component termination: " + Name);

            foreach (IInputPort port in _inputPorts.Values)
            {
                if (port is Connection)
                {
                    Connection cx = port as Connection;
                    if (cx.Count() > 0)
                        Console.Out.WriteLine("{0}: Component terminated with {1} packets in input connection", cx.Name, cx.Count());
                    while (cx.Count() > 0)
                    {
                        Packet p = cx._buffer.Take();
                        Console.Out.WriteLine(p);
                    }


                }
                if (port is InitializationConnection)
                {
                    InitializationConnection iip = port as InitializationConnection;
                    if (!(iip.IsClosed()))
                        FlowError.Complain("Component terminated with input port not closed: " + iip.Name);
                }
            }

            foreach (OutputPort port in _outputPorts.Values)
            {
                port.Close();
            }

            _status = States.Terminated; //will not be set if never activated
            _network.NotifyTerminated();
        }
    }
}

// }
