#---------------------------------------------------------------------------
 #
 #	Title                : EMESARY inter-object communication
 #
 #	File Type            : Implementation File
 #
 #	Description          : Provides generic inter-object communication. For an object to receive a message it
 #	                     : must first register with an instance of a Transmitter, and provide a Receive method
 #
 #	                     : To send a message use a Transmitter with an object. That's all there is to it.
 #  
 #  References           : http://chateau-logic.com/content/emesary-nasal-implementation-flightgear
#                        : http://www.chateau-logic.com/content/class-based-inter-object-communication
 #                       : http://chateau-logic.com/content/emesary-efficient-inter-object-communication-using-interfaces-and-inheritance
 #                       : http://chateau-logic.com/content/c-wpf-application-plumbing-using-emesary
 #
 #	Author               : Richard Harrison (richard@zaretto.com)
 #
 #	Creation Date        : 29 January 2016
 #
 #	Version              : 4.8
 #
 #  Copyright � 2016 Richard Harrison           Released under GPL V2
 #
 #---------------------------------------------------------------------------*/

var __emesaryUniqueId = 14; # 0-15 are reserved, this way the global transmitter will be 15.

# Transmitters send notifications to all recipients that are registered.
var Transmitter =
{
    ReceiptStatus_OK : 0,          # Processing completed successfully
    ReceiptStatus_Fail : 1,        # Processing resulted in at least one failure
    ReceiptStatus_Abort : 2,       # Fatal error, stop processing any further recipieints of this message. Implicitly failed.
    ReceiptStatus_Finished : 3,    # Definitive completion - do not send message to any further recipieints
    ReceiptStatus_NotProcessed : 4,# Return value when method doesn't process a message.
    ReceiptStatus_Pending : 5,     # Message sent with indeterminate return status as processing underway
    ReceiptStatus_PendingFinished : 6,# Message definitively handled, status indeterminate. The message will not be sent any further

    # create a new transmitter. shouldn't need many of these
    new: func(_ident)
    {
        var new_class = { parents: [Transmitter]};
        new_class.Recipients = [];
        new_class.Ident = _ident;
        __emesaryUniqueId += 1;
        new_class.UniqueId = __emesaryUniqueId;
        return new_class;
    },

    # Add a recipient to receive notifications from this transmitter
    Register: func (recipient)
    {
        append(me.Recipients, recipient);
    },

    # Stops a recipient from receiving notifications from this transmitter.
    DeRegister: func(todelete_recipient)
    {
        var out_idx = 0;
        var element_deleted = 0;

        for (var idx = 0; idx < size(me.Recipients); idx += 1)
        {
            if (me.Recipients[idx] != todelete_recipient)
            {
                me.Recipients[out_idx] = me.Recipients[idx];
                out_idx = out_idx + 1;
            }
            else
                element_deleted = 1;
        }

        if (element_deleted)
            pop(me.Recipients);
    },

    RecipientCount: func
    {
        return size(me.Recipients);
    },

    PrintRecipients: func
    {
        print("Emesary: Recipient list for ",me.Ident,"(",me.UniqueId,")");
        for (var idx = 0; idx < size(me.Recipients); idx += 1)
            print("Emesary: Recipient[",idx,"] ",me.Recipients[idx].Ident," (",me.Recipients[idx].UniqueId,")");
    },

    # Notify all registered recipients. Stop when receipt status of abort or finished are received.
    # The receipt status from this method will be 
    #  - OK > message handled
    #  - Fail > message not handled. A status of Abort from a recipient will result in our status
    #           being fail as Abort means that the message was not and cannot be handled, and
    #           allows for usages such as access controls.
    NotifyAll: func(message)
    {
        var return_status = Transmitter.ReceiptStatus_NotProcessed;
        foreach (var recipient; me.Recipients)
        {
            if (recipient.RecipientActive)
            {
                var rstat = recipient.Receive(message);

                if(rstat == Transmitter.ReceiptStatus_Fail)
                {
                    return_status = Transmitter.ReceiptStatus_Fail;
                }
                elsif(rstat == Transmitter.ReceiptStatus_Pending)
                {
                    return_status = Transmitter.ReceiptStatus_Pending;
                }
                elsif(rstat == Transmitter.ReceiptStatus_PendingFinished)
                {
                    return rstat;
                }
#               elsif(rstat == Transmitter.ReceiptStatus_NotProcessed)
#               {
#                   ;
#               }
                elsif(rstat == Transmitter.ReceiptStatus_OK)
                {
                    if (return_status == Transmitter.ReceiptStatus_NotProcessed)
                        return_status = rstat;
                }
                elsif(rstat == Transmitter.ReceiptStatus_Abort)
                {
                    return Transmitter.ReceiptStatus_Abort;
                }
                elsif(rstat == Transmitter.ReceiptStatus_Finished)
                {
                    return Transmitter.ReceiptStatus_OK;
                }
            }
        }
        return return_status;
    },

    # Returns true if a return value from NotifyAll is to be considered a failure.
    IsFailed: func(receiptStatus)
    {
        # Failed is either Fail or Abort.
        # NotProcessed isn't a failure because it hasn't been processed.
        if (receiptStatus == Transmitter.ReceiptStatus_Fail or receiptStatus == Transmitter.ReceiptStatus_Abort)
            return 1;
        return 0;
    }
};

#
#
# Base class for Notifications. By convention a Notification has a type and a value.
#   SubClasses can add extra properties or methods.
# Properties:
# Ident : Generic message identity. Can be an ident, or for simple messages a value that needs transmitting.
# NotificationType  : Notification Type
# IsDistinct : non zero if this message supercedes previous messages of this type.
#              Distinct messages are usually sent often and self contained
#              (i.e. no relative state changes such as toggle value)
#              Messages that indicate an event (such as after a pilot action)
#              will usually be non-distinct. So an example would be gear/up down
#              or ATC acknowledgements that all need to be transmitted
# The IsDistinct is important for any messages that are bridged over MP as
# only the most recently sent distinct message will be transmitted over MP
var Notification =
{
    new: func(_type, _ident)
    {
        var new_class = { parents: [Notification]};
        new_class.Ident = _ident;
        new_class.NotificationType = _type;
        new_class.IsDistinct = 1;
        new_class.FromIncomingBridge = 0;
        new_class.Callsign = nil;
        return new_class;
    },
};

# Inherit or implement class with the same signatures to receive messages.
var Recipient =
{
    new: func(_ident)
    {
        var new_class = { parents: [Recipient]};
        if (_ident == nil or _ident == "")
        {
            _ident = id(new_class);
            print("Emesary Error: Ident required when creating a recipient, defaulting to ",_ident);
        }
        Recipient.construct(_ident, new_class);
    },
    construct: func(_ident, new_class)
    {
        new_class.Ident = _ident;
        new_class.RecipientActive = 1;
        __emesaryUniqueId += 1;
        new_class.UniqueId = __emesaryUniqueId;
        new_class.Receive = func(notification)
        {
            # warning if required function not 
            print("Emesary Error: Receive function not implemented in recipient ",me.Ident);
            return Transmitter.ReceiptStatus_NotProcessed;
        };
        return new_class;
    },
};

#
# Instantiate a Global Transmitter, this is a convenience and a known starting point. Generally most classes will
# use this transmitters, however other transmitters can be created and merely use the global transmitter to discover each other
var GlobalTransmitter =  Transmitter.new("GlobalTransmitter");

#
#
# This is basically a base64 like encode except we just use alphanumerics which gives us a base62 encode.
var BinaryAsciiTransfer = 
{
    alphabet : "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
    encodeInt : func(num)
    {
        if (num == 0)
            return substr(BinaryAsciiTransfer.alphabet,0,1);
        
        var arr="";
        var _base = size(BinaryAsciiTransfer.alphabet);
        while(num > 0)
        {
            var rem = math.mod(num,_base);
            num = int(num / _base);
            arr =substr(BinaryAsciiTransfer.alphabet, rem,1) ~ arr;
        }
        return arr;
    },
    decodeInt : func(v)
    {
        var _base = size(BinaryAsciiTransfer.alphabet);
        var power = size(v) - 1;
        var num = 0;

        var idx = 0;
        for(var idx=0; idx < size(v); idx += 1)
        {
            var c = substr(v,idx,1);
            var cc = find(c,BinaryAsciiTransfer.alphabet);
            if (cc < 0)
                return nil;
            num += int(cc * math.exp(math.ln(_base) * power));
          
            power -= 1;
        }
        return num;
    }
};

var TransferCoord = 
{
# 28 bits = 268435456 (268 435 456)
# to transfer lat lon (360 degree range) 268435456/360=745654
# we could use different factors for lat lon due to the differing range, however
# this will be fine.
 encode : func(v)
 {
     return BinaryAsciiTransfer.encodeInt( (v.lat()+90)*745654) ~ "." ~
       BinaryAsciiTransfer.encodeInt((v.lon()+180)*745654) ~ "." ~
         BinaryAsciiTransfer.encodeInt((v.alt()+1400)*100);
 }
 ,
 decode : func(v)
 {
     var parts = split(".", v);
     var lat = (BinaryAsciiTransfer.decodeInt(parts[0]) / 745654)-90;
     var lon = (BinaryAsciiTransfer.decodeInt(parts[1]) / 745654)-180;
     var alt = (BinaryAsciiTransfer.decodeInt(parts[2]) / 100)-1400;
     return geo.Coord.new().set_latlon(lat, lon).set_alt(alt);
 }
};

var TransferString = 
{
#
# just to pack a valid range and keep the lower and very upper control codes for seperators
# that way we don't need to do anything special to encode the string.
    getalphanumericchar : func(v)
    {
        if (find(v,"-./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_abcdefghijklmnopqrstuvwxyz") > 0)
          return v;
        return nil;
    },
    encode : func(v)
    {
        var l = size(v);
        if (l > 16)
            l = 16;
        var rv = BinaryAsciiTransfer.encodeInt(l);

        for(var ii = 0; ii < l; ii = ii + 1)
        {
            ev = TransferString.getalphanumericchar(substr(v,ii,1));
            if (ev != nil)
                rv = rv ~ ev;
        }
        return rv;
    },
    decode : func(v)
    {
        var l = BinaryAsciiTransfer.decodeInt(v);
        var rv = substr(v,1,l-1);
        return rv;
    }
};

var TransferByte = 
{
    encode : func(v)
    {
        return BinaryAsciiTransfer.encodeInt(v);
    },
    decode : func(v)
    {
        return BinaryAsciiTransfer.decodeInt(v);
    }
};

var TransferInt = 
{
    encode : func(v)
    {
        return BinaryAsciiTransfer.encodeInt(v);
    },
    decode : func(v)
    {
        return BinaryAsciiTransfer.decodeInt(v);
    }
};

var TransferFixedDouble = 
{
    encode : func(v, precision=4)
    {
        return BinaryAsciiTransfer.encodeInt(v*math.exp(math.ln(10) * precision));
    },
    decode : func(v, precision=4)
    {
        return BinaryAsciiTransfer.decodeInt(v)/math.exp(math.ln(10) * precision);
    }
};