// **********************************************************************
//
// Copyright (c) 2003-2016 ZeroC, Inc. All rights reserved.
//
// This copy of Ice is licensed to you under the terms described in the
// ICE_LICENSE file included in this distribution.
//
// **********************************************************************

namespace IceInternal
{
    using System.Diagnostics;
    using System.Collections.Generic;

    public sealed class ACMConfig : System.ICloneable
    {
        internal ACMConfig(bool server)
        {
            timeout = 60 * 1000;
            heartbeat = Ice.ACMHeartbeat.HeartbeatOnInvocation;
            close = server ? Ice.ACMClose.CloseOnInvocation : Ice.ACMClose.CloseOnInvocationAndIdle;
        }
        
        public ACMConfig(Ice.Properties p, Ice.Logger l, string prefix, ACMConfig dflt)
        {
            Debug.Assert(prefix != null);
            
            string timeoutProperty;
            if((prefix.Equals("Ice.ACM.Client") || prefix.Equals("Ice.ACM.Server")) &&
               p.getProperty(prefix + ".Timeout").Length == 0)
            {
                timeoutProperty = prefix; // Deprecated property.
            }
            else
            {
                timeoutProperty = prefix + ".Timeout";
            };
        
            timeout = p.getPropertyAsIntWithDefault(timeoutProperty, dflt.timeout / 1000) * 1000;

            int hb = p.getPropertyAsIntWithDefault(prefix + ".Heartbeat", (int)dflt.heartbeat);
            if(hb >= (int)Ice.ACMHeartbeat.HeartbeatOff && hb <= (int)Ice.ACMHeartbeat.HeartbeatAlways)
            {
                heartbeat = (Ice.ACMHeartbeat)hb;
            }
            else
            {
                l.warning("invalid value for property `" + prefix + ".Heartbeat" + 
                          "', default value will be used instead");
                heartbeat = dflt.heartbeat;
            }

            int cl = p.getPropertyAsIntWithDefault(prefix + ".Close", (int)dflt.close);
            if(cl >= (int)Ice.ACMClose.CloseOff && cl <= (int)Ice.ACMClose.CloseOnIdleForceful)
            {
                close = (Ice.ACMClose)cl;
            }
            else
            {
                l.warning("invalid value for property `" + prefix + ".Close" +
                          "', default value will be used instead");
                close = dflt.close;
            }
        }

        public object Clone()
        {
            return this.MemberwiseClone();
        }

        public int timeout;
        public Ice.ACMHeartbeat heartbeat;
        public Ice.ACMClose close;
    };

    public interface ACMMonitor : TimerTask
    {
        void add(Ice.ConnectionI con);
        void remove(Ice.ConnectionI con);
        void reap(Ice.ConnectionI con);
    
        ACMMonitor acm(Ice.Optional<int> timeout, Ice.Optional<Ice.ACMClose> c, Ice.Optional<Ice.ACMHeartbeat> h);
        Ice.ACM getACM();
    };

    class FactoryACMMonitor : ACMMonitor
    {
        internal class Change
        {
            internal Change(Ice.ConnectionI connection, bool remove)
            {
                this.connection = connection;
                this.remove = remove;
            }

            public readonly Ice.ConnectionI connection;
            public readonly bool remove;
        };

        internal FactoryACMMonitor(Instance instance, ACMConfig config)
        {
            _instance = instance;
            _config = config;
        }

        internal void destroy()
        {
            lock(this)
            {
                if(_instance == null)
                {
                    return;
                }
                _instance = null;
                _connections.Clear();
                _changes.Clear();
            }
        }

        public void add(Ice.ConnectionI connection)
        {
            if(_config.timeout == 0)
            {
                return;
            }

            lock(this)
            {
                if(_connections.Count == 0)
                {
                    _connections.Add(connection);
                    _instance.timer().scheduleRepeated(this, _config.timeout / 2);
                }
                else
                {
                    _changes.Add(new Change(connection, false));
                }
            }
        }

        public void remove(Ice.ConnectionI connection)
        {
            if(_config.timeout == 0)
            {
                return;
            }
        
            lock(this)
            {
                Debug.Assert(_instance != null);
                _changes.Add(new Change(connection, true));
            }
        }

        public void reap(Ice.ConnectionI connection)
        {
            lock(this)
            {
                _reapedConnections.Add(connection);
            }
        }

        public ACMMonitor acm(Ice.Optional<int> timeout, Ice.Optional<Ice.ACMClose> c, Ice.Optional<Ice.ACMHeartbeat> h)
        {
            Debug.Assert(_instance != null);
        
            ACMConfig config = (ACMConfig)_config.Clone();
            if(timeout.HasValue)
            {
                config.timeout = timeout.Value * 1000; // To milliseconds
            }
            if(c.HasValue)
            {
                config.close = c.Value;
            }
            if(h.HasValue)
            {
                config.heartbeat = h.Value;
            }
            return new ConnectionACMMonitor(this, _instance.timer(), config);
        }
    
        public Ice.ACM getACM()
        {
            Ice.ACM acm = new Ice.ACM();
            acm.timeout = _config.timeout / 1000;
            acm.close = _config.close;
            acm.heartbeat = _config.heartbeat;
            return acm;
        }
    
        internal List<Ice.ConnectionI> swapReapedConnections()
        {
            lock(this)
            {
                if(_reapedConnections.Count == 0)
                {
                    return null;
                }
                List<Ice.ConnectionI> connections = _reapedConnections;
                _reapedConnections = new List<Ice.ConnectionI>();
                return connections;
            }
        }

        public void runTimerTask()
        {
            lock(this)
            {
                if(_instance == null)
                {
                    return;
                }

                foreach(Change change in _changes)
                {
                    if(change.remove)
                    {
                        _connections.Remove(change.connection);
                    }
                    else
                    {
                        _connections.Add(change.connection);
                    }
                }
                _changes.Clear();

                if(_connections.Count == 0)
                {
                    _instance.timer().cancel(this);
                    return;
                }
            }
        
        
            //
            // Monitor connections outside the thread synchronization, so
            // that connections can be added or removed during monitoring.
            //
            long now = Time.currentMonotonicTimeMillis();
            foreach(Ice.ConnectionI connection in _connections)
            {
                try
                {          
                    connection.monitor(now, _config);
                }
                catch(System.Exception ex)
                {   
                    handleException(ex);
                }
            }
        }
    
        internal void handleException(System.Exception ex)
        {
            lock(this)
            {
                if(_instance == null)
                {
                    return;
                }        
                _instance.initializationData().logger.error("exception in connection monitor:\n" + ex);
            }
        }

        private Instance _instance;
        readonly private ACMConfig _config;

        private HashSet<Ice.ConnectionI> _connections = new HashSet<Ice.ConnectionI>();
        private List<Change> _changes = new List<Change>();
        private List<Ice.ConnectionI> _reapedConnections = new List<Ice.ConnectionI>();
    };

    internal class ConnectionACMMonitor : ACMMonitor
    {
        internal ConnectionACMMonitor(FactoryACMMonitor parent, Timer timer, ACMConfig config)
        {
            _parent = parent;
            _timer = timer;
            _config = config;
        }

        public void add(Ice.ConnectionI connection)
        {
            lock(this)
            {
                Debug.Assert(_connection == null);
                _connection = connection;
                if(_config.timeout > 0)
                {
                    _timer.scheduleRepeated(this, _config.timeout / 2);
                }
            }
        }

        public void remove(Ice.ConnectionI connection)
        {
            lock(this)
            {
                Debug.Assert(_connection == connection);
                _connection = null;
                if(_config.timeout > 0)
                {
                    _timer.cancel(this);
                }
            }
        }
    
        public void reap(Ice.ConnectionI connection)
        {
            _parent.reap(connection);
        }
    
        public ACMMonitor acm(Ice.Optional<int> timeout, Ice.Optional<Ice.ACMClose> c, Ice.Optional<Ice.ACMHeartbeat> h)
        {
            return _parent.acm(timeout, c, h);
        }
    
        public Ice.ACM getACM()
        {
            Ice.ACM acm = new Ice.ACM();
            acm.timeout = _config.timeout / 1000;
            acm.close = _config.close;
            acm.heartbeat = _config.heartbeat;
            return acm;
        }
    
        public void runTimerTask()
        {
            Ice.ConnectionI connection;
            lock(this)
            {
                if(_connection == null)
                {
                    return;
                }
                connection = _connection;
            }
        
            try
            {          
                connection.monitor(Time.currentMonotonicTimeMillis(), _config);
            }
            catch(System.Exception ex)
            {   
                _parent.handleException(ex);
            }
        }

        readonly private FactoryACMMonitor _parent;
        readonly private Timer _timer;
        readonly private ACMConfig _config;

        private Ice.ConnectionI _connection;
    };
}
