namespace FBPLib
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Reflection;
    using System.IO;
    using System.Threading;
    using Spring.Threading.Helpers;   // CountDownLatch
    using System.Text.RegularExpressions;
    using FBPLib.Properties;

    /// <summary> The abstract class which all flow Networks extend directly or
    /// indirectly.  A specific flow Network must override the <code>define()</code>
    /// method, which is written using the <i>mini-language</i>
    /// (actually just highly restricted Java invoking the <code>protected</code>
    /// methods of this class).  The mini-language specifies what
    /// threads are to be created using which components, and what
    /// connections are established between the ports of those components.
    /// Here is a sample: 
    /// <pre>
    /// connect(component("Read", typeof(ReadText)),
    ///		port("OUT"),
    ///		component("Write", typeof(WriteText)),
    ///		port("IN"));
    /// Object d = (Object) "Data\\Testing.txt";
    /// initialize(d,
    ///		component("Read"),
    ///		port("SOURCE"));
    /// Stream st = Console.OpenStandardOutput();
    ///	initialize(st,
    ///		component("Write"),
    ///		port("DESTINATION"));
    /// </pre>
    /// The effect of the above is to create two threads named <code>Read</code>
    /// (an instance of <code>ReadText</code>) and <code>Write</code> (an instance of
    /// <code>WriteText</code>, connect the <code>OUT</code> port of the former to
    /// the <code>IN</code> port of the latter, and initialize the <code>SOURCE</code>
    /// and <code>DESTINATION</code> parameter ports with suitable Java objects
    /// for reading the standard input and writing the standard output.
    /// *
    /// </summary>

    public abstract class Network : 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.
           * */
        public void InitBlock()
        {
            _components = new Dictionary<string, Component>();
            Network network = this;

            // Get the directory our exe is running from.
            string strPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
            strPath = strPath.Substring(0, strPath.LastIndexOf('\\'));
            //AssemblyName assemblyName = new AssemblyName();
            Type[] types, types2;
            _alltypes = new Type[0];

            // Search for all DLLs in the folder and try to extract their interface.
            foreach (string strFile in Directory.GetFiles(strPath, "*.dll"))
            {
                // Open the class library.
                Assembly engine = Assembly.LoadFrom(strFile);

                //assemblyName.CodeBase = /*assemblyPath +*/ lib;
                // engine = Assembly.Load(assemblyName);
                types = engine.GetTypes();
                int j = -1;
                types2 = new Type[_alltypes.Length];
                foreach (Type t in _alltypes)
                {
                    j++;
                    types2[j] = t;
                }
                _alltypes = new Type[types2.Length + types.Length];
                int i = -1;
                foreach (Type t in types2)
                {
                    i++;
                    _alltypes[i] = t;
                }

                foreach (Type t in types)
                {
                    i++;
                    _alltypes[i] = t;
                }
            }
        }

        protected internal static int DEBUGSIZE = 1;

        protected internal static int PRODUCTIONSIZE = 10;

        // int defaultCapacity = DEBUGSIZE;       // change this when you go to production
        internal static int _defaultCapacity = PRODUCTIONSIZE; // use this one for production

        //UPGRADE_NOTE: The initialization of  'components' was moved to method 'InitBlock'. 'ms-help://MS.VSCC/commoner/redir/redirect.htm?keyword="jlca1005"'
        internal Dictionary<string, Component> _components;

        internal bool _active = true;   // for deadlock detection   

        internal Dictionary<Component, TimeoutHandler> _timeouts = new Dictionary<Component, TimeoutHandler>();

        Thread _mainthread = null;

        public CountDownLatch _cdl;

        //static internal string _netname;

        public object[] parms;

        Type[] _alltypes;

        //int _discardCount = 0;

        // return object to use for synchronising state changes
        internal object StateSynchRoot
        {
            get { return (_components as ICollection).SyncRoot; }
        }

        /// <summary>Drive define method of Network
        /// </summary>

        internal virtual void CallDefine()
        {
            //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to ' ' which has different behavior. 'ms-help://MS.VSCC/commoner/redir/redirect.htm?keyword="jlca1100"'
            try
            {
                Define();
            }
            catch (System.Exception t)
            {
                FlowError.Complain(t.Message);
            }
        }

        /// <summary>Returns a Component class object, given the Component name in the Network
        /// </summary>		
        protected internal Component Component(System.String name)
        {
            if (!_components.ContainsKey(name))
                FlowError.Complain("Reference to unknown Component " + name);
            Component c = (Component)(_components[name]);

            c.Name = name;
            return c;
        }
        /// <summary>Stores the Component class object with its Network name in the Hashtable called
        /// 'components'
        /// </summary>		
        protected internal Component Component(System.String name, System.Type type)
        {
            if (_components.ContainsKey(name))
                FlowError.Complain("Attempt to redefine Component " + name);
            Component c = null;
            //UPGRADE_NOTE: Exception 'java.lang.InstantiationException' was converted to ' ' which has different behavior. 'ms-help://MS.VSCC/commoner/redir/redirect.htm?keyword="jlca1100"'
            Type foundType = null;
            foreach (Type t in _alltypes)
            {
                if ((t.FullName == type.FullName) & (t.IsClass))
                {
                    foundType = t;
                    break;
                }
            }
            if (foundType == null)
                FlowError.Complain("Component not found: " + type.FullName);

            try
            {
                //ConstructorInfo cnst = foundType.GetConstructor(Type.EmptyTypes /*new Type[0] */);
                //c = (Component)(cnst.Invoke(null /* new Object[0] */));
                c = NewComponent(foundType, name, this);
            }
            catch (System.UnauthorizedAccessException e)
            {
                FlowError.Complain("Illegal access to Component" + type.FullName);
                return null; // unreachable
            }
            catch (System.Exception e)
            {
                FlowError.Complain("Cannot instantiate Component" + type.FullName);
                return null; // unreachable
            }
            c.Name = name;
            c.Type = foundType;
            c._mother = this;
            _components.Add(name, c);
            //++_comp_count;
            c._network = this;
            return c;


        }

        protected internal void Connect(string sender, string receiver)
        {
            string s1, s2, r1, r2;
            int i, j;
            if ((i = sender.IndexOf('.')) == -1)
                FlowError.Complain("Invalid sender name: " + sender);
            if ((j = receiver.IndexOf('.')) == -1)
                FlowError.Complain("Invalid receiver name:" + receiver);
            s1 = sender.Substring(0, i);  // start, length
            s2 = sender.Substring(i + 1);  // start
            r1 = receiver.Substring(0, j);  // start, length
            r2 = receiver.Substring(j + 1);  // start

            Connect(Component(s1), Port(s2),
               Component(r1), Port(r2));
        }
        /// <summary>Connect an output port of one Component to an input port
        /// of another, using default capacity
        /// </summary>
        protected internal void Connect(Component sender, System.String outName, Component receiver, System.String inName)
        {

            Connect(sender, outName, receiver, inName, 0);

        }
        /// <summary>Connect an output port of one Component to an input port
        /// of another, specifying a connection capacity
        /// </summary>

        protected internal void Connect(Component sender, System.String outName, Component receiver, System.String inName, int size)
        {
            int cap = size;
            if (size == 0)
                cap = _defaultCapacity;

            if (outName.Equals("*"))
                outName = "*OUT";
            if (inName.Equals("*"))
                inName = "*IN";

            if (sender._outputPorts.ContainsKey(outName))
                FlowError.Complain("Multiple connections to output port:" + sender.Name + ' ' + outName);

            Regex re = new Regex(@"^(\w+)\[");  // test for square bracket
            Match m;
            Connection cx = null;

            if (receiver._inputPorts.ContainsKey(inName))
                if (!(receiver._inputPorts[inName] is Connection))
                    FlowError.Complain("Input port cannot have IIP as well as connection: " +
                        receiver.Name + "." + inName);
                else
                {
                    cx = receiver._inputPorts[inName] as Connection;
                    goto ok;
                }

            if (inName.Substring(0, 1).Equals("*"))
                cx = new Connection(cap);
            else
            {
                m = re.Match(inName);  // does it have square bracket in the name?
                if (!m.Success)    // no
                {
                    if (!receiver._inputPorts.ContainsKey(inName))
                    {
                        if (!receiver._inputPortAttrs.ContainsKey(inName) ||
                         receiver._inputPortAttrs[inName].arrayPort)
                            FlowError.Complain("Input port not specified correctly in attributes: " +
                                receiver.Name + "." + inName);
                        receiver._inputPortAttrs.Remove(inName);

                        cx = new Connection(cap);
                    }
                }
                else                // yes
                {
                    string s = m.Groups[1].Value;
                    if (!receiver._inputPortAttrs.ContainsKey(s) ||
                        !receiver._inputPortAttrs[s].arrayPort)
                        FlowError.Complain("Input port not specified correctly in attributes: " +
                               receiver.Name + "." + inName);
                    cx = new Connection(cap);
                    receiver._inputPortAttrs[s].used = true;

                }
            }
            cx.setReceiver(receiver);
            cx.Name = receiver.Name + "." + inName;
            receiver._inputPorts.Add(inName, cx);

        ok:
            if (size != 0 && size != cap)
                FlowError.Complain("Connection capacity does not agree with previous specification");

            // Now start handling output port
            OutputPort op = null;
            if (sender._outputPorts.ContainsKey(outName))
            {
                op = sender._outputPorts[outName];
                cx.BumpSenderCount(op);
                goto ok2;
            }

            if (!outName.Substring(0, 1).Equals("*"))
            {
                m = re.Match(outName);  // does it have square bracket in the name?
                if (!m.Success)
                {     //no
                    if (!sender._outputPortAttrs.ContainsKey(outName) ||
                         sender._outputPortAttrs[outName].arrayPort)
                        FlowError.Complain("Output port not specified correctly in attributes: " +
                            sender.Name + "." + outName);
                    sender._outputPortAttrs.Remove(outName);
                }
                else                   //yes
                {
                    string s = m.Groups[1].Value;
                    if (!sender._outputPortAttrs.ContainsKey(s) ||
                        !sender._outputPortAttrs[s].arrayPort)
                        FlowError.Complain("Output port not specified correctly in attributes: " +
                               sender.Name + "." + outName);
                    sender._outputPortAttrs[s].used = true;
                }
            }

            op = new OutputPort();
            sender._outputPorts.Add(outName, op);
            cx.BumpSenderCount(op);

        ok2:
            op._cnxt = cx;
            op.Sender = sender;
            op.Name = sender.Name + "." + outName;
        }


        public abstract void Define();

        /// <summary>Execute method used by Network being used as if it were a Component
        /// </summary>
        public override void Execute()
        {
            /*
            Packet ss = substreamSensitive.Receive();
            if (ss == null)
            {
                CallDefine();
                Initiate();
                Packet p;
                while ((p = _inport.Receive()) != null)
                {
                    netsrc.Send(p);
                }

                WaitForAll();

            }
            else
            {
                ss.Drop(this);
                int depth = 0;
                Packet p;
                while ((p = _inport.Receive()) != null)
                {
                    if (p._type == Packet.Types.Open)
                        depth++;
                    else if (p._type == Packet.Types.Close)
                        depth--;
                    if (depth == 0)
                    {
                        CallDefine();
                        Initiate();
                        // CopyToOutput();
                    }
                    netsrc.Send(p);
                    if (depth == 0)
                    {
                        netsrc.Close();
                        WaitForAll();
                    }
                }
            }
             * */
        }


        /// <summary>Execute Network as a whole
        /// </summary>
        [MTAThreadAttribute]   // this the default, so not really necessary
        public void Go()
        {
            Type t = this.GetType();
            Name = t.FullName;

            DateTime now = DateTime.Now;

            InitBlock();
            _mainthread = new Thread(delegate()
            {
                CallDefine();

                _cdl = new CountDownLatch(_components.Count);


                _compLog.Trace(Name + ": run started");
                _active = true;

                Initiate();

                WaitForAll();

                TimeSpan duration = DateTime.Now - now;

                _compLog.Trace("{0} - run time: {1}", Name, duration);

            });

            _mainthread.Start();
        }

        protected internal void Initialize(object param, string receiver)
        {
            string r1, r2;
            int i;
            if ((i = receiver.IndexOf('.')) == -1)
                FlowError.Complain("invalid receiver name" + receiver);
            r1 = receiver.Substring(0, i);  // start, length
            r2 = receiver.Substring(i + 1);  // start

            Initialize(param, Component(r1), Port(r2));
        }
        /// <summary>Build InitializationConnection object
        /// </summary>

        protected internal void Initialize(System.Object content, Component receiver, System.String inName)
        {
            //if (inName.Equals("*"))
            //    inName = "*IN";

            Regex re = new Regex(@"^(\w+)\[");
            Match m;
            //Connection cx = null;

            if (receiver._inputPorts.ContainsKey(inName))
                FlowError.Complain("IIp port cannot be shared: " +
                    receiver.Name + "." + inName);

            m = re.Match(inName);  // does it have square bracket in the name?
            if (!m.Success)    // no
            {
                if (!receiver._inputPorts.ContainsKey(inName))
                {
                    if (!receiver._inputPortAttrs.ContainsKey(inName) ||
                     receiver._inputPortAttrs[inName].arrayPort)
                        FlowError.Complain("Input port not specified correctly in attributes: " +
                            receiver.Name + "." + inName);
                    receiver._inputPortAttrs.Remove(inName);
                }
            }
            else                // yes
            {
                string s = m.Groups[1].Value;
                if (!receiver._inputPortAttrs.ContainsKey(s) ||
                    !receiver._inputPortAttrs[s].arrayPort)
                    FlowError.Complain("Input port not specified correctly in attributes: " +
                           receiver.Name + "." + inName);
                receiver._inputPortAttrs[s].used = true;

            }

            InitializationConnection ic = new InitializationConnection(content, receiver);
            receiver._inputPorts[inName] = ic;
            ic.Name = receiver.Name + "." + inName;
            //ic.network = this;

        }
        /// <summary>Go through components activating any that can be activated
        /// </summary>
        internal virtual void Initiate()
        {

            foreach (Component c in _components.Values)
            {
                foreach (string s in c._outputPortAttrs.Keys)
                    if (!c._outputPortAttrs[s].used)
                        FlowError.Complain("Output port not defined in connect statement: " + c.Name + "." + s);

                c.OpenPorts();
            }

            foreach (Component c in _components.Values)
            {
                c.SelfStarting = true;
                bool start = true;

                foreach (IInputPort port in c._inputPorts.Values)
                {
                    if (port is Connection)
                    {
                        Connection cnxt = (Connection)port;
                        cnxt.CheckTypes();
                        if (-1 == cnxt.Name.IndexOf("*"))
                            c.SelfStarting = false;
                        start = false;
                        break;
                    }
                }
                if (start || c.MustRun)
                    c.Activate();
            }


        }
        public override System.Object[] Introspect()
        {
            return new System.Object[] { "operates a general Network defining a structure of " + "components connected by their ports", "IN", "input", typeof(System.Object), "general input to the whole Network", "OUT", "output", typeof(System.Object), "general output from the whole Network", "SUBSTRING-SENSITIVE", "parameter", null, "makes input substring-sensitive if present" };
        }
        /*
        /// <summary>method establishing input connection for subnet
        /// </summary>
        protected internal void NetworkInput(Component receiver, System.String inName)
        {
            Connect(this, "*NETSOURCE", receiver, inName);
            netsrc = OpenOutput("*NETSOURCE");
        }
        /// <summary>method establishing output connection for subnet
        /// </summary>
        protected internal void NetworkOutput(Component sender, System.String outName)
        {
            Connect(sender, outName, this, "*NETSINK");
            netdst = OpenInput("*NETSINK");
        }
         * */

        /// <summary>method to open ports for subnet
        /// </summary>
        public override void OpenPorts()
        {
            /*
            _inport = OpenInput("IN");
            _outport = OpenOutput("OUT");
            substreamSensitive = OpenInput("SUBSTREAM-SENSITIVE");
             * */
        }


        /// <summary>method to register a port name  
        /// </summary>

        protected internal System.String Port(System.String name)
        {
            if (name.Equals("*"))
                return name;
            else if (name.IndexOf((System.Char)'*') != -1)
            {
                FlowError.Complain("Stray * in port name");
                return name; // unreachable
            }
            else
                return name;
        }
        /// <summary>method to register a port (with index) 
        /// </summary>

        protected internal System.String Port(System.String name, int index)
        {
            // return "*" + name + "*" + (char)index;
            return string.Format("{0}[{1}]", name, index);
        }


        /// <summary>Test if Network as a whole has terminated
        /// </summary>

        internal virtual void WaitForAll()
        {
            bool possibleDeadlock = false;
            bool deadlock = false;

            int freq = 500;   // check every .5 second
            Settings s = Settings.Default;
            while (true)
            {
                if (_cdl.Await(new TimeSpan(0, 0, 0, 0, freq)))   // 500 msecs 
                    break;  // if CountDownLatch finished, exit

                if (s.DeadlockTestEnabled)
                {
                    TestTimeouts(freq);
                    if (!_active)  // else interval elapsed
                        if (!possibleDeadlock)
                            possibleDeadlock = true;
                        else
                            if (!DeadlockTest())
                            {
                                possibleDeadlock = false;
                                continue;
                            }
                            else
                            {
                                deadlock = true;
                                break;
                            }

                    _active = false;
                }
            }

            if (deadlock) InterruptAll();
            else
            {

                foreach (Component c in _components.Values)
                { c._thread.Join(); }

                //if (outputCopier != null)
                //   outputCopier.Join();
            }

        }
        bool DeadlockTest()
        {
            List<string> msgs = new List<string>();
            // Messages are added to list, rather than written directly,
            //   in case it is not a deadlock
            msgs.Add("Network has deadlocked");
            lock (StateSynchRoot)
            {
                foreach (Component comp in _components.Values)
                {
                    if (comp.Status == States.Active ||
                        comp.Status == States.LongWait) return false;
                    msgs.Add(String.Format("--- {1,-13} -- {0}", comp.Name, comp.Status));
                }
                foreach (string m in msgs)
                    Console.Out.WriteLine(m);
                //FlowError.Complain("Deadlock detected");
                System.Diagnostics.Trace.Fail("*** Deadlock detected in Network ");
                return true;
            }
        }
        // called by WaitForAll method
        void TestTimeouts(int freq)
        {
            lock (_timeouts)
            {
                foreach (TimeoutHandler t in _timeouts.Values)
                {
                    t.Decrement(freq);
                }
            }
        }
        internal virtual void InterruptAll()
        {

            IDictionaryEnumerator all = _components.GetEnumerator();
            while (all.MoveNext())
            {
                Component c = (Component)(all.Value);
                if (!c.isTerminated())
                {
                    c._thread.Interrupt();
                    Console.Out.WriteLine(c.Name + " interrupted");
                }

            }
        }
        internal void NotifyTerminated()
        {
            // lock (StateSynchRoot)
            // {
            //   foreach (Component comp in _components.Values)
            //   {
            //      if (comp.Status != States.Terminated)
            //          return;
            //  }
            //_mainthread.Interrupt();  
            _cdl.CountDown();
            // }
        }


    }
}