/*
 
Copyright (c) 2017 Ahmed Kh. Zamil

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/

using Esiur.Data;
using Esiur.Core;
using Esiur.Net.Packets;
using Esiur.Resource;
using Esiur.Resource.Template;
using Esiur.Security.Authority;
using Esiur.Security.Permissions;
using System;
using System.Collections.Generic;

using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;

namespace Esiur.Net.IIP;

partial class DistributedConnection
{
    KeyList<uint, DistributedResource> resources = new KeyList<uint, DistributedResource>();
    KeyList<uint, AsyncReply<DistributedResource>> resourceRequests = new KeyList<uint, AsyncReply<DistributedResource>>();
    KeyList<Guid, AsyncReply<TypeTemplate>> templateRequests = new KeyList<Guid, AsyncReply<TypeTemplate>>();


    KeyList<string, AsyncReply<IResource>> pathRequests = new KeyList<string, AsyncReply<IResource>>();

    Dictionary<Guid, TypeTemplate> templates = new Dictionary<Guid, TypeTemplate>();

    KeyList<uint, AsyncReply> requests = new KeyList<uint, AsyncReply>();

    volatile uint callbackCounter = 0;

    //List<IResource> subscriptions = new List<IResource>();
    Dictionary<IResource, List<byte>> subscriptions = new Dictionary<IResource, List<byte>>();// new List<IResource>();


    internal KeyList<IResource, DateTime> cache = new();

    object subscriptionsLock = new object();

    AsyncQueue<DistributedResourceQueueItem> queue = new AsyncQueue<DistributedResourceQueueItem>();

    /// <summary>
    /// Send IIP request.
    /// </summary>
    /// <param name="action">Packet action.</param>
    /// <param name="args">Arguments to send.</param>
    /// <returns></returns>
    internal SendList SendRequest(IIPPacket.IIPPacketAction action)
    {
        var reply = new AsyncReply<object[]>();
        var c = callbackCounter++; // avoid thread racing
        requests.Add(c, reply);

        return (SendList)SendParams(reply).AddUInt8((byte)(0x40 | (byte)action)).AddUInt32(c);
    }

    /*
    internal IAsyncReply<object[]> SendRequest(IIPPacket.IIPPacketAction action, params object[] args)
    {
        var reply = new AsyncReply<object[]>();
        callbackCounter++;
        var bl = new BinaryList((byte)(0x40 | (byte)action), callbackCounter);
        bl.AddRange(args);
        Send(bl.ToArray());
        requests.Add(callbackCounter, reply);
        return reply;
    }
    */

    //uint maxcallerid = 0;

    internal SendList SendReply(IIPPacket.IIPPacketAction action, uint callbackId)
    {
        /*
        if (callbackId > maxcallerid)
        {
            maxcallerid = callbackId;
        }
        else
        {
            Console.Beep();

        }
        */

        return (SendList)SendParams().AddUInt8((byte)(0x80 | (byte)action)).AddUInt32(callbackId);
    }

    internal SendList SendEvent(IIPPacket.IIPPacketEvent evt)
    {
        return (SendList)SendParams().AddUInt8((byte)(evt));
    }

    internal AsyncReply SendListenRequest(uint instanceId, byte index)
    {
        var reply = new AsyncReply<object>();
        var c = callbackCounter++;
        requests.Add(c, reply);

        SendParams().AddUInt8((byte)(0x40 | (byte)Packets.IIPPacket.IIPPacketAction.Listen))
                    .AddUInt32(c)
                    .AddUInt32(instanceId)
                    .AddUInt8(index)
                    .Done();

        return reply;
    }

    internal AsyncReply SendUnlistenRequest(uint instanceId, byte index)
    {
        var reply = new AsyncReply<object>();
        var c = callbackCounter++;
        requests.Add(c, reply);

        SendParams().AddUInt8((byte)(0x40 | (byte)Packets.IIPPacket.IIPPacketAction.Unlisten))
                    .AddUInt32(c)
                    .AddUInt32(instanceId)
                    .AddUInt8(index)
                    .Done();

        return reply;
    }


    internal AsyncReply<object> SendInvoke(uint instanceId, byte index, Map<byte, object> parameters)
    {
        var pb = Codec.Compose(parameters, this);// Codec.ComposeVarArray(parameters, this, true);

        var reply = new AsyncReply<object>();
        var c = callbackCounter++;
        requests.Add(c, reply);

        SendParams().AddUInt8((byte)(0x40 | (byte)Packets.IIPPacket.IIPPacketAction.InvokeFunction))
                    .AddUInt32(c)
                    .AddUInt32(instanceId)
                    .AddUInt8(index)
                    .AddUInt8Array(pb)
                    .Done();

        //var bl = new BinaryList((byte)(0x40 | (byte)Packets.IIPPacket.IIPPacketAction.InvokeFunctionArrayArguments),
        //                        callbackCounter, instanceId, index, pb);
        //Send(bl.ToArray());

        return reply;
    }

    internal AsyncReply<object[]> SendDetachRequest(uint instanceId)
    {
        try
        {
            return SendRequest(IIPPacket.IIPPacketAction.DetachResource).AddUInt32(instanceId).Done();
        }
        catch
        {
            return null;
        }
    }

    void SendError(ErrorType type, uint callbackId, ushort errorCode, string errorMessage = "")
    {
        var msg = DC.ToBytes(errorMessage);
        if (type == ErrorType.Management)
            SendParams()
                        .AddUInt8((byte)(0xC0 | (byte)IIPPacket.IIPPacketReport.ManagementError))
                        .AddUInt32(callbackId)
                        .AddUInt16(errorCode)
                        .Done();
        else if (type == ErrorType.Exception)
            SendParams()
                        .AddUInt8((byte)(0xC0 | (byte)IIPPacket.IIPPacketReport.ExecutionError))
                        .AddUInt32(callbackId)
                        .AddUInt16(errorCode)
                        .AddUInt16((ushort)msg.Length)
                        .AddUInt8Array(msg)
                        .Done();
    }

    void SendProgress(uint callbackId, int value, int max)
    {
        SendParams()
            .AddUInt8((byte)(0xC0 | (byte)IIPPacket.IIPPacketReport.ProgressReport))
            .AddUInt32(callbackId)
            .AddInt32(value)
            .AddInt32(max)
            .Done();
        //SendParams(, callbackId, value, max);
    }

    void SendChunk(uint callbackId, object chunk)
    {
        var c = Codec.Compose(chunk, this);
        SendParams()
            .AddUInt8((byte)(0xC0 | (byte)IIPPacket.IIPPacketReport.ChunkStream))
            .AddUInt32(callbackId)
            .AddUInt8Array(c)
            .Done();
    }

    void IIPReply(uint callbackId, params object[] results)
    {
        var req = requests.Take(callbackId);
        req?.Trigger(results);
    }

    void IIPReplyInvoke(uint callbackId, TransmissionType transmissionType, byte[] content)
    {
        var req = requests.Take(callbackId);

        var (_, parsed) = Codec.Parse(content, 0, this, null, transmissionType);
        parsed.Then((rt) =>
        {
            req?.Trigger(rt);
        });
    }

    void IIPReportError(uint callbackId, ErrorType errorType, ushort errorCode, string errorMessage)
    {
        var req = requests.Take(callbackId);
        req?.TriggerError(new AsyncException(errorType, errorCode, errorMessage));
    }

    void IIPReportProgress(uint callbackId, ProgressType type, int value, int max)
    {
        var req = requests[callbackId];
        req?.TriggerProgress(type, value, max);
    }

    void IIPReportChunk(uint callbackId, TransmissionType dataType, byte[] data)
    {
        if (requests.ContainsKey(callbackId))
        {
            var req = requests[callbackId];
            var (_, parsed) = Codec.Parse(data, dataType.Offset, this, null, dataType);
            parsed.Then((x) =>
            {
                req.TriggerChunk(x);
            });
        }
    }

    void IIPEventResourceReassigned(uint resourceId, uint newResourceId)
    {

    }

    void IIPEventResourceDestroyed(uint resourceId)
    {
        if (resources.Contains(resourceId))
        {
            var r = resources[resourceId];
            resources.Remove(resourceId);
            r.Destroy();
        }
    }

    void IIPEventPropertyUpdated(uint resourceId, byte index, TransmissionType dataType, byte[] data)
    {

        Fetch(resourceId, null).Then(r =>
        {
            var item = new AsyncReply<DistributedResourceQueueItem>();
            queue.Add(item);

            var (_, parsed) = Codec.Parse(data, dataType.Offset, this, null, dataType);// 0, this);
            parsed.Then((arguments) =>
            {
                var pt = r.Instance.Template.GetPropertyTemplateByIndex(index);
                if (pt != null)
                {
                    item.Trigger(new DistributedResourceQueueItem((DistributedResource)r,
                                                    DistributedResourceQueueItem.DistributedResourceQueueItemType.Propery,
                                                    arguments, index));
                }
                else
                {    // ft found, fi not found, this should never happen
                    queue.Remove(item);
                }
            });

        });

        /*
        if (resources.Contains(resourceId))
        {
            // push to the queue to gaurantee serialization
            var reply = new AsyncReply<DistributedResourceQueueItem>();
            queue.Add(reply);

            var r = resources[resourceId];
            Codec.Parse(content, 0, this).Then((arguments) =>
            {
                if (!r.IsAttached)
                {
                    // property updated before the template is received
                    r.AddAfterAttachement(reply, 
                                            new DistributedResourceQueueItem((DistributedResource)r, 
                                                             DistributedResourceQueueItem.DistributedResourceQueueItemType.Propery, 
                                                             arguments, index));
                }
                else
                {
                    var pt = r.Instance.Template.GetPropertyTemplate(index);
                    if (pt != null)
                    {
                        reply.Trigger(new DistributedResourceQueueItem((DistributedResource)r, 
                                                        DistributedResourceQueueItem.DistributedResourceQueueItemType.Propery, 
                                                        arguments, index));
                    }
                    else
                    {    // ft found, fi not found, this should never happen
                        queue.Remove(reply);
                    }
                }
            });
        }
        */
    }


    void IIPEventEventOccurred(uint resourceId, byte index, TransmissionType dataType, byte[] data)
    {
        Fetch(resourceId, null).Then(r =>
        {
            // push to the queue to gaurantee serialization
            var item = new AsyncReply<DistributedResourceQueueItem>();
            queue.Add(item);

            var (_, parsed) = Codec.Parse(data, dataType.Offset, this, null, dataType);//, 0, this);
            parsed.Then((arguments) =>
            {
                var et = r.Instance.Template.GetEventTemplateByIndex(index);
                if (et != null)
                {
                    item.Trigger(new DistributedResourceQueueItem((DistributedResource)r,
                                  DistributedResourceQueueItem.DistributedResourceQueueItemType.Event, arguments, index));
                }
                else
                {    // ft found, fi not found, this should never happen
                    queue.Remove(item);
                }

            });
        });

        /*
        if (resources.Contains(resourceId))
        {
            // push to the queue to gaurantee serialization
            var reply = new AsyncReply<DistributedResourceQueueItem>();
            var r = resources[resourceId];

            queue.Add(reply);

            Codec.ParseVarArray(content, this).Then((arguments) =>
            {
                if (!r.IsAttached)
                {
                    // event occurred before the template is received
                    r.AddAfterAttachement(reply,
                                            new DistributedResourceQueueItem((DistributedResource)r,
                                      DistributedResourceQueueItem.DistributedResourceQueueItemType.Event, arguments, index));
                }
                else
                {
                    var et = r.Instance.Template.GetEventTemplate(index);
                    if (et != null)
                    {
                        reply.Trigger(new DistributedResourceQueueItem((DistributedResource)r, 
                                      DistributedResourceQueueItem.DistributedResourceQueueItemType.Event, arguments, index));
                    }
                    else
                    {    // ft found, fi not found, this should never happen
                        queue.Remove(reply);
                    }
                }
            });
        }
        */
    }

    void IIPEventChildAdded(uint resourceId, uint childId)
    {
        Fetch(resourceId, null).Then(parent =>
        {
            Fetch(childId, null).Then(child =>
            {
                parent.children.Add(child);
                child.parents.Add(parent);

                //parent.Instance.Children.Add(child);
            });
        });
    }

    void IIPEventChildRemoved(uint resourceId, uint childId)
    {
        Fetch(resourceId, null).Then(parent =>
        {
            Fetch(childId, null).Then(child =>
            {
                parent.children.Remove(child);
                child.parents.Remove(parent);

                //                    parent.Instance.Children.Remove(child);
            });
        });
    }

    void IIPEventRenamed(uint resourceId, string name)
    {
        Fetch(resourceId, null).Then(resource =>
        {
            resource.Instance.Variables["name"] = name;
        });
    }


    void IIPEventAttributesUpdated(uint resourceId, byte[] attributes)
    {
        Fetch(resourceId, null).Then(resource =>
        {
            var attrs = attributes.GetStringArray(0, (uint)attributes.Length);

            GetAttributes(resource, attrs).Then(s =>
            {
                resource.Instance.SetAttributes(s);
            });
        });
    }

    void IIPRequestAttachResource(uint callback, uint resourceId)
    {

        Warehouse.GetById(resourceId).Then((res) =>
        {
            if (res != null)
            {
                if (res.Instance.Applicable(session, ActionType.Attach, null) == Ruling.Denied)
                {
                    SendError(ErrorType.Management, callback, 6);
                    return;
                }

                var r = res as IResource;

                // unsubscribe
                Unsubscribe(r);

                //r.Instance.ResourceEventOccurred -= Instance_EventOccurred;
                //r.Instance.CustomResourceEventOccurred -= Instance_CustomEventOccurred;
                //r.Instance.ResourceModified -= Instance_PropertyModified;
                //r.Instance.ResourceDestroyed -= Instance_ResourceDestroyed;


                // r.Instance.Children.OnAdd -= Children_OnAdd;
                // r.Instance.Children.OnRemoved -= Children_OnRemoved;

                //r.Instance.Attributes.OnModified -= Attributes_OnModified;

                // Console.WriteLine("Attach {0} {1}", r.Instance.Link, r.Instance.Id);

                // add it to attached resources so GC won't remove it from memory
                ///attachedResources.Add(r);

                var link = DC.ToBytes(r.Instance.Link);

                if (r is DistributedResource)
                {
                    // reply ok
                    SendReply(IIPPacket.IIPPacketAction.AttachResource, callback)
                        .AddGuid(r.Instance.Template.ClassId)
                        .AddUInt64(r.Instance.Age)
                        .AddUInt16((ushort)link.Length)
                        .AddUInt8Array(link)
                        //.AddUInt8Array(DataSerializer.PropertyValueArrayComposer((r as DistributedResource)._Serialize(), this, true))
                        .AddUInt8Array(Codec.Compose((r as DistributedResource)._Serialize(), this))
                        .Done();
                }
                else
                {
                    // reply ok
                    SendReply(IIPPacket.IIPPacketAction.AttachResource, callback)
                        .AddGuid(r.Instance.Template.ClassId)
                        .AddUInt64(r.Instance.Age)
                        .AddUInt16((ushort)link.Length)
                        .AddUInt8Array(link)
                        .AddUInt8Array(Codec.Compose(r.Instance.Serialize(), this))
                        //.AddUInt8Array(DataSerializer.PropertyValueArrayComposer(r.Instance.Serialize(), this, true))
                        .Done();
                }



                // subscribe
                //r.Instance.ResourceEventOccurred += Instance_EventOccurred;
                //r.Instance.CustomResourceEventOccurred += Instance_CustomEventOccurred;
                //r.Instance.ResourceModified += Instance_PropertyModified;
                //r.Instance.ResourceDestroyed += Instance_ResourceDestroyed;

                Subscribe(r);

                //r.Instance.Children.OnAdd += Children_OnAdd;
                //r.Instance.Children.OnRemoved += Children_OnRemoved;

                //r.Instance.Attributes.OnModified += Attributes_OnModified;


            }
            else
            {
                // reply failed
                //SendParams(0x80, r.Instance.Id, r.Instance.Age, r.Instance.Serialize(false, this));
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
            }
        });
    }

    private void Attributes_OnModified(string key, object oldValue, object newValue, KeyList<string, object> sender)
    {
        if (key == "name")
        {
            var instance = (sender.Owner as Instance);
            var name = DC.ToBytes(newValue.ToString());
            SendEvent(IIPPacket.IIPPacketEvent.ChildRemoved)
                    .AddUInt32(instance.Id)
                    .AddUInt16((ushort)name.Length)
                    .AddUInt8Array(name)
                    .Done();
        }
    }

    private void Children_OnRemoved(Instance sender, IResource value)
    {
        SendEvent(IIPPacket.IIPPacketEvent.ChildRemoved)
            .AddUInt32(sender.Id)
            .AddUInt32(value.Instance.Id)
            .Done();
    }

    private void Children_OnAdd(Instance sender, IResource value)
    {
        //if (sender.Applicable(sender.Resource, this.session, ActionType.))
        SendEvent(IIPPacket.IIPPacketEvent.ChildAdded)
            .AddUInt32(sender.Id)
            .AddUInt32(value.Instance.Id)
            .Done();
    }


    public bool RemoveChild(IResource parent, IResource child)
    {
        SendEvent(IIPPacket.IIPPacketEvent.ChildRemoved)
            .AddUInt32((parent as DistributedResource).Id)
            .AddUInt32((child as DistributedResource).Id)
            .Done();

        return true;
    }

    public bool AddChild(IResource parent, IResource child)
    {
        SendEvent(IIPPacket.IIPPacketEvent.ChildAdded)
            .AddUInt32((parent as DistributedResource).Id)
            .AddUInt32((child as DistributedResource).Id)
            .Done();

        return true;
    }


    void IIPRequestReattachResource(uint callback, uint resourceId, ulong resourceAge)
    {
        Warehouse.GetById(resourceId).Then((res) =>
        {
            if (res != null)
            {
                var r = res as IResource;
                // unsubscribe
                Unsubscribe(r);
                Subscribe(r);

                //r.Instance.ResourceEventOccurred -= Instance_EventOccurred;
                //r.Instance.CustomResourceEventOccurred -= Instance_CustomEventOccurred;
                //r.Instance.ResourceModified -= Instance_PropertyModified;
                //r.Instance.ResourceDestroyed -= Instance_ResourceDestroyed;

                //r.Instance.Children.OnAdd -= Children_OnAdd;
                //r.Instance.Children.OnRemoved -= Children_OnRemoved;

                //r.Instance.Attributes.OnModified -= Attributes_OnModified;

                // subscribe
                //r.Instance.ResourceEventOccurred += Instance_EventOccurred;
                //r.Instance.CustomResourceEventOccurred += Instance_CustomEventOccurred;
                //r.Instance.ResourceModified += Instance_PropertyModified;
                //r.Instance.ResourceDestroyed += Instance_ResourceDestroyed;

                //r.Instance.Children.OnAdd += Children_OnAdd;
                //r.Instance.Children.OnRemoved += Children_OnRemoved;

                //r.Instance.Attributes.OnModified += Attributes_OnModified;

                // reply ok
                SendReply(IIPPacket.IIPPacketAction.ReattachResource, callback)
                        .AddUInt64(r.Instance.Age)
                        .AddUInt8Array(Codec.Compose(r.Instance.Serialize(), this))
                            .Done();
            }
            else
            {
                // reply failed
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
            }
        });
    }

    void IIPRequestDetachResource(uint callback, uint resourceId)
    {
        Warehouse.GetById(resourceId).Then((res) =>
        {
            if (res != null)
            {
                //var r = res as IResource;
                //r.Instance.ResourceEventOccurred -= Instance_EventOccurred;
                //r.Instance.CustomResourceEventOccurred -= Instance_CustomEventOccurred;
                //r.Instance.ResourceModified -= Instance_PropertyModified;
                //r.Instance.ResourceDestroyed -= Instance_ResourceDestroyed;

                Unsubscribe(res);

                // remove from attached resources
                //attachedResources.Remove(res);

                // reply ok
                SendReply(IIPPacket.IIPPacketAction.DetachResource, callback).Done();
            }
            else
            {
                // reply failed
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
            }
        });
    }

    void IIPRequestCreateResource(uint callback, uint storeId, uint parentId, byte[] content)
    {

        Warehouse.GetById(storeId).Then(store =>
        {
            if (store == null)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.StoreNotFound);
                return;
            }

            if (!(store is IStore))
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceIsNotStore);
                return;
            }

            // check security
            if (store.Instance.Applicable(session, ActionType.CreateResource, null) != Ruling.Allowed)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.CreateDenied);
                return;
            }

            Warehouse.GetById(parentId).Then(parent =>
            {

                // check security

                if (parent != null)
                    if (parent.Instance.Applicable(session, ActionType.AddChild, null) != Ruling.Allowed)
                    {
                        SendError(ErrorType.Management, callback, (ushort)ExceptionCode.AddChildDenied);
                        return;
                    }

                uint offset = 0;

                var className = content.GetString(offset + 1, content[0]);
                offset += 1 + (uint)content[0];

                var nameLength = content.GetUInt16(offset, Endian.Little);
                offset += 2;
                var name = content.GetString(offset, nameLength);

                var cl = content.GetUInt32(offset, Endian.Little);
                offset += 4;

                var type = Type.GetType(className);

                if (type == null)
                {
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ClassNotFound);
                    return;
                }

                DataDeserializer.ListParser(content, offset, cl, this, null).Then(parameters =>
                {
                    offset += cl;
                    cl = content.GetUInt32(offset, Endian.Little);

                    //Codec.ParseStructure(content, offset, cl, this).Then(attributes =>
                    DataDeserializer.TypedMapParser(content, offset, cl, this, null).Then(attributes =>
                    {
                        offset += cl;
                        cl = (uint)content.Length - offset;

                        //Codec.ParseStructure(content, offset, cl, this).Then(values =>
                        DataDeserializer.TypedMapParser(content, offset, cl, this, null).Then(values =>
                        {

#if NETSTANDARD
                            var constructors = Type.GetType(className).GetTypeInfo().GetConstructors();
#else
                                var constructors = Type.GetType(className).GetConstructors();
#endif

                            var matching = constructors.Where(x =>
                            {
                                var ps = x.GetParameters();
                                if (ps.Length > 0 && ps.Length == parameters.Length + 1)
                                    if (ps.Last().ParameterType == typeof(DistributedConnection))
                                        return true;

                                return ps.Length == parameters.Length;
                            }
                            ).ToArray();

                            var pi = matching[0].GetParameters();

                            // cast arguments
                            object[] args = null;

                            if (pi.Length > 0)
                            {
                                int argsCount = pi.Length;
                                args = new object[pi.Length];

                                if (pi[pi.Length - 1].ParameterType == typeof(DistributedConnection))
                                {
                                    args[--argsCount] = this;
                                }

                                if (parameters != null)
                                {
                                    for (int i = 0; i < argsCount && i < parameters.Length; i++)
                                    {
                                        args[i] = DC.CastConvert(parameters[i], pi[i].ParameterType);
                                    }
                                }
                            }

                            // create the resource
                            var resource = Activator.CreateInstance(type, args) as IResource;

                            Warehouse.Put(name, resource, store as IStore, parent).Then(ok =>
                           {
                               SendReply(IIPPacket.IIPPacketAction.CreateResource, callback)
                              .AddUInt32(resource.Instance.Id)
                              .Done();

                           }).Error(x =>
                           {
                               SendError(ErrorType.Exception, callback, (ushort)ExceptionCode.AddToStoreFailed);
                           });

                        });
                    });
                });
            });
        });
    }

    void IIPRequestDeleteResource(uint callback, uint resourceId)
    {
        Warehouse.GetById(resourceId).Then(r =>
        {
            if (r == null)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                return;
            }

            if (r.Instance.Store.Instance.Applicable(session, ActionType.Delete, null) != Ruling.Allowed)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.DeleteDenied);
                return;
            }

            if (Warehouse.Remove(r))
                SendReply(IIPPacket.IIPPacketAction.DeleteResource, callback).Done();
            //SendParams((byte)0x84, callback);
            else
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.DeleteFailed);
        });
    }

    void IIPRequestGetAttributes(uint callback, uint resourceId, byte[] attributes, bool all = false)
    {
        Warehouse.GetById(resourceId).Then(r =>
        {
            if (r == null)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                return;
            }

            //                if (!r.Instance.Store.Instance.Applicable(r, session, ActionType.InquireAttributes, null))
            if (r.Instance.Applicable(session, ActionType.InquireAttributes, null) != Ruling.Allowed)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ViewAttributeDenied);
                return;
            }

            string[] attrs = null;

            if (!all)
                attrs = attributes.GetStringArray(0, (uint)attributes.Length);

            var st = r.Instance.GetAttributes(attrs);

            if (st != null)
                SendReply(all ? IIPPacket.IIPPacketAction.GetAllAttributes : IIPPacket.IIPPacketAction.GetAttributes, callback)
                          .AddUInt8Array(Codec.Compose(st, this))
                          .Done();
            else
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.GetAttributesFailed);

        });
    }

    void IIPRequestAddChild(uint callback, uint parentId, uint childId)
    {
        Warehouse.GetById(parentId).Then(parent =>
        {
            if (parent == null)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                return;
            }

            Warehouse.GetById(childId).Then(child =>
            {
                if (child == null)
                {
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                    return;
                }

                if (parent.Instance.Applicable(this.session, ActionType.AddChild, null) != Ruling.Allowed)
                {
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.AddChildDenied);
                    return;
                }

                if (child.Instance.Applicable(this.session, ActionType.AddParent, null) != Ruling.Allowed)
                {
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.AddParentDenied);
                    return;
                }

                parent.Instance.Store.AddChild(parent, child);

                SendReply(IIPPacket.IIPPacketAction.AddChild, callback).Done();
                //child.Instance.Parents
            });

        });
    }

    void IIPRequestRemoveChild(uint callback, uint parentId, uint childId)
    {
        Warehouse.GetById(parentId).Then(parent =>
        {
            if (parent == null)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                return;
            }

            Warehouse.GetById(childId).Then(child =>
            {
                if (child == null)
                {
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                    return;
                }

                if (parent.Instance.Applicable(this.session, ActionType.RemoveChild, null) != Ruling.Allowed)
                {
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.AddChildDenied);
                    return;
                }

                if (child.Instance.Applicable(this.session, ActionType.RemoveParent, null) != Ruling.Allowed)
                {
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.AddParentDenied);
                    return;
                }

                parent.Instance.Store.RemoveChild(parent, child);// Children.Remove(child);

                SendReply(IIPPacket.IIPPacketAction.RemoveChild, callback).Done();
                //child.Instance.Parents
            });

        });
    }

    void IIPRequestRenameResource(uint callback, uint resourceId, string name)
    {
        Warehouse.GetById(resourceId).Then(resource =>
        {
            if (resource == null)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                return;
            }

            if (resource.Instance.Applicable(this.session, ActionType.Rename, null) != Ruling.Allowed)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.RenameDenied);
                return;
            }


            resource.Instance.Name = name;
            SendReply(IIPPacket.IIPPacketAction.RenameResource, callback).Done();
        });
    }

    void IIPRequestResourceChildren(uint callback, uint resourceId)
    {
        Warehouse.GetById(resourceId).Then(resource =>
        {
            if (resource == null)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                return;
            }

            resource.Instance.Children<IResource>().Then(children =>
            {
                SendReply(IIPPacket.IIPPacketAction.ResourceChildren, callback)
                .AddUInt8Array(Codec.Compose(children, this))// Codec.ComposeResourceArray(children, this, true))
                    .Done();

            });


        });
    }

    void IIPRequestResourceParents(uint callback, uint resourceId)
    {
        Warehouse.GetById(resourceId).Then(resource =>
        {
            if (resource == null)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                return;
            }

            resource.Instance.Parents<IResource>().Then(parents =>
            {
                SendReply(IIPPacket.IIPPacketAction.ResourceParents, callback)
                .AddUInt8Array(Codec.Compose(parents, this))
                //.AddUInt8Array(Codec.ComposeResourceArray(parents, this, true))
                .Done();

            });

        });
    }

    void IIPRequestClearAttributes(uint callback, uint resourceId, byte[] attributes, bool all = false)
    {
        Warehouse.GetById(resourceId).Then(r =>
        {
            if (r == null)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                return;
            }

            if (r.Instance.Store.Instance.Applicable(session, ActionType.UpdateAttributes, null) != Ruling.Allowed)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.UpdateAttributeDenied);
                return;
            }

            string[] attrs = null;

            if (!all)
                attrs = attributes.GetStringArray(0, (uint)attributes.Length);

            if (r.Instance.RemoveAttributes(attrs))
                SendReply(all ? IIPPacket.IIPPacketAction.ClearAllAttributes : IIPPacket.IIPPacketAction.ClearAttributes, callback).Done();
            else
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.UpdateAttributeFailed);

        });
    }

    void IIPRequestUpdateAttributes(uint callback, uint resourceId, byte[] attributes, bool clearAttributes = false)
    {
        Warehouse.GetById(resourceId).Then(r =>
        {
            if (r == null)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                return;
            }

            if (r.Instance.Store.Instance.Applicable(session, ActionType.UpdateAttributes, null) != Ruling.Allowed)
            {
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.UpdateAttributeDenied);
                return;
            }


            DataDeserializer.TypedMapParser(attributes, 0, (uint)attributes.Length, this, null).Then(attrs =>
            {
                if (r.Instance.SetAttributes((Map<string, object>)attrs, clearAttributes))
                    SendReply(clearAttributes ? IIPPacket.IIPPacketAction.ClearAllAttributes : IIPPacket.IIPPacketAction.ClearAttributes,
                              callback).Done();
                else
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.UpdateAttributeFailed);
            });

        });

    }

    void IIPRequestLinkTemplates(uint callback, string resourceLink)
    {
        Action<IResource[]> queryCallback = (r) =>
        {
            if (r == null)
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
            else
            {
                var list = r.Where(x => x.Instance.Applicable(session, ActionType.ViewTemplate, null) != Ruling.Denied).ToArray();

                if (list.Length == 0)
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                else
                {
                    // get all templates related to this resource

                    var msg = new BinaryList();

                    var templates = new List<TypeTemplate>();
                    foreach (var resource in list)
                        templates.AddRange(TypeTemplate.GetDependencies(resource.Instance.Template).Where(x => !templates.Contains(x)));

                    foreach (var t in templates)
                    {
                        msg.AddInt32(t.Content.Length)
                            .AddUInt8Array(t.Content);
                    }

                    // digggg
                    SendReply(IIPPacket.IIPPacketAction.LinkTemplates, callback)
                            //.AddInt32(msg.Length)
                            //.AddUInt8Array(msg.ToArray())
                            .AddUInt8Array(TransmissionType.Compose(TransmissionTypeIdentifier.RawData, msg.ToArray()))
                            .Done();
                }
            }
        };

        if (Server?.EntryPoint != null)
            Server.EntryPoint.Query(resourceLink, this).Then(queryCallback);
        else
            Warehouse.Query(resourceLink).Then(queryCallback);
    }

    void IIPRequestTemplateFromClassName(uint callback, string className)
    {
        var t = Warehouse.GetTemplateByClassName(className);

        if (t != null)
            SendReply(IIPPacket.IIPPacketAction.TemplateFromClassName, callback)
                    .AddUInt8Array(TransmissionType.Compose(TransmissionTypeIdentifier.RawData, t.Content))
                    //.AddInt32(t.Content.Length)
                    //.AddUInt8Array(t.Content)
                    .Done();
        else
        {
            // reply failed
            SendError(ErrorType.Management, callback, (ushort)ExceptionCode.TemplateNotFound);
        }
    }

    void IIPRequestTemplateFromClassId(uint callback, Guid classId)
    {
        var t = Warehouse.GetTemplateByClassId(classId);

        if (t != null)
            SendReply(IIPPacket.IIPPacketAction.TemplateFromClassId, callback)
                    .AddUInt8Array(TransmissionType.Compose(TransmissionTypeIdentifier.RawData, t.Content))
                    //.AddInt32(t.Content.Length)
                    //.AddUInt8Array(t.Content)
                    .Done();
        else
        {
            // reply failed
            SendError(ErrorType.Management, callback, (ushort)ExceptionCode.TemplateNotFound);
        }
    }



    void IIPRequestTemplateFromResourceId(uint callback, uint resourceId)
    {
        Warehouse.GetById(resourceId).Then((r) =>
        {
            if (r != null)
                SendReply(IIPPacket.IIPPacketAction.TemplateFromResourceId, callback)
                        .AddInt32(r.Instance.Template.Content.Length)
                        .AddUInt8Array(r.Instance.Template.Content)
                        .Done();
            else
            {
                // reply failed
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.TemplateNotFound);
            }
        });
    }




    void IIPRequestQueryResources(uint callback, string resourceLink)
    {

        Action<IResource[]> queryCallback = (r) =>
        {
            if (r == null)
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
            else
            {
                var list = r.Where(x => x.Instance.Applicable(session, ActionType.Attach, null) != Ruling.Denied).ToArray();

                if (list.Length == 0)
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                else
                    SendReply(IIPPacket.IIPPacketAction.QueryLink, callback)
                                .AddUInt8Array(Codec.Compose(list, this))
                                //.AddUInt8Array(Codec.ComposeResourceArray(list, this, true))
                                .Done();
            }
        };

        if (Server?.EntryPoint != null)
            Server.EntryPoint.Query(resourceLink, this).Then(queryCallback);
        else
            Warehouse.Query(resourceLink).Then(queryCallback);
    }

    void IIPRequestResourceAttribute(uint callback, uint resourceId)
    {

    }

    [Attribute]
    public ExceptionLevel ExceptionLevel { get; set; }
        = ExceptionLevel.Code | ExceptionLevel.Message | ExceptionLevel.Source | ExceptionLevel.Trace;

    private Tuple<ushort, string> SummerizeException(Exception ex)
    {
        ex = ex.InnerException != null ? ex.InnerException : ex;

        var code = (ExceptionLevel & ExceptionLevel.Code) == 0 ? 0 : ex is AsyncException ae ? ae.Code : 0;
        var msg = (ExceptionLevel & ExceptionLevel.Message) == 0 ? "" : ex.Message;
        var source = (ExceptionLevel & ExceptionLevel.Source) == 0 ? "" : ex.Source;
        var trace = (ExceptionLevel & ExceptionLevel.Trace) == 0 ? "" : ex.StackTrace;

        return new Tuple<ushort, string>((ushort)code, $"{source}: {msg}\n{trace}");
    }

    void IIPRequestInvokeFunction(uint callback, uint resourceId, byte index, TransmissionType transmissionType, byte[] content)
    {
        //Console.WriteLine("IIPRequestInvokeFunction " + callback + " " + resourceId + "  " + index);

        Warehouse.GetById(resourceId).Then((r) =>
        {
            if (r == null)
            {
                // no resource with this id
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
                return;
            }

            var ft = r.Instance.Template.GetFunctionTemplateByIndex(index);

            if (ft == null)
            {
                // no function at this index
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.MethodNotFound);
                return;
            }

            var (_, parsed) = Codec.Parse(content, 0, this, null, transmissionType);

            parsed.Then(results =>
            {
                var arguments = (Map<byte, object>)results;// (object[])results;

                // un hold the socket to send data immediately
                this.Socket.Unhold();

                if (r is DistributedResource)
                {
                    var rt = (r as DistributedResource)._Invoke(index, arguments);
                    if (rt != null)
                    {
                        rt.Then(res =>
                        {
                            SendReply(IIPPacket.IIPPacketAction.InvokeFunction, callback)
                                        .AddUInt8Array(Codec.Compose(res, this))
                                        .Done();
                        });
                    }
                    else
                    {
                        // function not found on a distributed object
                        SendError(ErrorType.Management, callback, (ushort)ExceptionCode.MethodNotFound);
                    }
                }
                else
                {
                    var fi = r.GetType().GetMethod(ft.Name);

                    if (fi != null)
                    {
                        if (r.Instance.Applicable(session, ActionType.Execute, ft) == Ruling.Denied)
                        {
                            SendError(ErrorType.Management, callback,
                                (ushort)ExceptionCode.InvokeDenied);
                            return;
                        }

                        // cast arguments
                        ParameterInfo[] pis = fi.GetParameters();

                        object[] args = new object[pis.Length];

                        if (pis.Length > 0)
                        {
                            if (pis.Last().ParameterType == typeof(DistributedConnection))
                            {
                                for (byte i = 0; i < pis.Length - 1; i++)
                                    args[i] = arguments.ContainsKey(i) ?
                                                DC.CastConvert(arguments[i], pis[i].ParameterType) : Type.Missing;
                                args[args.Length - 1] = this;
                            }
                            else
                            {
                                for (byte i = 0; i < pis.Length; i++)
                                    args[i] = arguments.ContainsKey(i) ?
                                                DC.CastConvert(arguments[i], pis[i].ParameterType) : Type.Missing;
                            }
                        }

                        object rt;

                        try
                        {
                            rt = fi.Invoke(r, args);
                        }
                        catch (Exception ex)
                        {
                            var (code, msg) = SummerizeException(ex);
                            SendError(ErrorType.Exception, callback, code, msg);
                            return;
                        }

                        if (rt is System.Collections.IEnumerable && !(rt is Array || rt is Map<string, object> || rt is string))
                        {
                            var enu = rt as System.Collections.IEnumerable;

                            try
                            {
                                foreach (var v in enu)
                                    SendChunk(callback, v);
                                SendReply(IIPPacket.IIPPacketAction.InvokeFunction, callback)
                                .AddUInt8((byte)TransmissionTypeIdentifier.Null)
                                .Done();
                            }
                            catch (Exception ex)
                            {
                                var (code, msg) = SummerizeException(ex);
                                SendError(ErrorType.Exception, callback, code, msg);
                            }

                        }
                        else if (rt is Task)
                        {
                            (rt as Task).ContinueWith(t =>
                            {
#if NETSTANDARD
                                var res = t.GetType().GetTypeInfo().GetProperty("Result").GetValue(t);
#else
                                            var res = t.GetType().GetProperty("Result").GetValue(t);
#endif
                                SendReply(IIPPacket.IIPPacketAction.InvokeFunction, callback)
                                 .AddUInt8Array(Codec.Compose(res, this))
                                 .Done();
                            });

                            //await t;
                            //SendParams((byte)0x90, callback, Codec.Compose(res, this));
                        }
                        else if (rt is AsyncReply)// Codec.ImplementsInterface(rt.GetType(), typeof(IAsyncReply<>)))// rt.GetType().GetTypeInfo().IsGenericType
                                                  //&& rt.GetType().GetGenericTypeDefinition() == typeof(IAsyncReply<>))
                        {
                            (rt as AsyncReply).Then(res =>
                            {
                                SendReply(IIPPacket.IIPPacketAction.InvokeFunction, callback)
                                            .AddUInt8Array(Codec.Compose(res, this))
                                            .Done();
                            }).Error(ex =>
                            {
                                var (code, msg) = SummerizeException(ex);
                                SendError(ErrorType.Exception, callback, code, msg);
                            }).Progress((pt, pv, pm) =>
                            {
                                SendProgress(callback, pv, pm);
                            }).Chunk(v =>
                            {
                                SendChunk(callback, v);
                            });
                        }
                        else
                        {
                            SendReply(IIPPacket.IIPPacketAction.InvokeFunction, callback)
                                    .AddUInt8Array(Codec.Compose(rt, this))
                                    .Done();
                        }
                    }
                    else
                    {
                        // ft found, fi not found, this should never happen
                        SendError(ErrorType.Management, callback, (ushort)ExceptionCode.MethodNotFound);
                    }
                }
            });

        });
    }




    void IIPRequestListen(uint callback, uint resourceId, byte index)
    {
        Warehouse.GetById(resourceId).Then((r) =>
        {
            if (r != null)
            {
                var et = r.Instance.Template.GetEventTemplateByIndex(index);

                if (et != null)
                {
                    if (r is DistributedResource)
                    {
                        (r as DistributedResource).Listen(et).Then(x =>
                       {
                           SendReply(IIPPacket.IIPPacketAction.Listen, callback).Done();
                       }).Error(x => SendError(ErrorType.Exception, callback, (ushort)ExceptionCode.GeneralFailure));
                    }
                    else
                    {
                        lock (subscriptionsLock)
                        {
                            if (!subscriptions.ContainsKey(r))
                            {
                                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.NotAttached);
                                return;
                            }

                            if (subscriptions[r].Contains(index))
                            {
                                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.AlreadyListened);
                                return;
                            }

                            subscriptions[r].Add(index);

                            SendReply(IIPPacket.IIPPacketAction.Listen, callback).Done();
                        }
                    }
                }
                else
                {
                    // pt not found
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.MethodNotFound);
                }
            }
            else
            {
                // resource not found
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
            }
        });

    }

    void IIPRequestUnlisten(uint callback, uint resourceId, byte index)
    {
        Warehouse.GetById(resourceId).Then((r) =>
        {
            if (r != null)
            {
                var et = r.Instance.Template.GetEventTemplateByIndex(index);

                if (et != null)
                {
                    if (r is DistributedResource)
                    {
                        (r as DistributedResource).Unlisten(et).Then(x =>
                        {
                            SendReply(IIPPacket.IIPPacketAction.Unlisten, callback).Done();
                        }).Error(x => SendError(ErrorType.Exception, callback, (ushort)ExceptionCode.GeneralFailure));
                    }
                    else
                    {
                        lock (subscriptionsLock)
                        {
                            if (!subscriptions.ContainsKey(r))
                            {
                                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.NotAttached);
                                return;
                            }

                            if (!subscriptions[r].Contains(index))
                            {
                                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.AlreadyUnlistened);
                                return;
                            }

                            subscriptions[r].Remove(index);

                            SendReply(IIPPacket.IIPPacketAction.Unlisten, callback).Done();
                        }
                    }
                }
                else
                {
                    // pt not found
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.MethodNotFound);
                }
            }
            else
            {
                // resource not found
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
            }
        });

    }

    //        void IIPRequestGetProperty(uint callback, uint resourceId, byte index)
    //        {
    //            Warehouse.GetById(resourceId).Then((r) =>
    //            {
    //                if (r != null)
    //                {
    //                    var pt = r.Instance.Template.GetPropertyTemplateByIndex(index);
    //                    if (pt != null)
    //                    {
    //                        if (r is DistributedResource)
    //                        {
    //                            SendReply(IIPPacket.IIPPacketAction.GetProperty, callback)
    //                                        .AddUInt8Array(Codec.Compose((r as DistributedResource)._Get(pt.Index), this))
    //                                        .Done();
    //                        }
    //                        else
    //                        {
    //#if NETSTANDARD
    //                            var pi = r.GetType().GetTypeInfo().GetProperty(pt.Name);
    //#else
    //                            var pi = r.GetType().GetProperty(pt.Name);
    //#endif

    //                            if (pi != null)
    //                            {
    //                                SendReply(IIPPacket.IIPPacketAction.GetProperty, callback)
    //                                            .AddUInt8Array(Codec.Compose(pi.GetValue(r), this))
    //                                            .Done();
    //                            }
    //                            else
    //                            {
    //                                // pt found, pi not found, this should never happen
    //                            }
    //                        }
    //                    }
    //                    else
    //                    {
    //                        // pt not found
    //                    }
    //                }
    //                else
    //                {
    //                    // resource not found
    //                }
    //            });
    //        }

    void IIPRequestInquireResourceHistory(uint callback, uint resourceId, DateTime fromDate, DateTime toDate)
    {
        Warehouse.GetById(resourceId).Then((r) =>
        {
            if (r != null)
            {
                r.Instance.Store.GetRecord(r, fromDate, toDate).Then((results) =>
                {
                    var history = DataSerializer.HistoryComposer(results, this, true);

                    /*
                    ulong fromAge = 0;
                    ulong toAge = 0;

                    if (results.Count > 0)
                    {
                        var firstProp = results.Values.First();
                        //var lastProp = results.Values.Last();

                        if (firstProp.Length > 0)
                        {
                            fromAge = firstProp[0].Age;
                            toAge = firstProp.Last().Age;
                        }

                    }*/

                    SendReply(IIPPacket.IIPPacketAction.ResourceHistory, callback)
                            .AddUInt8Array(history)
                            .Done();

                });
            }
        });
    }

    //        void IIPRequestGetPropertyIfModifiedSince(uint callback, uint resourceId, byte index, ulong age)
    //        {
    //            Warehouse.GetById(resourceId).Then((r) =>
    //            {
    //                if (r != null)
    //                {
    //                    var pt = r.Instance.Template.GetFunctionTemplateByIndex(index);
    //                    if (pt != null)
    //                    {
    //                        if (r.Instance.GetAge(index) > age)
    //                        {
    //#if NETSTANDARD
    //                            var pi = r.GetType().GetTypeInfo().GetProperty(pt.Name);
    //#else
    //                            var pi = r.GetType().GetProperty(pt.Name);
    //#endif
    //                            if (pi != null)
    //                            {
    //                                SendReply(IIPPacket.IIPPacketAction.GetPropertyIfModified, callback)
    //                                            .AddUInt8Array(Codec.Compose(pi.GetValue(r), this))
    //                                            .Done();
    //                            }
    //                            else
    //                            {
    //                                // pt found, pi not found, this should never happen
    //                            }
    //                        }
    //                        else
    //                        {
    //                            SendReply(IIPPacket.IIPPacketAction.GetPropertyIfModified, callback)
    //                                    .AddUInt8((byte)DataType.NotModified)
    //                                    .Done();
    //                        }
    //                    }
    //                    else
    //                    {
    //                        // pt not found
    //                    }
    //                }
    //                else
    //                {
    //                    // resource not found
    //                }
    //            });
    //        }

    void IIPRequestSetProperty(uint callback, uint resourceId, byte index, TransmissionType transmissionType, byte[] content)
    {
        Warehouse.GetById(resourceId).Then((r) =>
        {
            if (r != null)
            {

                var pt = r.Instance.Template.GetPropertyTemplateByIndex(index);
                if (pt != null)
                {
                    var (_, parsed) = Codec.Parse(content, 0, this, null, transmissionType);
                    parsed.Then((value) =>
                    {
                        if (r is DistributedResource)
                        {
                            // propagation
                            (r as DistributedResource)._Set(index, value).Then((x) =>
                        {
                            SendReply(IIPPacket.IIPPacketAction.SetProperty, callback).Done();
                        }).Error(x =>
                        {
                            SendError(x.Type, callback, (ushort)x.Code, x.Message);
                        });
                        }
                        else
                        {

                            /*
#if NETSTANDARD
                            var pi = r.GetType().GetTypeInfo().GetProperty(pt.Name);
#else
                            var pi = r.GetType().GetProperty(pt.Name);
#endif*/

                            var pi = pt.PropertyInfo;

                            if (pi != null)
                            {

                                if (r.Instance.Applicable(session, ActionType.SetProperty, pt, this) == Ruling.Denied)
                                {
                                    SendError(ErrorType.Exception, callback, (ushort)ExceptionCode.SetPropertyDenied);
                                    return;
                                }

                                if (!pi.CanWrite)
                                {
                                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ReadOnlyProperty);
                                    return;
                                }


                                if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition()
                                    == typeof(DistributedPropertyContext<>))
                                {
                                    value = Activator.CreateInstance(pi.PropertyType, this, value);
                                    //value = new DistributedPropertyContext(this, value);
                                }
                                else
                                {
                                    // cast new value type to property type
                                    value = DC.CastConvert(value, pi.PropertyType);
                                }


                                try
                                {
                                    pi.SetValue(r, value);
                                    SendReply(IIPPacket.IIPPacketAction.SetProperty, callback).Done();
                                }
                                catch (Exception ex)
                                {
                                    SendError(ErrorType.Exception, callback, 0, ex.Message);
                                }

                            }
                            else
                            {
                                // pt found, pi not found, this should never happen
                                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.PropertyNotFound);
                            }
                        }

                    });
                }
                else
                {
                    // property not found
                    SendError(ErrorType.Management, callback, (ushort)ExceptionCode.PropertyNotFound);
                }
            }
            else
            {
                // resource not found
                SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
            }
        });
    }

    /*
    void IIPReplyAttachResource(uint callback, uint resourceAge, object[] properties)
    {
        if (requests.ContainsKey(callback))
        {
            var req = requests[callback];
            var r = resources[(uint)req.Arguments[0]];

            if (r == null)
            {
                r.Instance.Deserialize(properties);
                r.Instance.Age = resourceAge;
                r.Attached();

                // process stack
                foreach (var rr in resources.Values)
                    rr.Stack.ProcessStack();
            }
            else
            {
                // resource not found
            }
        }
    }

    void IIPReplyReattachResource(uint callback, uint resourceAge, object[] properties)
    {
        var req = requests.Take(callback);

        if (req != null)
        {
            var r = resources[(uint)req.Arguments[0]];

            if (r == null)
            {
                r.Instance.Deserialize(properties);
                r.Instance.Age = resourceAge;
                r.Attached();

                // process stack
                foreach (var rr in resources.Values)
                    rr.Stack.ProcessStack();
            }
            else
            {
                // resource not found
            }
        }
    }


    void IIPReplyDetachResource(uint callback)
    {
        var req = requests.Take(callback);
        // nothing to do
    }

    void IIPReplyCreateResource(uint callback, Guid classId, uint resourceId)
    {
        var req = requests.Take(callback);
        // nothing to do

    }
    void IIPReplyDeleteResource(uint callback)
    {
        var req = requests.Take(callback);
        // nothing to do

    }

    void IIPReplyTemplateFromClassName(uint callback, ResourceTemplate template)
    {
        // cache
        if (!templates.ContainsKey(template.ClassId))
            templates.Add(template.ClassId, template);

        var req = requests.Take(callback);
        req?.Trigger(template);
    }

    void IIPReplyTemplateFromClassId(uint callback, ResourceTemplate template)
    {
        // cache
        if (!templates.ContainsKey(template.ClassId))
            templates.Add(template.ClassId, template);

        var req = requests.Take(callback);
        req?.Trigger(template);

    }

    void IIPReplyTemplateFromResourceLink(uint callback, ResourceTemplate template)
    {
        // cache
        if (!templates.ContainsKey(template.ClassId))
            templates.Add(template.ClassId, template);

        var req = requests.Take(callback);
        req?.Trigger(template);
    }

    void IIPReplyTemplateFromResourceId(uint callback, ResourceTemplate template)
    {
        // cache
        if (!templates.ContainsKey(template.ClassId))
            templates.Add(template.ClassId, template);

        var req = requests.Take(callback);
        req?.Trigger(template);
    }

    void IIPReplyResourceIdFromResourceLink(uint callback, Guid classId, uint resourceId, uint resourceAge)
    {
        var req = requests.Take(callback);
        req?.Trigger(template);
    }

    void IIPReplyInvokeFunction(uint callback, object returnValue)
    {

    }

    void IIPReplyGetProperty(uint callback, object value)
    {

    }
    void IIPReplyGetPropertyIfModifiedSince(uint callback, object value)
    {

    }
    void IIPReplySetProperty(uint callback)
    {

    }
    */

    /// <summary>
    /// Get the ResourceTemplate for a given class Id. 
    /// </summary>
    /// <param name="classId">Class GUID.</param>
    /// <returns>ResourceTemplate.</returns>
    public AsyncReply<TypeTemplate> GetTemplate(Guid classId)
    {
        if (templates.ContainsKey(classId))
            return new AsyncReply<TypeTemplate>(templates[classId]);
        else if (templateRequests.ContainsKey(classId))
            return templateRequests[classId];

        var reply = new AsyncReply<TypeTemplate>();
        templateRequests.Add(classId, reply);

        SendRequest(IIPPacket.IIPPacketAction.TemplateFromClassId)
                    .AddGuid(classId)
                    .Done()
                    .Then((rt) =>
                    {
                        templateRequests.Remove(classId);
                        templates.Add(((TypeTemplate)rt[0]).ClassId, (TypeTemplate)rt[0]);
                        Warehouse.PutTemplate(rt[0] as TypeTemplate);
                        reply.Trigger(rt[0]);
                    }).Error((ex) =>
                    {
                        reply.TriggerError(ex);
                    });

        return reply;
    }

    // IStore interface 
    /// <summary>
    /// Get a resource by its path.
    /// </summary>
    /// <param name="path">Path to the resource.</param>
    /// <returns>Resource</returns>
    public AsyncReply<IResource> Get(string path)
    {

        var rt = new AsyncReply<IResource>();

        Query(path).Then(ar =>
        {

            //if (filter != null)
            //  ar = ar?.Where(filter).ToArray();

            // MISSING: should dispatch the unused resources. 
            if (ar?.Length > 0)
                rt.Trigger(ar[0]);
            else
                rt.Trigger(null);
        }).Error(ex => rt.TriggerError(ex));


        return rt;

        /*

        if (pathRequests.ContainsKey(path))
            return pathRequests[path];

        var reply = new AsyncReply<IResource>();
        pathRequests.Add(path, reply);

        var bl = new BinaryList(path);
        bl.Insert(0, (ushort)bl.Length);

        SendRequest(IIPPacket.IIPPacketAction.QueryLink, bl.ToArray()).Then((rt) =>
        {
            pathRequests.Remove(path);
            //(Guid)rt[0],
            Fetch((uint)rt[1]).Then((r) =>
           {
               reply.Trigger(r);
           });
        }).Error((ex) =>
        {
            reply.TriggerError(ex);
        }); ;


        return reply;
        */
    }

    /// <summary>
    /// Retrive a resource by its instance Id.
    /// </summary>
    /// <param name="iid">Instance Id</param>
    /// <returns>Resource</returns>
    public AsyncReply<IResource> Retrieve(uint iid)
    {
        foreach (var r in resources.Values)
            if (r.Instance.Id == iid)
                return new AsyncReply<IResource>(r);
        return new AsyncReply<IResource>(null);
    }


    public AsyncReply<TypeTemplate[]> GetLinkTemplates(string link)
    {
        var reply = new AsyncReply<TypeTemplate[]>();

        var l = DC.ToBytes(link);

        SendRequest(IIPPacket.IIPPacketAction.LinkTemplates)
        .AddUInt16((ushort)l.Length)
        .AddUInt8Array(l)
        .Done()
        .Then((rt) =>
        {

            var templates = new List<TypeTemplate>();
            // parse templates
            var tt = (TransmissionType)rt[0];
            var data = (byte[])rt[1];
            //var offset = 0;
            for (var offset = tt.Offset; offset < tt.ContentLength;)
            {
                var cs = data.GetUInt32(offset, Endian.Little);
                offset += 4;
                templates.Add(TypeTemplate.Parse(data, offset, cs));
                offset += cs;
            }

            reply.Trigger(templates.ToArray());

        }).Error((ex) =>
        {
            reply.TriggerError(ex);
        });

        return reply;
    }

    /// <summary>
    /// Fetch a resource from the other end
    /// </summary>
    /// <param name="classId">Class GUID</param>
    /// <param name="id">Resource Id</param>Guid classId
    /// <returns>DistributedResource</returns>
    public AsyncReply<DistributedResource> Fetch(uint id, uint[] requestSequence)
    {
        var resource = resources[id];
        var request = resourceRequests[id];

        if (request != null)
        {
            if (resource != null && (requestSequence?.Contains(id) ?? false))
                return new AsyncReply<DistributedResource>(resource);
            else
                return request;
        }
        else if (resource != null && !resource.Suspended)
        {
            return new AsyncReply<DistributedResource>(resource);
        }


        var reply = new AsyncReply<DistributedResource>();
        resourceRequests.Add(id, reply);

        var newSequence = requestSequence != null ? requestSequence.Concat(new uint[] { id }).ToArray() : new uint[] { id };

        SendRequest(IIPPacket.IIPPacketAction.AttachResource)
                    .AddUInt32(id)
                    .Done()
                    .Then((rt) =>
                    {

                        if (rt == null)
                        {
                            reply.TriggerError(new AsyncException(ErrorType.Management,
                                (ushort)ExceptionCode.ResourceNotFound, "Null response"));
                            return;
                        }

                        DistributedResource dr;
                        TypeTemplate template = null;
                        Guid classId = (Guid)rt[0];

                        if (resource == null)
                        {
                            template = Warehouse.GetTemplateByClassId(classId, TemplateType.Wrapper);
                            if (template?.DefinedType != null)
                                dr = Activator.CreateInstance(template.DefinedType, this, id, (ulong)rt[1], (string)rt[2]) as DistributedResource;
                            else
                                dr = new DistributedResource(this, id, (ulong)rt[1], (string)rt[2]);
                        }
                        else
                            dr = resource;

                        var transmissionType = (TransmissionType)rt[3];
                        var content = (byte[])rt[4];

                        var initResource = (DistributedResource ok) =>
                        {
                            var (_, parsed) = Codec.Parse(content, 0, this, newSequence, transmissionType);
                            parsed.Then(results =>
                            {
                                var ar = results as object[];

                                var pvs = new List<PropertyValue>();

                                for (var i = 0; i < ar.Length; i += 3)
                                    pvs.Add(new PropertyValue(ar[i + 2], (ulong?)ar[i], (DateTime?)ar[i + 1]));

                                dr._Attach(pvs.ToArray());// (PropertyValue[])pvs);
                                resourceRequests.Remove(id);
                                reply.Trigger(dr);
                            }).Error(ex => reply.TriggerError(ex));

                        };

                        if (template == null)
                        {
                            GetTemplate((Guid)rt[0]).Then((tmp) =>
                            {
                                // ClassId, ResourceAge, ResourceLink, Content
                                if (resource == null)
                                {
                                    Warehouse.Put(id.ToString(), dr, this, null, tmp).Then(initResource).Error(ex => reply.TriggerError(ex));
                                }
                                else
                                {
                                    initResource(resource);
                                }
                            }).Error((ex) =>
                            {
                                reply.TriggerError(ex);
                            });

                        }
                        else
                        {
                            if (resource == null)
                            {
                                Warehouse.Put(id.ToString(), dr, this, null, template)
                                    .Then(initResource).Error((ex) => reply.TriggerError(ex));
                            }
                            else
                            {
                                initResource(resource);
                            }

                        }

                    }).Error((ex) =>
                    {
                        reply.TriggerError(ex);
                    });


        return reply;
    }


    public AsyncReply<IResource[]> GetChildren(IResource resource)
    {
        var rt = new AsyncReply<IResource[]>();

        SendRequest(IIPPacket.IIPPacketAction.ResourceChildren)
                    .AddUInt32(resource.Instance.Id)
                    .Done()
                    .Then(ar =>
                    {
                        var dataType = (TransmissionType)ar[0];
                        var data = (byte[])ar[1];

                        var (_, parsed) = Codec.Parse(data, dataType.Offset, this, null, dataType);

                        parsed.Then(resources => rt.Trigger(resources))
                              .Error(ex => rt.TriggerError(ex));

                        //Codec.ParseResourceArray(d, 0, (uint)d.Length, this).Then(resources =>
                        //{
                        //  rt.Trigger(resources);
                        //}).Error(ex => rt.TriggerError(ex));
                    });

        return rt;
    }

    public AsyncReply<IResource[]> GetParents(IResource resource)
    {
        var rt = new AsyncReply<IResource[]>();

        SendRequest(IIPPacket.IIPPacketAction.ResourceParents)
            .AddUInt32(resource.Instance.Id)
            .Done()
            .Then(ar =>
            {
                var dataType = (TransmissionType)ar[0];
                var data = (byte[])ar[1];
                var (_, parsed) = Codec.Parse(data, dataType.Offset, this, null, dataType);

                parsed.Then(resources => rt.Trigger(resources))
                      .Error(ex => rt.TriggerError(ex));

                //Codec.ParseResourceArray(d, 0, (uint)d.Length, this).Then(resources =>
                //{
                //    rt.Trigger(resources);
                //}).Error(ex => rt.TriggerError(ex));
            });

        return rt;
    }

    public AsyncReply<bool> RemoveAttributes(IResource resource, string[] attributes = null)
    {
        var rt = new AsyncReply<bool>();

        if (attributes == null)
            SendRequest(IIPPacket.IIPPacketAction.ClearAllAttributes)
                .AddUInt32(resource.Instance.Id)
                .Done()
                .Then(ar => rt.Trigger(true))
                .Error(ex => rt.TriggerError(ex));
        else
        {
            var attrs = DC.ToBytes(attributes);
            SendRequest(IIPPacket.IIPPacketAction.ClearAttributes)
                .AddUInt32(resource.Instance.Id)
                .AddInt32(attrs.Length)
                .AddUInt8Array(attrs)
                .Done()
                .Then(ar => rt.Trigger(true))
                .Error(ex => rt.TriggerError(ex));
        }

        return rt;
    }

    public AsyncReply<bool> SetAttributes(IResource resource, Map<string, object> attributes, bool clearAttributes = false)
    {
        var rt = new AsyncReply<bool>();

        SendRequest(clearAttributes ? IIPPacket.IIPPacketAction.UpdateAllAttributes : IIPPacket.IIPPacketAction.UpdateAttributes)
            .AddUInt32(resource.Instance.Id)
            //.AddUInt8Array(Codec.ComposeStructure(attributes, this, true, true, true))
            .AddUInt8Array(Codec.Compose(attributes, this))
            .Done()
            .Then(ar => rt.Trigger(true))
            .Error(ex => rt.TriggerError(ex));

        return rt;
    }

    public AsyncReply<Map<string, object>> GetAttributes(IResource resource, string[] attributes = null)
    {
        var rt = new AsyncReply<Map<string, object>>();

        if (attributes == null)
        {
            SendRequest(IIPPacket.IIPPacketAction.GetAllAttributes)
                .AddUInt32(resource.Instance.Id)
                .Done()
                .Then(ar =>
                {
                    var dataType = (TransmissionType)ar[0];
                    var data = (byte[])ar[1];
                    //Codec.Parse(d, )
                    var (_, parsed) = Codec.Parse(data, 0, this, null, dataType);
                    parsed.Then(st =>
                    {

                        resource.Instance.SetAttributes(st as Map<string, object>);

                        rt.Trigger(st);
                    }).Error(ex => rt.TriggerError(ex));
                });
        }
        else
        {
            var attrs = DC.ToBytes(attributes);
            SendRequest(IIPPacket.IIPPacketAction.GetAttributes)
                .AddUInt32(resource.Instance.Id)
                .AddInt32(attrs.Length)
                .AddUInt8Array(attrs)
                .Done()
                .Then(ar =>
                {
                    var dataType = (TransmissionType)ar[0];
                    var data = (byte[])ar[1];

                    var (_, parsed) = Codec.Parse(data, 0, this, null, dataType);
                    parsed.Then(st =>
                    {

                        resource.Instance.SetAttributes((Map<string, object>)st);

                        rt.Trigger(st);
                    }).Error(ex => rt.TriggerError(ex));
                });
        }

        return rt;
    }

    /// <summary>
    /// Get resource history.
    /// </summary>
    /// <param name="resource">IResource.</param>
    /// <param name="fromDate">From date.</param>
    /// <param name="toDate">To date.</param>
    /// <returns></returns>
    public AsyncReply<KeyList<PropertyTemplate, PropertyValue[]>> GetRecord(IResource resource, DateTime fromDate, DateTime toDate)
    {
        if (resource is DistributedResource)
        {
            var dr = resource as DistributedResource;

            if (dr.Connection != this)
                return new AsyncReply<KeyList<PropertyTemplate, PropertyValue[]>>(null);

            var reply = new AsyncReply<KeyList<PropertyTemplate, PropertyValue[]>>();

            SendRequest(IIPPacket.IIPPacketAction.ResourceHistory)
                .AddUInt32(dr.Id)
                .AddDateTime(fromDate)
                .AddDateTime(toDate)
                .Done()
                .Then(rt =>
                {
                    var content = (byte[])rt[0];

                    DataDeserializer.HistoryParser(content, 0, (uint)content.Length, resource, this, null)
                                      .Then((history) => reply.Trigger(history));

                }).Error((ex) => reply.TriggerError(ex));

            return reply;
        }
        else
            return new AsyncReply<KeyList<PropertyTemplate, PropertyValue[]>>(null);
    }

    /// <summary>
    /// Query resources at specific link.
    /// </summary>
    /// <param name="path">Link path.</param>
    /// <returns></returns>
    public AsyncReply<IResource[]> Query(string path)
    {
        var str = DC.ToBytes(path);
        var reply = new AsyncReply<IResource[]>();

        SendRequest(IIPPacket.IIPPacketAction.QueryLink)
                    .AddUInt16((ushort)str.Length)
                    .AddUInt8Array(str)
                    .Done()
                    .Then(ar =>
                    {
                        var dataType = (TransmissionType)ar[0];
                        var data = ar[1] as byte[];

                        var (_, parsed) = Codec.Parse(data, dataType.Offset, this, null, dataType);

                        parsed.Then(resources => reply.Trigger(resources))
                              .Error(ex => reply.TriggerError(ex));

                        //Codec.ParseResourceArray(content, 0, (uint)content.Length, this)
                        //                      .Then(resources => reply.Trigger(resources));

                    }).Error(ex => reply.TriggerError(ex));

        return reply;
    }


    /// <summary>
    /// Create a new resource.
    /// </summary>
    /// <param name="store">The store in which the resource is saved.</param>
    /// <param name="className">Class full name.</param>
    /// <param name="parameters">Constructor parameters.</param>
    /// <param name="attributes">Resource attributeds.</param>
    /// <param name="values">Values for the resource properties.</param>
    /// <returns>New resource instance</returns>
    public AsyncReply<DistributedResource> Create(IStore store, IResource parent, string className, object[] parameters, Map<string, object> attributes, Map<string, object> values)
    {
        var reply = new AsyncReply<DistributedResource>();
        var pkt = new BinaryList()
                                .AddUInt32(store.Instance.Id)
                                .AddUInt32(parent.Instance.Id)
                                .AddUInt8((byte)className.Length)
                                .AddString(className)
                                .AddUInt8Array(Codec.Compose(parameters, this))
                                .AddUInt8Array(Codec.Compose(attributes, this))
                                .AddUInt8Array(Codec.Compose(values, this));

        pkt.InsertInt32(8, pkt.Length);

        SendRequest(IIPPacket.IIPPacketAction.CreateResource)
            .AddUInt8Array(pkt.ToArray())
            .Done()
            .Then(args =>
            {
                var rid = (uint)args[0];

                Fetch(rid, null).Then((r) =>
                {
                    reply.Trigger(r);
                });

            });

        return reply;
    }

    private void Subscribe(IResource resource)
    {
        lock (subscriptionsLock)
        {
            resource.Instance.EventOccurred += Instance_EventOccurred;
            resource.Instance.CustomEventOccurred += Instance_CustomEventOccurred;
            resource.Instance.PropertyModified += Instance_PropertyModified;
            resource.Instance.Destroyed += Instance_ResourceDestroyed;

            subscriptions.Add(resource, new List<byte>());
        }
    }

    private void Unsubscribe(IResource resource)
    {
        lock (subscriptionsLock)
        {
            // do something with the list...
            resource.Instance.EventOccurred -= Instance_EventOccurred;
            resource.Instance.CustomEventOccurred -= Instance_CustomEventOccurred;
            resource.Instance.PropertyModified -= Instance_PropertyModified;
            resource.Instance.Destroyed -= Instance_ResourceDestroyed;

            subscriptions.Remove(resource);
        }

    }

    private void UnsubscribeAll()
    {
        lock (subscriptionsLock)
        {
            foreach (var resource in subscriptions.Keys)
            {
                resource.Instance.EventOccurred -= Instance_EventOccurred;
                resource.Instance.CustomEventOccurred -= Instance_CustomEventOccurred;
                resource.Instance.PropertyModified -= Instance_PropertyModified;
                resource.Instance.Destroyed -= Instance_ResourceDestroyed;
            }

            subscriptions.Clear();
        }
    }

    private void Instance_ResourceDestroyed(IResource resource)
    {

        Unsubscribe(resource);
        // compose the packet
        SendEvent(IIPPacket.IIPPacketEvent.ResourceDestroyed)
                    .AddUInt32(resource.Instance.Id)
                    .Done();


    }

    private void Instance_PropertyModified(PropertyModificationInfo info)
    {
        //var pt = resource.Instance.Template.GetPropertyTemplateByName(name);
        // if (pt == null)
        //    return;

        SendEvent(IIPPacket.IIPPacketEvent.PropertyUpdated)
                    .AddUInt32(info.Resource.Instance.Id)
                    .AddUInt8(info.PropertyTemplate.Index)
                    .AddUInt8Array(Codec.Compose(info.Value, this))
                    .Done();

    }

    //        private void Instance_EventOccurred(IResource resource, string name, string[] users, DistributedConnection[] connections, object[] args)

    private void Instance_CustomEventOccurred(CustomEventOccurredInfo info)
    {
        if (info.EventTemplate.Listenable)
        {
            lock (subscriptionsLock)
            {
                // check the client requested listen
                if (!subscriptions.ContainsKey(info.Resource))
                    return;

                if (!subscriptions[info.Resource].Contains(info.EventTemplate.Index))
                    return;
            }
        }

        if (!info.Receivers(this.session))
            return;

        if (info.Resource.Instance.Applicable(this.session, ActionType.ReceiveEvent, info.EventTemplate, info.Issuer) == Ruling.Denied)
            return;


        // compose the packet
        SendEvent(IIPPacket.IIPPacketEvent.EventOccurred)
                    .AddUInt32(info.Resource.Instance.Id)
                    .AddUInt8((byte)info.EventTemplate.Index)
                    .AddUInt8Array(Codec.Compose(info.Value, this))
                    .Done();
    }

    private void Instance_EventOccurred(EventOccurredInfo info)
    {
        if (info.EventTemplate.Listenable)
        {
            lock (subscriptionsLock)
            {
                // check the client requested listen
                if (!subscriptions.ContainsKey(info.Resource))
                    return;

                if (!subscriptions[info.Resource].Contains(info.EventTemplate.Index))
                    return;
            }
        }

        if (info.Resource.Instance.Applicable(this.session, ActionType.ReceiveEvent, info.EventTemplate, null) == Ruling.Denied)
            return;

        // compose the packet
        SendEvent(IIPPacket.IIPPacketEvent.EventOccurred)
                    .AddUInt32(info.Resource.Instance.Id)
                    .AddUInt8((byte)info.EventTemplate.Index)
                    .AddUInt8Array(Codec.Compose(info.Value, this))
                    .Done();
    }
}
