/// <summary>  Poll the ZIS for messages pending in the agent's queue.
        /// 
        /// This method is typically called by a ProtocolHandler thread to perform a
        /// periodic pull when the agent is running in pull mode. It may also be
        /// called by the framework to force a pull if the framework requires an
        /// immediate response to a message it has sent to the ZIS, such as a
        /// request for SIF_ZoneStatus.
        /// 
        /// 
        /// If a message is retrieved from the ZIS, it is dispatched through the
        /// Adk's usual message routing mechanism just as pushed messages are. Thus,
        /// there is no difference between push and pull mode once a message has
        /// been obtained from the ZIS. Because message routing is asynchronous (i.e.
        /// the MessageDispatcher will forward the message to the appropriate
        /// framework or agent code), this method does not return a value. If an
        /// error is returned in the SIF_Ack, the agent's FaultListener will be
        /// notified if one is registered. If an exception occurs, it is thrown to
        /// the caller.
        /// 
        /// 
        /// Each time this method is invoked it repeatedly sends SIF_GetMessage to
        /// the ZIS until no more messages are available, effectively emptying out
        /// the agent's queue.
        /// 
        /// </summary>
        /// <returns> zero if no messages were waiting in the agent's queue; 1 if a
        /// message was pulled from the agent's queue; -1 if the zone is sleeping
        /// </returns>
        public virtual int Pull()
        {
            //  Wait for fPullCanStart to be set to true by the ZoneImpl class once
            //  the zone is connected
            if (fPullCanStart != null)
            {
                lock (fPullCanStart)
                {
                    try
                    {
                        Monitor.Wait(fPullCanStart);
                    }
                    catch
                    {
                        // Do Nothing
                    }
                }
            }

            while (true)
            {
                if ((Adk.Debug & AdkDebugFlags.Messaging_Pull) != 0)
                {
                    fZone.Log.Debug("Polling for next message...");
                }

                //  Send a SIF_GetMessage, get a SIF_Ack
                SIF_SystemControl sys = new SIF_SystemControl();
                SIF_SystemControlData cmd = new SIF_SystemControlData();
                cmd.AddChild(new SIF_GetMessage());
                sys.SIF_SystemControlData = cmd;
                SIF_Ack ack = null;
                try
                {
                    ack = send(sys, true);
                }
                catch (PullMessageParseException pmpe)
                {
                    // Unable to parse the pulled message. Try sending the proper
                    // Error SIF_Ack to remove the message from the queue
                    if (pmpe.fSourceMessage != null)
                    {
                        fZone.Log.Debug("Handling exception by creating a SIF_Error", pmpe.fParseException);
                        // Try parsing out the SIF_OriginalMsgId so that we can remove the message
                        // from the queue.
                        // Ack either the SIF_Ack or the internal, embedded message, based on our setting
                        int startIndex = fAckAckOnPull ? 0 : 10;
                        int messageStart = pmpe.fSourceMessage.IndexOf("<SIF_Message", startIndex);
                        SifException sourceException = null;
                        if (pmpe.fParseException is SifException)
                        {
                            sourceException = (SifException)pmpe.fParseException;
                        }
                        else
                        {
                            sourceException = new SifException(
                                SifErrorCategoryCode.Xml,
                                SifErrorCodes.XML_GENERIC_ERROR_1,
                                "Unable to parse pulled SIF_Message",
                                pmpe.fParseException.Message,
                                fZone, pmpe.fParseException );
                        }
                        SIF_Ack errorAck = SIFPrimitives.ackError(pmpe.fSourceMessage.Substring(messageStart), sourceException, fZone);
                        errorAck.SifVersion = sys.SifVersion;
                        send( errorAck );
                        continue;
                    }
                }


                //
                //  Process the response. If status code 9 (no message), no
                //  action is taken. If status code 0 (success), the content of
                //  the SIF_Status / SIF_Data element is parsed and dispatched.
                //  If an error is reported in the ack, the agent's fault
                //  handler is called with a SifException describing the error
                //  if a fault handler has been registered.
                //
                if (ack.HasStatusCode(SifStatusCodes.NO_MESSAGES_9))
                {
                    if ((Adk.Debug & AdkDebugFlags.Messaging_Pull ) != 0)
                        fZone.Log.Debug( "No messages waiting in agent queue" );

                    return 0;
                }

                if(ack.HasError())
                {
                    SifException se = new SifException(ack, fZone);
                    fZone.Log.Debug("Unable to pull the next message from the queue: " + se.ToString());
                    AdkUtils._throw(se, fZone.Log);
                }

                if (ack.HasStatusCode(SifStatusCodes.SUCCESS_0))
                {
                    AdkException parseEx = null;
                    SifMessagePayload payload = getPullMessagePayload(ack);
                    if ((Adk.Debug & ( AdkDebugFlags.Messaging | AdkDebugFlags.Messaging_Pull ) ) != 0)
                    {
                        fZone.Log.Debug("Pulled a " + payload.ElementDef.Tag(payload.SifVersion) + " message (SIF " + payload.SifVersion + ")");
                    }

                    #region	Notify MessagingListeners...

                    bool cancelled = false;
                    ICollection<IMessagingListener> msgList = GetMessagingListeners(fZone);
                    if (msgList.Count > 0)
                    {
                        StringWriter tmp = new StringWriter();
                        SifWriter sifwriter = new SifWriter(tmp);
                        sifwriter.Write(payload);
                        sifwriter.Flush();
                        tmp.Flush();

                        StringBuilder xml = new StringBuilder();
                        xml.Append(tmp.ToString());

                        //	Determine message type before parsing
                        foreach (IMessagingListener listener in msgList)
                        {
                            try
                            {
                                SifMessageType pload =
                                    (SifMessageType)
                                    Adk.Dtd.GetElementType(payload.ElementDef.Name);
                                MessagingReturnCode code = listener.OnMessageReceived(pload, xml);
                                switch (code)
                                {
                                    case MessagingReturnCode.Discard:
                                        cancelled = true;
                                        break;

                                    case MessagingReturnCode.Reparse:
                                        {
                                            try
                                            {
                                                //	Reparse the XML into a new message
                                                payload =
                                                    (SifMessagePayload)
                                                    fParser.Parse(xml.ToString(), fZone);
                                            }
                                            catch (IOException ioe)
                                            {
                                                parseEx =
                                                    new AdkException
                                                        (
                                                        "Failed to reparse message that was modified by MessagingListener: " +
                                                        ioe, fZone);
                                            }
                                        }
                                        break;
                                }
                            }
                            catch (AdkException adke)
                            {
                                parseEx = adke;
                            }
                        }
                    }

                    #endregion

                    if (fQueue != null)
                    {
                        //  TODO: put message on agent local queue
                    }
                    else
                    {
                        if (parseEx != null)
                        {
                            throw parseEx;
                        }

                        int ackStatus = SifStatusCodes.IMMEDIATE_ACK_1;
                        SifException err = null;
                        bool acknowledge = true;

                        try
                        {
                            //  Dispatch the message
                            if (!cancelled)
                            {
                                ackStatus = dispatch(payload);
                            }
                        }
                        catch (LifecycleException)
                        {
                            throw;
                        }
                        catch (SifException se)
                        {
                            err = se;
                        }
                        catch (AdkException adke)
                        {
                            //  TODO: This needs to generate proper category/code based on payload

                            if (adke.HasSifExceptions())
                            {
                                //  Return the first exception
                                err = adke.SIFExceptions[0];
                            }
                            else
                            {
                                //  Build a SifException to describe this AdkException
                                err =
                                    new SifException
                                        (SifErrorCategoryCode.Generic,
                                          SifErrorCodes.GENERIC_GENERIC_ERROR_1, adke.Message, fZone);
                            }
                        }
                        catch (Exception thr)
                        {
                            //  Uncaught exception (probably an Adk internal error)
                            string txt =
                                "An unexpected error occurred while processing a pulled message: " +
                                thr;
                            fZone.Log.Debug(txt);

                            //  Build a SifException to describe this Throwable
                            err =
                                new SifException
                                    (SifErrorCategoryCode.System, SifErrorCodes.SYS_GENERIC_ERROR_1,
                                      thr.Message, thr.ToString(), fZone, thr);

                            fZone.Log.Debug(err);
                        }

                        if (acknowledge)
                        {
                            sendPushAck(ack, payload, ackStatus, err);
                        }
                        else
                            return 1;
                    }
                }
                else
                {
                    // We only get to here if there is no error and no success code
                    if (ack.HasStatusCode(SifStatusCodes.SLEEPING_8))
                    {
                        //  Zone is sleeping
                        return -1;
                    }
                    else
                    {
                        // Unknown condition
                        AdkUtils._throw(new SifException(ack, fZone), fZone.Log );
                    }
                }
            }
        }
 private SIF_Ack SifSystemControl(SifElement command, ZoneImpl zone)
 {
     SIF_SystemControl msg = new SIF_SystemControl(zone.HighestEffectiveZISVersion);
     SIF_SystemControlData cmd = new SIF_SystemControlData();
     cmd.AddChild(command);
     msg.SIF_SystemControlData = cmd;
     return zone.Dispatcher.send(msg);
 }