namespace InABox.IPC
{
    public abstract class RPCServerTransport<TConnection> : IRPCServerTransport where TConnection : notnull
    {
        
        private Dictionary<TConnection, RPCServerSession> _sessions = new Dictionary<TConnection, RPCServerSession>();

        protected RPCServerSession CreateSession(TConnection connection)
        {
            var session = new RPCServerSession();
            _sessions[connection] = session;
            return session;
        }

        protected RPCServerSession? GetSession(TConnection? connection)
        {
            if (connection == null)
                return null;
            _sessions.TryGetValue(connection, out RPCServerSession? session);
            return session;
        }

        protected RPCServerSession? DeleteSession(TConnection connection)
        {
            _sessions.Remove(connection, out RPCServerSession? session);
            return session;
        }
        
        private Dictionary<String, IRPCCommandHandler> _handlers = new Dictionary<String, IRPCCommandHandler>();

        public void AddHandler<TSender, TCommand, TProperties, TResult>(RPCCommandHandler<TSender, TProperties, TResult> handler)
            where TCommand : IRPCCommand<TProperties, TResult>
            where TSender : class
        {
            _handlers[typeof(TCommand).Name] = handler;
        }
        
        public abstract void Start();

        public abstract void Stop();
        
        public event RPCTransportOpenEvent? OnOpen;

        protected void DoOpen(TConnection connection)
        {
            var session = CreateSession(connection);
            OnOpen?.Invoke(this, new RPCTransportOpenArgs(session) );
        }

        public event RPCTransportCloseEvent? OnClose;

        protected void DoClose(TConnection connection , RPCTransportCloseEventType type)
        {
            var session = GetSession(connection);
            OnClose?.Invoke(this, new RPCTransportCloseArgs(session, type));
        }

        public event RPCTransportExceptionEvent? OnException;
        protected void DoException(TConnection? connection, Exception e)
        {
            var session = GetSession(connection);
            OnException?.Invoke(this, new RPCTransportExceptionArgs(session, e));
        }

        public event RPCTransportMessageEvent? BeforeMessage;

        protected void DoBeforeMessage(RPCServerSession? session, RPCMessage? message)
        {
            BeforeMessage?.Invoke(this, new RPCTransportMessageArgs(session,message));
        }

        public event RPCTransportMessageEvent? AfterMessage;
        protected void DoAfterMessage(RPCServerSession? session, RPCMessage? message)
        {
            AfterMessage?.Invoke(this, new RPCTransportMessageArgs(session,message));
        }
        
        public RPCMessage? DoMessage(TConnection? connection, RPCMessage? message)
        {
            var session = GetSession(connection);
            DoBeforeMessage(session,message);
            RPCMessage? response = null;
            if (session != null)
            {
                if (message != null)
                {
                    response = new RPCMessage(message.ID, message.Command, "");

                    if (_handlers.TryGetValue(message.Command, out var command))
                    {
                        try
                        {
                            response.Payload = command.Execute(message.Payload) ?? "";
                        }
                        catch (Exception err)
                        {
                            DoException(connection, err);
                            response.Error = RPCError.SERVERERROR;
                        }
                    }
                    else
                    {
                        DoException(connection, new Exception("Command Not Found"));
                        response.Error = RPCError.COMMANDNOTFOUND;
                    }

                    DoAfterMessage(session, response);
                }
                else
                    DoException(connection, new Exception("NULL Message Received"));
            }
            else
                DoException(connection, new Exception("Session not Found"));

            return response;
        }
        
    }
}