<?php
require_once ('wagent-prot.php');
require_once ('check_file.php');
require_once ('external/sockets/src/Exception/SocketException.php');
require_once ('external/sockets/src/Socket.php');
require_once ('external/sockets/src/Server.php');

use Navarr\Socket\Exception\SocketException;
use Navarr\Socket\Socket;
use Navarr\Socket\Server;

function _log($data)
{
    $result = print_r($data, true);
    file_put_contents("agent_server.txt", $result ."\n", FILE_APPEND);
}

class AgentServer extends Server
{
    const AUTOREPLY_NONE = 1;
    const AUTOREPLY_DELIVERED = 2;
    const AUTOREPLY_READ = 3;

    const PENDIND_EVENTS_FILE = "pending_events.dat";

    const MAXREAD_LINE = 4096;

    private $wa = null;
    private $registration = null;
    private $number = null;
    private $eventQueue = null;
    private $timeStart = 0;
    private $idleTimeout = 0;
    private $everPolledMessages = false;
    private $forcedEventFlushed = true;
    private $isCleaningUp = false;
    private $isActive = false;
    private $nickname = "";
    private $autoReplyMode = AUTOREPLY_DELIVERED;

    private function Initialize ($number, $nickname, $autoReplyMode = AUTOREPLY_DELIVERED)
    {
        if ($this->IsInitialized())
            return;

        $readReceipt      = false;
        $deliveredReceipt = false;

        if ($autoReplyMode == AgentServer::AUTOREPLY_NONE) {
            $readReceipt = false;
            $deliveredReceipt = false;
        }
        if ($autoReplyMode == AgentServer::AUTOREPLY_DELIVERED) {
            $readReceipt = false;
            $deliveredReceipt = true;
        }
        if ($autoReplyMode == AgentServer::AUTOREPLY_READ) {
            $readReceipt = true;
            $deliveredReceipt = true;
        }

        $this->number = $number;
        $this->nickname = $nickname;
        $this->autoReplyMode = $autoReplyMode;

        if ($this->eventQueue == null)
            $this->eventQueue = new SplQueue();

        $this->wa = new WagentProt($number, $nickname, /* debug output */ false);
        $this->wa->enableReadReceipt($readReceipt);
        $this->wa->enableDeliveredReceipt($deliveredReceipt);

        $this->BindEvents ();
        $this->RestorePendingEvents();
    }

    function GetRegistration ()
    {
        if ($this->number == null)
            return null;
        if ($this->registration == null)
            $this->CreateRegistration();
        return $this->registration;
    }

    function LiteConnect ($number)
    {
        $this->number = $number;
        $this->CreateRegistration();
    }

    function Connect ($number, $nickname, $autoReplyMode = AUTOREPLY_DELIVERED)
    {
        $this->Initialize ($number, $nickname, $autoReplyMode);
        $this->wa->connect();
    }

    function PushEvent ($eventName, $eventData)
    {
        $item ["name"] = $eventName;
        $item ["data"] = $eventData;
        $this->eventQueue->enqueue($item);
    }

    function PopEvent ()
    {
        if ($this->eventQueue->isEmpty())
            return null;
        return $this->eventQueue->dequeue ();
    }

    function Close ()
    {
        if ($this->wa != null) {
            $this->wa->disconnect();
            $this->isActive = false;
        }
    }

    function Login ($password)
    {
        try {
            $this->wa->loginWithPassword ($password);
            return true;
        } catch (LoginFailureException $e) {
            return false;
        }
    }

    function ParseID ($jids)
    {
        if (!is_array($jids))
            return WagentProt::ParseID ($jids);

        $result = array();
        foreach ($jids as $key => $jid) {
            $result[$key] = WagentProt::ParseID ($jid);
        }
        return $result;
    }

    public function OnCredentialsBad ($phoneNumber, $status, $reason, $id)
    {
        $params ["status"] = $status;
        $params ["reason"] = $reason;
        $params ["code"] = "403";
        $params ["id"] = $id;
        $this->PushEvent("oncredentialsbad", $params);
    }

    public function OnCredentialsGood ($phoneNumber, $login, $pw, $type, $expiration, $kind, $price, $cost, $currency, $price_expiration, $id)
    {
        $params ["login"] = $login;
        $params ["pw"] = $pw;
        $params ["type"] = $type;
        $params ["expiration"] = $expiration;
        $params ["kind"] = $kind;
        $params ["price"] = $price;
        $params ["cost"] = $cost;
        $params ["currency"] = $currency;
        $params ["price_expiration"] = $price_expiration;
        $params ["id"] = $id;
        $this->PushEvent("oncredentialsgood", $params);
    }

    public function OnCodeRegisterFailed ($phoneNumber, $status, $reason, $retry_after, $id)
    {
        $params ["id"] = $id;
        $params ["status"] = $status;
        $params ["reason"] = $reason;
        $params ["retry_after"] = $retry_after;
        $params ["code"] = "403";
        $this->PushEvent("oncoderegisterfailed", $params);
    }

    public function OnCodeRegister ($phoneNumber, $login, $pw, $type, $expiration, $kind, $price, $cost, $currency, $price_expiration, $id)
    {
        $params ["id"] = $id;
        $params ["login"] = $login;
        $params ["pw"] = $pw;
        $params ["type"] = $type;
        $params ["expiration"] = $expiration;
        $params ["kind"] = $kind;
        $params ["price"] = $price;
        $params ["cost"] = $cost;
        $params ["currency"] = $currency;
        $params ["price_expiration"] = $price_expiration;
        $this->PushEvent("oncoderegister", $params);
    }

    public function OnCodeRequestFailedTooRecent ($phoneNumber, $method, $reason, $retry_after, $id)
    {
        $params ["id"] = $id;
        $params ["method"] = $method;
        $params ["reason"] = $reason;
        $params ["code"] = "400";
        $params ["retry_after"] = $retry_after;
        $this->PushEvent("oncoderequestfailedtoorecent", $params);
    }

    public function OnCodeRequestFailedTooManyGuesses ($phoneNumber, $method, $reason, $retry_after, $id)
    {
        $params ["id"] = $id;
        $params ["method"] = $method;
        $params ["reason"] = $reason;
        $params ["retry_after"] = $retry_after;
        $params ["code"] = "401";
        $this->PushEvent("oncoderequestfailedtoomanyguesses", $params);
    }

    public function OnCodeRequestFailed ($phoneNumber, $method, $reason, $param, $id)
    {
        $params ["id"] = $id;
        $params ["method"] = $method;
        $params ["reason"] = $reason;
        $params ["param"] = $param;
        $params ["code"] = "401";
        $this->PushEvent("oncoderequestfailed", $params);
    }

    public function OnCodeRequest ($phoneNumber, $method, $length, $id)
    {
        $params ["id"] = $id;
        $params ["method"] = $method;
        $params ["length"] = $length;
        $this->PushEvent("oncoderequest", $params);
    }

    public function OnWhatsAppConnectError ($phoneNumber, $socket)
    {
         $this->Cleanup();
    }

    public function OnWhatsAppDisconnect ($phoneNumber, $socket)
    {
         $this->Cleanup();
    }

    public function OnMessageReceivedServer ($phoneNumber, $from, $id, $class, $t)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["class"] = $class;
        $params ["t"] = $t;
        $this->PushEvent("onmessagereceivedserver", $params);
    }

    public function OnMessageReceivedClient ($phoneNumber, $from, $id, $class, $t, $participant = null)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["class"] = $class;
        $params ["t"] = $t;
        $params ["participant"] = WagentProt::ParseID($participant);
        $this->PushEvent("onmessagereceivedclient", $params);
    }

    public function OnGetPrivacySettings($phoneNumber, $id, $values)
    {
        $params ["id"] = $id;
        $params ["values"] = $values;
        $this->PushEvent("ongetprivacysettings", $params);
    }

    public function OnGetClientConfig($phoneNumber, $id, $platform, $clientId, $lg, $lc, $preview, $default, $groups, $call)
    {
        $params ["id"] = $id;
        $params ["platform"] = $platform;
        $params ["clientid"] = $clientId;
        $params ["lg"] = $lg;
        $params ["lc"] = $lc;
        $params ["preview"] = $preview;
        $params ["default"] = $default;
        $params ["groups"] = $groups;
        $params ["call"] = $call;
        $this->PushEvent("ongetclientconfig", $params);
    }

    public function OnGetMessage ($phoneNumber, $from, $id, $type, $t, $notify, $data)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["type"] = $type;
        $params ["t"] = $t;
        $params ["notify"] = $notify;
        $params ["data"] = $data;
        $this->PushEvent("ongetmessage", $params);
    }

    public function OnGetGroupImage ($phoneNumber,
                                     $from,
                                     $participant,
                                     $id,
                                     $type,
                                     $t,
                                     $notify,
                                     $size,
                                     $url,
                                     $file,
                                     $mimetype,
                                     $filehash,
                                     $width,
                                     $height,
                                     $data,
                                     $caption
                                     )
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["participant"] = WagentProt::ParseID($participant);
        $params ["id"] = $id;
        $params ["type"] = $type;
        $params ["t"] = $t;
        $params ["notify"] = $notify;
        $params ["size"] = $size;
        $params ["url"] = $url;
        $params ["file"] = $file;
        $params ["mimetype"] = $mimetype;
        $params ["filehash"] = $filehash;
        $params ["width"] = $width;
        $params ["height"] = $height;
        $params ["data"] = $data;
        $params ["caption"] = $caption;

        $this->PushEvent("ongetgroupimage", $params);
    }

    public function OnGetGroupVideo ($phoneNumber,
                                     $from,
                                     $participant,
                                     $id,
                                     $type,
                                     $t,
                                     $notify,
                                     $url,
                                     $file,
                                     $size,
                                     $mimetype,
                                     $filehash,
                                     $duration,
                                     $vcodec,
                                     $acodec,
                                     $data,
                                     $caption,
                                     $width,
                                     $height,
                                     $fps,
                                     $vbitrate,
                                     $asampfreq,
                                     $asampfmt,
                                     $abitrate
                                     )
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["participant"] = WagentProt::ParseID($participant);
        $params ["id"] = $id;
        $params ["type"] = $type;
        $params ["t"] = $t;
        $params ["notify"] = $notify;
        $params ["url"] = $url;
        $params ["file"] = $file;
        $params ["size"] = $size;
        $params ["mimetype"] = $mimetype;
        $params ["filehash"] = $filehash;
        $params ["duration"] = $duration;
        $params ["vcodec"] = $vcodec;
        $params ["acodec"] = $acodec;
        $params ["data"] = $data;
        $params ["caption"] = $caption;
        $params ["width"] = $width;
        $params ["height"] = $height;
        $params ["fps"] = $fps;
        $params ["vbitrate"] = $vbitrate;
        $params ["asampfreq"] = $asampfreq;
        $params ["asampfmt"] = $asampfmt;
        $params ["abitrate"] = $abitrate;

        $this->PushEvent("ongetgroupvideo", $params);
    }

    public function OnGetGroupMessage ($phoneNumber, $from, $participant, $id, $type, $t, $notify, $data)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["participant"] = WagentProt::ParseID($participant);
        $params ["id"] = $id;
        $params ["type"] = $type;
        $params ["t"] = $t;
        $params ["notify"] = $notify;
        $params ["data"] = $data;
        $this->PushEvent("ongetgroupmessage", $params);
    }

    public function OnGetImage ($phoneNumber, $from, $id, $type, $t, $notify,
                                $size,
                                $url,
                                $file,
                                $mimetype,
                                $filehash,
                                $width,
                                $height,
                                $data,
                                $caption)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["type"] = $type;
        $params ["t"] = $t;
        $params ["notify"] = $notify;
        $params ["size"] = $size;
        $params ["url"] = $url;
        $params ["file"] = $file;
        $params ["mimetype"] = $mimetype;
        $params ["filehash"] = $filehash;
        $params ["width"] = $width;
        $params ["height"] = $height;
        $params ["data"] = $data;
        $params ["caption"] = $caption;
        $this->PushEvent("ongetimage", $params);
    }

    public function OnGetVideo ($phoneNumber, $from, $id, $type, $t, $notify,
                                $url,
                                $file,
                                $size,
                                $mimetype,
                                $filehash,
                                $duration,
                                $vcodec,
                                $acodec,
                                $data,
                                $caption,
                                $width,
                                $height,
                                $fps,
                                $vbitrate,
                                $asampfreq,
                                $asampfmt,
                                $abitrate)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["type"] = $type;
        $params ["t"] = $t;
        $params ["notify"] = $notify;
        $params ["size"] = $size;
        $params ["url"] = $url;
        $params ["file"] = $file;
        $params ["mimetype"] = $mimetype;
        $params ["filehash"] = $filehash;
        $params ["duration"] = $duration;
        $params ["vcodec"] = $vcodec;
        $params ["acodec"] = $acodec;
        $params ["data"] = $data;
        $params ["caption"] = $caption;
        $params ["width"] = $width;
        $params ["height"] = $height;
        $params ["fps"] = $fps;
        $params ["vbitrate"] = $vbitrate;
        $params ["asampfreq"] = $asampfreq;
        $params ["asampfmt"] = $asampfmt;
        $params ["abitrate"] = $abitrate;
        $this->PushEvent("ongetvideo", $params);
    }

    public function OnGetAudio ($phoneNumber, $from, $id, $type, $t, $notify,
                                $size,
                                $url,
                                $file,
                                $mimetype,
                                $filehash,
                                $seconds,
                                $acodec,
                                $participant)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["type"] = $type;
        $params ["t"] = $t;
        $params ["notify"] = $notify;
        $params ["size"] = $size;
        $params ["url"] = $url;
        $params ["file"] = $file;
        $params ["mimetype"] = $mimetype;
        $params ["filehash"] = $filehash;
        $params ["seconds"] = $seconds;
        $params ["acodec"] = $acodec;
        $params ["participant"] = WagentProt::ParseID ($participant);
        $this->PushEvent("ongetaudio", $params);
    }

    public function OnGetVCard ($phoneNumber, $from, $id, $type, $t, $notify,
                                $name,
                                $data, $participant)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["type"] = $type;
        $params ["t"] = $t;
        $params ["notify"] = $notify;
        $params ["name"] = $name;
        $params ["data"] = $data;
        $params ["participant"] = WagentProt::ParseID($participant);
        $this->PushEvent("ongetvcard", $params);
    }

    public function OnGetLocation ($phoneNumber, $from, $id, $type, $t, $notify,
                                $name,
                                $longitude,
                                $latitude,
                                $url,
                                $data, $participant)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["type"] = $type;
        $params ["t"] = $t;
        $params ["notify"] = $notify;
        $params ["name"] = $name;
        $params ["longitude"] = $longitude;
        $params ["latitude"] = $latitude;
        $params ["url"] = $url;
        $params ["data"] = $data;
        $params ["participant"] = WagentProt::ParseID($participant);
        $this->PushEvent("ongetlocation", $params);
    }

    public function OnGroupsParticipantAdd ($phoneNumber, $groupId, $id)
    {
        $params ["groupid"] = WagentProt::ParseID ($groupId);
        $params ["id"] = $id;
        $this->PushEvent("ongroupsparticipantadd", $params);
    }

    public function OnGroupsParticipantChangedNumber($phoneNumber, $groupId, $time, $oldNumber, $notify, $newNumber) 
    {
        $params ["groupid"] = WagentProt::ParseID ($groupId);
        $params ["time"] = $time;
        $params ["oldnumber"] = $oldNumber;
        $params ["notify"] = $notify;
        $params ["newnumber"] = $newNumber;
        $this->PushEvent("ongroupsparticipantchangednumber", $params);
    }

    public function OnParticipantPromote($phoneNumber, $id, $groupId, $participant) 
    {
        $params ["id"] = $id;
        $params ["groupid"] = WagentProt::ParseID ($groupId);
        $params ["participant"] = $this->ParseID ($participant);
        $this->PushEvent("onparticipantpromote", $params);
    }

    public function OnSetPicture($phoneNumber, $id)
    {
        $params ["id"] = $id;
        $this->PushEvent("onsetpicture", $params);
    }

    public function OnStatusUpdate($phoneNumber, $id)
    {
        $params ["id"] = $id;
        $this->PushEvent("onstatusupdate", $params);
    }

    public function OnParticipantDemote($phoneNumber, $id, $groupId, $participant) 
    {
        $params ["id"] = $id;
        $params ["groupid"] = WagentProt::ParseID ($groupId);
        $params ["participant"] = $this->ParseID ($participant);
        $this->PushEvent("onparticipantdemote", $params);
    }

    public function OnGroupsParticipantRemove ($phoneNumber, $groupId, $id)
    {
        $params ["groupid"] = WagentProt::ParseID ($groupId);
        $params ["id"] = $id;
        $this->PushEvent("ongroupsparticipantremove", $params);
    }

    public function OnParticipantAdded($phoneNumber, $groupId, $jid, $id) 
    {
        $params ["groupid"] = WagentProt::ParseID ($groupId);
        $params ["jid"] = WagentProt::ParseID ($jid);
        $params ["id"] = $id;
        $this->PushEvent("onparticipantadded", $params);
    }

    public function OnParticipantRemoved($phoneNumber, $groupId, $jid, $id) 
    {
        $params ["groupid"] = WagentProt::ParseID ($groupId);
        $params ["jid"] = WagentProt::ParseID ($jid);
        $params ["id"] = $id;
        $this->PushEvent("onparticipantremoved", $params);
    }

    public function OnMessageComposing ($phoneNumber, $from, $id, $status, $t)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["status"] = $status;
        $params ["t"] = $t;
        $this->PushEvent("onmessagecomposing", $params);
    }

    public function OnMessagePaused ($phoneNumber, $from, $id, $status, $t)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["status"] = $status;
        $params ["t"] = $t;
        $this->PushEvent("onmessagepaused", $params);
    }

    public function OnGetSyncResult ($result, $id)
    {
        $result->existing = $this->ParseID($result->existing);
        $result->nonExisting = $this->ParseID($result->nonExisting);

        $params ["result"] = $result;
        $params ["id"] = $id;
        $this->PushEvent("ongetsyncresult", $params);
    }

    public function OnGetReceipt ($from, $id, $offline, $retry, $participant)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["offline"] = $offline;
        $params ["retry"] = $retry;
        $params ["participant"] = WagentProt::ParseID($participant);
        $this->PushEvent("ongetreceipt", $params);
    }

    public function OnGetPrivacyBlockedList ($phoneNumber, $id, $result)
    {
        $params ["result"] = $this->ParseID($result);
        $params ["id"] = $id;
        $this->PushEvent("ongetprivacyblockedlist", $params);
    }

    public function OnGetServerProperties ($phoneNumber, $id, $version, $props)
    {
        $params ["id"] = $id;
        $params ["version"] = $version;
        $params ["props"] = $props;
        $this->PushEvent("ongetserverproperties", $params);
    }

    public function OnGroupsChatCreate ($phoneNumber, $groupId, $id)
    {
        $params ["groupid"] = WagentProt::ParseID ($groupId);
        $params ["id"] = $id;
        $this->PushEvent("ongroupschatcreate", $params);
    }

    public function OnGroupsSubjectSet ($phoneNumber, $groupId, $id)
    {
        $params ["groupid"] = WagentProt::ParseID ($groupId);
        $params ["id"] = $id;
        $this->PushEvent("ongroupssubjectset", $params);
    }

    public function OnGroupsChatEnd ($phoneNumber, $groupId,  $id)
    {
        $params ["groupid"] = $groupId;
        $params ["id"] = $id;
        $this->PushEvent("ongroupschatend", $params);
    }

    public function OnGetGroups ($phoneNumber, $id, $groupList)
    {
        $params ["id"] = $id;
        foreach ($groupList as $key => $group) {
            $groupList [$key] ["creator"] = $this->ParseID ($group ["creator"]);
            $groupList [$key] ["s_o"] = $this->ParseID ($group ["s_o"]);
        }
        $params ["grouplist"] = $groupList;
        $this->PushEvent("ongetgroups", $params);
    }

    public function OnGetGroupV2Info ($phoneNumber,
                $groupID,
                $creator,
                $creation,
                $subject,
                $participants,
                $admins,
                $fromGetGroups, $msgid)
    {
        $info ["groupid"] = $groupID;
        $info ["creator"] = $creator;
        $info ["creation"] = $creation;
        $info ["subject"] = $subject;
        $info ["participants"] = $this->ParseID($participants);
        $info ["admins"] = $this->ParseID($admins);

        if (!$fromGetGroups || $msgid == null) {
            $this->PushEvent("ongetgroupv2info", $info);
            return;
        }

        $index = $this->GetEventIndex ($msgid);
        if ($index == -1) {
            $this->PushEvent("ongetgroupv2info", $info);
            return;
        }

        $this->UpdateGroupEventParticipants ($index, $groupID, $info ["participants"], $info ["admins"]);
    }

    function UpdateGroupEventParticipants ($index, $groupID, $participants, $admins)
    {
        $event = $this->GetEventFromIndex ($index);

        if ($event == null)
            return;

        $data = $event ["data"];

        if(!isset($data["grouplist"]))
            return;

        $groupList = $data["grouplist"];

        foreach ($groupList as $key => $group) {
            if ($groupList [$key] ["id"] != $groupID)
                continue;
            $groupList [$key] ["participants"] = $participants;
            $groupList [$key] ["admins"] = $admins;
        }

        $data ["grouplist"] = $groupList;
        $event ["data"] = $data;

        $this->ReplaceEventAtIndex ($index, $event);
    }

    public function OnGetStatuses ($phoneNumber, $id, $statuses)
    {
        $params ["id"] = $id;

        $hasError = false;

        // Cleanup all from values with only the phonenumber.
        foreach ($statuses as $key => $value)
            $statuses [$key]["from"] = $this->ParseID ($value ["from"]);
        // Check if there is any error result,
        foreach ($statuses as $key => $value) {
            if (empty($value["code"]))
                continue;

            $code = $value["code"];

            if ($code[0] == '4') {
                $hasError = true;
                break;
            }
        }
        
        $params ["statuses"] = $statuses;

        if ($hasError)
            $params ["code"] = "207";

        $this->PushEvent("ongetstatuses", $params);
    }

    public function OnGetStatus ($phoneNumber, $from, $status, $id, $t, $data)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["status"] = $status;
        $params ["id"] = $id;
        $params ["t"] = $t;
        $params ["data"] = $data;
        $this->PushEvent("ongetstatus", $params);
    }

    public function OnGetError ($phoneNumber, $from, $id, $tag, $type)
    {
        $code = $tag->getAttribute ('code');
        $text = $tag->getAttribute ('text');

        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        if (empty($code))
            $params ["code"] = "";
        else
            $params ["code"] = $code;
        if (empty($text))
            $params ["text"] = "";
        else
            $params ["text"] = $text;
        $params ["type"] = $type;
        $this->PushEvent("ongeterror", $params);
    }

    public function OnStreamError ($tag)
    {
        $text = $tag->getAttribute ('text');
        if (empty($text))
            $params ["text"] = "Stream Error: " . $tag->getTag();
        else
            $params ["text"] = "Stream Error: " . $text;
        $params ["code"] = "500";

        $this->PushEvent("onstreamerror", $params);

        $this->Cleanup();
    }

    public function OnProfilePictureChanged ($phoneNumber, $from, $id, $t)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["t"] = $t;
        $this->PushEvent("onprofilepicturechanged", $params);
    }

    public function OnProfilePictureDeleted ($phoneNumber, $from, $id, $t)
    {
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["id"] = $id;
        $params ["t"] = $t;
        $this->PushEvent("onprofilepicturedeleted", $params);
    }

    public function OnMediaUploadFailed ($phoneNumber, $id, $node, $messageNode, $text)
    {
        $params ["id"] = $id;
        $params ["code"] = "400";
        $params ["text"] = $text;
        $this->PushEvent("onmediauploadfailed", $params);
    }

    public function OnMediaMessageSent ($phoneNumber, $to,
                $id,
                $filetype,
                $url,
                $filename,
                $filesize,
                $filehash,
                $caption,
                $icon)
    {
        $params ["filetype"] = $filetype;
        $params ["id"] = $id;
        $params ["url"] = $url;
        $params ["filename"] = $filename;
        $params ["filehash"] = $filehash;
        $params ["filesize"] = $filesize;
        $params ["caption"] = $caption;
        $this->PushEvent("onmediamessagesent", $params);
    }

    public function OnClose ($phoneNumber, $error)
    {
         $this->Cleanup();
    }

    public function OnSendMessage ($phoneNumber, $targets, $id, $node)
    {
        $params ["targets"] = $this->ParseID($targets);
        $params ["id"] = $id;
        $this->PushEvent("onsendmessage", $params);
    }

    public function OnSendMessageReceived ($phoneNumber, $id, $from, $type)
    {
        $params ["id"] = $id;
        $params ["from"] = WagentProt::ParseID ($from);
        $params ["type"] = $type;
        $this->PushEvent("onsendmessagereceived", $params);
    }

    function BindRegistrationEvents ()
    {
        $this->registration->eventManager()->bind("onCredentialsBad", array($this, "OnCredentialsBad"));
        $this->registration->eventManager()->bind("onCodeRegisterFailed", array($this, "OnCodeRegisterFailed"));
        $this->registration->eventManager()->bind("onCredentialsGood", array($this, "OnCredentialsGood"));
        $this->registration->eventManager()->bind("onCodeRegister", array($this, "OnCodeRegister"));
        $this->registration->eventManager()->bind("onCodeRequestFailedTooRecent", array($this, "OnCodeRequestFailedTooRecent"));
        $this->registration->eventManager()->bind("onCodeRequestFailedTooManyGuesses", array($this, "OnCodeRequestFailedTooManyGuesses"));
        $this->registration->eventManager()->bind("onCodeRequest", array($this, "OnCodeRequest"));
        $this->registration->eventManager()->bind("onCodeRequestFailed", array($this, "OnCodeRequestFailed"));
    }

    function BindEvents ()
    {
        $this->wa->eventManager()->bind("onPresenceAvailable", array($this, "OnPresenceAvailableReceived"));
        $this->wa->eventManager()->bind("onPresenceUnavailable", array($this, "OnPresenceUnavailableReceived"));
        $this->wa->eventManager()->bind("onConnectError", array($this, "OnWhatsAppConnectError"));
        $this->wa->eventManager()->bind("onDisconnect", array($this, "OnWhatsAppDisconnect"));
        $this->wa->eventManager()->bind("onMessageComposing", array($this, "OnMessageComposing"));
        $this->wa->eventManager()->bind("onMessagePaused", array($this, "OnMessagePaused"));
        $this->wa->eventManager()->bind("onGetImage", array($this, "OnGetImage"));
        $this->wa->eventManager()->bind("onGetVideo", array($this, "OnGetVideo"));
        $this->wa->eventManager()->bind("onGetAudio", array($this, "OnGetAudio"));
        $this->wa->eventManager()->bind("onGetvCard", array($this, "OnGetvCard"));
        $this->wa->eventManager()->bind("onGetSyncResult", array($this, "OnGetSyncResult"));
        $this->wa->eventManager()->bind("onGetReceipt", array($this, "OnGetReceipt"));
        $this->wa->eventManager()->bind("onGetPrivacyBlockedList", array($this, "OnGetPrivacyBlockedList"));
        $this->wa->eventManager()->bind("onGetServerProperties", array($this, "OnGetServerProperties"));
        $this->wa->eventManager()->bind("onGetProfilePicture", array($this, "OnGetProfilePicture"));
        $this->wa->eventManager()->bind("onGroupsChatCreate", array($this, "OnGroupsChatCreate"));
        $this->wa->eventManager()->bind("onGroupsSubjectSet", array($this, "OnGroupsSubjectSet"));
        $this->wa->eventManager()->bind("onGetGroups", array($this, "OnGetGroups"));
        $this->wa->eventManager()->bind("onGroupsChatEnd", array($this, "OnGroupsChatEnd"));
        $this->wa->eventManager()->bind("onGetGroupV2Info", array($this, "OnGetGroupV2Info"));
        $this->wa->eventManager()->bind("onGetStatus", array($this, "OnGetStatus"));
        $this->wa->eventManager()->bind("onGetStatuses", array($this, "OnGetStatuses"));
        $this->wa->eventManager()->bind("onGetError", array($this, "OnGetError"));
        $this->wa->eventManager()->bind("onStreamError", array($this, "OnStreamError"));
        $this->wa->eventManager()->bind("onProfilePictureChanged", array($this, "OnProfilePictureChanged"));
        $this->wa->eventManager()->bind("onProfilePictureDeleted", array($this, "OnProfilePictureDeleted"));
        $this->wa->eventManager()->bind("onParticipantsPromote", array($this, "OnParticipantPromote"));
        $this->wa->eventManager()->bind("onParticipantsDemote", array($this, "OnParticipantDemote"));
        $this->wa->eventManager()->bind("onSetPicture", array($this, "OnSetPicture"));
        $this->wa->eventManager()->bind("onStatusUpdate", array($this, "OnStatusUpdate"));
        $this->wa->eventManager()->bind("onGroupsParticipantsRemove", array($this, "OnGroupsParticipantRemove"));
        $this->wa->eventManager()->bind("onGroupsParticipantsAdd", array($this, "OnGroupsParticipantAdd"));
        $this->wa->eventManager()->bind("onParticipantAdded", array($this, "OnParticipantAdded"));
        $this->wa->eventManager()->bind("onParticipantRemoved", array($this, "OnParticipantRemoved"));
        $this->wa->eventManager()->bind("onGroupsParticipantChangedNumber", array($this, "OnGroupsParticipantChangedNumber"));
        $this->wa->eventManager()->bind("onMediaUploadFailed", array($this, "OnMediaUploadFailed"));
        $this->wa->eventManager()->bind("onMediaMessageSent", array($this, "OnMediaMessageSent"));
        $this->wa->eventManager()->bind("onClose", array($this, "OnClose"));
        $this->wa->eventManager()->bind("onSendMessage", array($this, "OnSendMessage"));
        $this->wa->eventManager()->bind("onSendMessageReceived", array($this, "OnSendMessageReceived"));
        $this->wa->eventManager()->bind("onMessageReceivedServer", array($this, "OnMessageReceivedServer"));
        $this->wa->eventManager()->bind("onMessageReceivedClient", array($this, "OnMessageReceivedClient"));
        $this->wa->eventManager()->bind("onGetClientConfig", array($this, "OnGetClientConfig"));
        $this->wa->eventManager()->bind("onGetPrivacySettings", array($this, "OnGetPrivacySettings"));
        $this->wa->eventManager()->bind("onGetMessage", array($this, "OnGetMessage"));
        $this->wa->eventManager()->bind("onGetLocation", array($this, "OnGetLocation"));
        $this->wa->eventManager()->bind("onGetGroupMessage", array($this, "OnGetGroupMessage"));
        $this->wa->eventManager()->bind("onGetGroupImage", array($this, "OnGetGroupImage"));
        $this->wa->eventManager()->bind("onGetGroupVideo", array($this, "OnGetGroupVideo"));
    }

    public function GetPicturesFolder ()
    {
        $picturesFolder = getcwd() . DIRECTORY_SEPARATOR . Constants::DATA_FOLDER . DIRECTORY_SEPARATOR . Constants::PICTURES_FOLDER;
        return $picturesFolder;
    }

    public function OnGetProfilePicture($phoneNumber, $id, $from, $type, $data)
    {
        $from = WagentProt::ParseID ($from);

        if ($type == "preview")
            $filename = $this->GetPicturesFolder (). DIRECTORY_SEPARATOR . $from . ".preview.jpg";
        else
            $filename = $this->GetPicturesFolder () . DIRECTORY_SEPARATOR. $from . ".jpg";
        
        $fp = @fopen($filename, "w");
        if ($fp) {
            fwrite($fp, $data);
            fclose($fp);
        }
        $params ["id"] = $id;
        $params ["from"] = $from;
        $params ["type"] = $type;
        $params ["filename"] = $filename;
        $this->PushEvent("ongetprofilepicture", $params);
    }

    public function OnPresenceAvailableReceived($phoneNumber, $from)
    {
        $params ["type"] = "available";
        $params ["from"] = WagentProt::ParseID($from);
        $params ["id"] = "";
        $this->PushEvent("onpresence", $params);
    }

    public function OnPresenceUnavailableReceived($phoneNumber, $from, $last)
    {
        $params ["from"] = WagentProt::ParseID($from);
        $params ["type"] = "unavailable";
        $params ["last"] = $last;
        $params ["id"] = "";
        $this->PushEvent("onpresence", $params);
    }

    public function __construct($ip = null, $timeout = 30)
    {
        $this->idleTimeout = $timeout;
        $socketTimeout = null;
        if ($timeout > 5)
            $socketTimeout = $timeout / 5;

        parent::__construct($ip, 0, $socketTimeout);

        $this->masterSocket->setBlocking(true);

        $this->addHook(Server::HOOK_CONNECT, array($this, 'OnConnect'));
        $this->addHook(Server::HOOK_INPUT, array($this, 'OnInput'));
        $this->addHook(Server::HOOK_DISCONNECT, array($this, 'OnDisconnect'));
        $this->addHook(Server::HOOK_TIMEOUT, array($this, 'OnTimeout'));
        $this->readType = PHP_NORMAL_READ;
        $this->ResetIdleTimer ();
    }

    public function OnConnect(Server $server, Socket $client, $message)
    {
    }

    public function Start ()
    {
        $this->run ();
    }

    function ResetIdleTimer ()
    {
        $this->timeStart = microtime(true);
    }

    function ForceServerQuit ()
    {
        $this->timeStart = 0;
    }

    function IdleTime ()
    {
        return microtime(true) - $this->timeStart;
    }

    public function OnTimeout(Server $server, Socket $client)
    {
        $this->SafePollMessage ();

        $idleTime = $this->IdleTime ();

        if ($idleTime > $this->idleTimeout) {
          $this->Cleanup();
          return Server::RETURN_HALT_SERVER;
        }
    }

    /**
     * Overrideable Read Functionality
     * @param Socket $client
     * @return string
     */
    protected function read(Socket $client)
    {
        try {
          return parent::read ($client);
        }
        catch (SocketException $e)
        {
        	return '';
        }
    }

    public function OnInput(Server $server, Socket $client, $message)
    {
        $message = trim ($message);
        if (empty ($message))
            return;
        try {

          $this->ResetIdleTimer ();
          $quit = $this->HandleMessage($client, $message);

          if ($quit) {
              $this->Cleanup();
              return Server::RETURN_HALT_SERVER;
          }
        }
        catch (Exception $e)
        {
          // Something went wrong...just close the our current connecton
          $this->Cleanup();
        }
    }

    private function IsSocketWritable ()
    {
        $r = array();
        $w = array($this->wa->getSocket());
        $e = array();
        $s = socket_select($r, $w, $e, 0, 10);
        return $s != 0;
    }

    private function CreateRegistration ()
    {
        $this->registration = new Registration($this->number);
        if ($this->eventQueue == null)
            $this->eventQueue = new SplQueue();
        $this->BindRegistrationEvents();
    }

    function IsRegistrationInitialized ()
    {
        if (!isset ($this->registration) || $this->registration == null)
            return false;
        return true;
    }

    function IsLiteConnected ()
    {
        if ($this->IsRegistrationInitialized ())
            return true;
        if (!empty($this->number))
            return true;
        return false;
    }

    function IsInitialized ()
    {
        if (!isset($this->wa) || $this->wa == null)
            return false;
        return true;
    }


    function IsConnected ()
    {
      if (!isset($this->wa) || $this->wa == null)
          return false;
      if (!$this->wa->isConnected())
          return false;
      if(!is_resource($this->wa->getSocket ()))
          return false;
      return $this->IsSocketWritable ();
    }    

    function IsLoggedIn ()
    {
      if (!$this->IsConnected())
        return false;
      return $this->wa->isLoggedIn();
    }

    function Cleanup ()
    {
        if (!isset($this->wa) || $this->wa == null || $this->isCleaningUp)
            return;
        $this->isCleaningUp = true;
        $this->isActive = false;

        $this->SavePendingEvents ();

        $this->wa->disconnect ();
        $this->wa = null;
        unset($this->wa);

        unset ($this->eventQueue);
        $this->eventQueue = null;
        $this->isCleaningUp = false;
    }

    private function GetEventsFile ()
    {
        $eventsFile = getcwd() . DIRECTORY_SEPARATOR . Constants::DATA_FOLDER . DIRECTORY_SEPARATOR . AgentServer::PENDIND_EVENTS_FILE;
        return $eventsFile;
    }

    private function RestorePendingEvents ()
    {
        $eventsFile = $this->GetEventsFile ();
        if (!file_exists($eventsFile))
            return;
        $data = file_get_contents($eventsFile);
        unlink($eventsFile);
        if (empty($data))
            return;
        $this->eventQueue->unserialize($data);
    }

    private function RemoveStreamErrorEvents ()
    {
        if ($this->eventQueue->isEmpty())
            return;

        $indexesToRemove = array();

        $this->eventQueue->rewind();

        while ($this->eventQueue->valid()) {

            $event = $this->eventQueue->current();
            $eventName  = $event ["name"];

            if ($eventName == "onstreamerror")
                $indexesToRemove [] = $this->eventQueue->key();

            $this->eventQueue->next();
        }

        foreach (array_reverse($indexesToRemove) as $index)
            $this->eventQueue->offsetUnset($index);

        $this->eventQueue->rewind();
    }

    private function SavePendingEvents ()
    {
        $eventsFile = $this->GetEventsFile ();

        $this->RemoveStreamErrorEvents();

        if (empty($this->eventQueue) || $this->eventQueue->isEmpty())
            return;

        $data = $this->eventQueue->serialize();
        file_put_contents($eventsFile, $data);
    }

    public function OnDisconnect(Server $server, Socket $client, $message)
    {

    }

    function SafePollMessage ($forcePeek = false)
    {
      if (!$this->IsConnected ())
          return;
      return $this->PollMessages ($forcePeek);
    }

    function PollMessages ($forcePeek = false)
    {
        if (!$this->IsConnected ())
            return;
        //
        //  HACK: This call is here due pollMessage bug,
        //  calling pollMessage not necessarily bring all events, we need to send something to the server to receive
        //  pending events
        $polledAnyEvent = false;

        $this->everPolledMessages = true;
        for ($i = 0; $i < 750; $i++) {
                if (!$this->IsConnected ())
                    break;
                if(!$this->wa->pollMessage())
                    break;
            $polledAnyEvent = true;
        }
        // HACK if pollMessage failed to fetch any new messages, send a getGroups node to force
        // server to puke the events
        if (!$polledAnyEvent && $forcePeek == true) {

            if ($this->forcedEventFlushed) {
                $this->wa->sendGetGroups();
                $this->forcedEventFlushed = false;
            }

            for ($i = 0; i < 750; $i++) {
                if (!$this->IsConnected ())
                    break;
                if(!$this->wa->pollMessage($this->wa->aReceipt, $this->wa->receiptType))
                    break;
            }
        }
    }

    function ReadArray (Socket $client)
    {
        $array = $this->ReadLine ($client);
        if ($array == null)
            return null;
        return explode ('|', $array);
    }

    function ReadInt (Socket $client)
    {
        $line = $this->ReadLine ($client);
        if (empty($line))
            return 0;
        return intval($line);
    }

    function ReadBool (Socket $client)
    {
        $line = $this->ReadLine ($client);
        if (empty($line))
            return false;
        return strcasecmp($line, "false") != 0;
    }

    private function ReadSocketLine (Socket $client)
    {
        $buff = $client->read (AgentServer::MAXREAD_LINE, PHP_NORMAL_READ);
        $len = strlen ($buff);
        $line = $buff;

        if ($len <= 0)
            return $line;

        while ($buff [$len - 1] != "\n") {

            $buff = $client->read (AgentServer::MAXREAD_LINE, PHP_NORMAL_READ);
            $len = strlen ($buff);

            if ($len <= 0)
                return $line;

            $line .= $buff;
        }
        return $line;
    }

    function ReadLine (Socket $client)
    {
        // There is a good reason why the ReadLine is written like this:
        // TELNET send extra \n and empty spaces 
        // So to keep easy to test the server from Python or from Telnet we ignore the extra empty spaces
        // To send empty string we send a fake "empty-token"

        do {
            $read = $this->ReadSocketLine ($client);
            $line   = trim($read);
        } while ($line !== "0" && empty ($line));

        $value = str_replace("\\n", "\n", $line);
        if ($value == "_none_a35825979c956e5a1f068e7cb21c280c")
            return null;
        return $value;
    }

    function WriteLine (Socket $client, $data)
    {
        if ($data == null || $data == "")
            $line = "_none_a35825979c956e5a1f068e7cb21c280c";
        else
            $line = str_replace("\n", "\\n", $data);
        $client->send ($line . "\r\n");
    }

    function WriteObject (Socket $client, $data)
    {
        if (empty($data))
            $data_str = "{}";
        else
            $data_str = json_encode($data);
        $this->WriteLine ($client, $data_str);
    }

    function HandleMessage (Socket $client, $message)
    {
        switch ($message) {
            case "isliteconnected":
                $isliteconnected = $this->IsLiteConnected();
                if ($isliteconnected)
                  $this->WriteLine ($client, "1");
                else
                  $this->WriteLine ($client, "0");
                break;
            case "isconnected":
                $isconnected = $this->IsConnected ();
                if ($isconnected)
                  $this->WriteLine ($client, "1");
                else
                  $this->WriteLine ($client, "0");
                break;
            case "isloggedin":
                $isloggedin = $this->IsLoggedIn ();
                if ($isloggedin)
                  $this->WriteLine ($client, "1");
                else
                  $this->WriteLine ($client, "0");
                break;
            case "liteconnect":
                $number  = $this->ReadLine($client);
                $this->LiteConnect ($number);
                $this->WriteLine ($client, "OK");
                break;
            case "connect":
                $number  = $this->ReadLine($client);
                $nickname = $this->ReadLine($client);
                $autoReply = $this->ReadInt($client);

                $this->Connect ($number, $nickname, $autoReply);
                $this->WriteLine ($client, "OK");
                break;
            case "login":
                $password = $this->ReadLine($client);
                $ok = $this->Login ($password);
                if (!$ok)
                    $this->WriteLine ($client, "ERROR");
                else
                    $this->WriteLine ($client, "OK");
                break;
            case "disconnect":
                $this->Close ();
                break;
            case "sendactivestatus":
                $this->SendActiveStatus ();
                $this->WriteLine ($client, "OK");
                break;
            case "sendofflinestatus":
                $this->SendOfflineStatus();
                $this->WriteLine ($client, "OK");
                break;
            case "sendgetgroups":
                $result = $this->SendGetGroups ();
                $this->WriteObject ($client, $result);
                break;
            case "sendchangenumber":
                $number = $this->ReadLine($client);
                $result = $this->SendChangeNumber ($number);
                $this->WriteObject ($client, $result);
                break;
            case "sendgetclientconfig":
                $result = $this->SendGetClientConfig ();
                $this->WriteObject ($client, $result);
                break;
            case "sendgetgroupv2info":
                $groupId = $this->ReadLine($client);
                $this->SendGetGroupV2Info ($groupId);
                $this->WriteLine ($client, "OK");
                break;
            case "peekevents":
                $this->PeekEvents ($client);
                $this->WriteLine ($client, "OK");
                break;
            case "peekeventsforce":
                $this->PeekEvents ($client, true);
                $this->WriteLine ($client, "OK");
                break;
            case "sendsetprivacysettings":
                $category = $this->ReadLine($client);
                $value  = $this->ReadLine($client);
                $result = $this->SendSetPrivacySettings($category, $value);
                $this->WriteObject ($client, $result);
                break;
            case "sendgetprivacysettings":
                $result = $this->SendGetPrivacySettings();
                $this->WriteObject ($client, $result);
                break;
            case "sendgetprofilepicture":
                $number = $this->ReadLine($client);
                $type = $this->ReadLine($client);
                $result = $this->SendGetProfilePicture($number, $type);
                $this->WriteObject ($client, $result);
                break;
            case "sendgetserverproperties":
                $result = $this->SendGetServerProperties();
                $this->WriteObject ($client, $result);
                break;
            case "sendextendaccount":
                $this->SendExtendAccount();
                $this->WriteLine ($client, "OK");
                break;
            case "sendremoveaccount":
                $lg = $this->ReadLine($client);
                $lc = $this->ReadLine($client);
                $feedback = $this->ReadLine($client);
                $result = $this->SendRemoveAccount($lg, $lc, $feedback);
                $this->WriteObject ($client, $result);
                break;
            case "sendgetstatuses":
                $jids = $this->ReadArray($client);
                $result = $this->SendGetStatuses($jids);
                $this->WriteObject ($client, $result);
                break;
            case "sendgroupschatcreate":
                $subject = $this->ReadLine($client);
                $participants = $this->ReadArray($client);
                $result = $this->SendGroupsChatCreate($subject, $participants);
                $this->WriteObject ($client, $result);
                break;
            case "sendsetgroupsubject":
                $gjid = $this->ReadLine($client);
                $subject = $this->ReadLine($client);
                $result = $this->SendSetGroupSubject($gjid, $subject);
                $this->WriteObject ($client, $result);
                break;
            case "sendgroupsleave":
                $gjid = $this->ReadLine($client);
                $result = $this->SendGroupsLeave($gjid);
                $this->WriteObject ($client, $result);
                break;
            case "sendgroupsparticipantadd":
                $gjid = $this->ReadLine($client);
                $participant = $this->ReadLine($client);
                $result = $this->SendGroupsParticipantAdd($gjid, $participant);
                $this->WriteObject ($client, $result);
                break;
            case "sendgroupsparticipantremove":
                $gjid = $this->ReadLine($client);
                $participant = $this->ReadLine($client);
                $result = $this->SendGroupsParticipantRemove($gjid, $participant);
                $this->WriteObject ($client, $result);
                break;
            case "sendpromoteparticipant":
                $gjid = $this->ReadLine($client);
                $participant = $this->ReadLine($client);
                $result = $this->SendPromoteParticipant($gjid, $participant);
                $this->WriteObject ($client, $result);
                break;
            case "senddemoteparticipant":
                $gjid = $this->ReadLine($client);
                $participant = $this->ReadLine($client);
                $result = $this->SendDemoteParticipant($gjid, $participant);
                $this->WriteObject ($client, $result);
                break;
            case "createmessageid":
                $result = $this->CreateMessageId ();
                $this->WriteLine ($client, $result);
                break;
            case "sendmessage":
                $target  = $this->ReadLine($client);
                $message = $this->ReadLine($client);
                $id = $this->ReadLine($client);
                $result = $this->SendMessage ($target, $message, $id);
                $this->WriteObject ($client, $result);
                break;
            case "sendmessageread":
                $to = $this->ReadLine($client);
                $id = $this->ReadLine($client);
                $this->SendMessageRead($to, $id);
                $this->WriteLine ($client, "OK");
                break;
            case "sendmessagereadbatch":
                $to = $this->ReadLine($client);
                $ids = $this->ReadArray($client);
                $this->SendMessageReadBatch($to, $ids);
                $this->WriteLine ($client, "OK");
                break;
            case "sendgroupmessageread":
                $to = $this->ReadLine($client);
                $id = $this->ReadLine($client);
                $participant = $this->ReadLine($client);
                $this->SendGroupMessageRead($to, $id, $participant);
                $this->WriteLine ($client, "OK");
                break;
            case "sendgroupmessagereadbatch":
                $to = $this->ReadLine($client);
                $ids = $this->ReadArray($client);
                $participant = $this->ReadLine($client);
                $this->SendGroupMessageReadBatch($to, $ids, $participant);
                $this->WriteLine ($client, "OK");
                break;
            case "sendmessagedelivered":
                $to = $this->ReadLine($client);
                $id = $this->ReadLine($client);
                $this->SendMessageDelivered($to, $id);
                $this->WriteLine ($client, "OK");
                break;
            case "sendmessagedeliveredbatch":
                $to = $this->ReadLine($client);
                $ids = $this->ReadArray($client);
                $this->SendMessageDeliveredBatch($to, $ids);
                $this->WriteLine ($client, "OK");
                break;
            case "sendgroupmessagedelivered":
                $to = $this->ReadLine($client);
                $id = $this->ReadLine($client);
                $participant = $this->ReadLine($client);
                $this->SendGroupMessageDelivered($to, $id, $participant);
                $this->WriteLine ($client, "OK");
                break;
            case "sendgroupmessagedeliveredbatch":
                $to = $this->ReadLine($client);
                $ids = $this->ReadArray($client);
                $participant = $this->ReadLine($client);
                $this->SendGroupMessageDeliveredBatch($to, $ids, $participant);
                $this->WriteLine ($client, "OK");
                break;
            case "sendmessageaudio":
                $to             = $this->ReadLine($client);
                $imageURL       = $this->ReadLine($client);
                $voice          = $this->ReadBool($client);
                $id             = $this->ReadLine($client);
                $storeURLmedia  = $this->ReadBool($client);
                $file_size      = $this->ReadInt ($client);
                $file_hash      = $this->ReadLine($client);

                $result = $this->SendMessageAudio($to, $imageURL, $voice, $id, $storeURLmedia, $file_size, $file_hash);
                $this->WriteObject ($client, $result);
                break;
            case "sendmessagecomposing":
                $to = $this->ReadLine($client);
                $this->SendMessageComposing($to);
                $this->WriteLine ($client, "OK");
                break;
            case "sendmessagepaused":
                $to = $this->ReadLine($client);
                $this->SendMessagePaused($to);
                $this->WriteLine ($client, "OK");
                break;
            case "sendmessageimage":
                $to              = $this->ReadLine($client);
                $imageURL        = $this->ReadLine($client);
                $caption         = $this->ReadLine($client);
                $messageId       = $this->ReadLine($client);
                $storeURLmedia   = $this->ReadBool($client);
                $file_size       = $this->ReadInt($client);
                $file_hash       = $this->ReadLine($client);

                $result = $this->SendMessageImage($to, $imageURL, $caption, $messageId, $storeURLmedia, $file_size, $file_hash);
                $this->WriteObject ($client, $result);
                break;
            case "sendmessagelocation":
                $to         = $this->ReadLine($client);
                $latitude   = $this->ReadLine($client);
                $longitude  = $this->ReadLine($client);
                $name       = $this->ReadLine($client);
                $url        = $this->ReadLine($client);
                $messageId  = $this->ReadLine($client);

                $result = $this->SendMessageLocation($to, $latitude, $longitude, $name, $url, $messageId);
                $this->WriteObject ($client, $result);
                break;
            case "sendmessagevideo":
                $to              = $this->ReadLine($client);
                $imageURL        = $this->ReadLine($client);
                $caption         = $this->ReadLine($client);
                $messageId       = $this->ReadLine($client);
                $storeURLmedia   = $this->ReadBool($client);
                $file_size       = $this->ReadInt($client);
                $file_hash       = $this->ReadLine($client);
                
                $result = $this->SendMessageVideo($to, $imageURL, $caption, $messageId, $storeURLmedia, $file_size, $file_hash);
                $this->WriteObject ($client, $result);
                break;
            case "sendupdatenickname":
                $nickname = $this->ReadLine($client);
                $this->UpdateNickname($nickname);
                $this->WriteLine ($client, "OK");
                break;
            case "sendgetpresences":
                $to = $this->ReadArray($client);
                $result = $this->SendGetPresences($to);
                $this->WriteObject($client, $result);
                break;
            case "sendsetprivacyblockedlist":
                $blockedJids = $this->ReadArray($client);
                $this->SendSetPrivacyBlockedList($blockedJids);
                $this->WriteLine ($client, "OK");
                break;
            case "sendgetprivacyblockedlist":
                $result = $this->SendGetPrivacyBlockedList();
                $this->WriteObject ($client, $result);
                break;
            case "sendsetprofilepicture":
                $path = $this->ReadLine($client);
                $result = $this->SendSetProfilePicture($path);
                $this->WriteObject($client, $result);
                break;
            case "sendremoveprofilepicture":
                $result = $this->SendRemoveProfilePicture();
                $this->WriteObject($client, $result);
                break;
            case "sendstatusupdate":
                $text = $this->ReadLine($client);
                $result = $this->SendStatusUpdate($text);
                $this->WriteObject($client, $result);
                break;
            case "sendvcard":
                $to = $this->ReadLine($client);
                $vCard = $this->ReadLine($client);
                $name = $this->ReadLine($client);
                $id = $this->ReadLine($client);
                $result = $this->SendVcard($to, $vCard,$name, $id);
                $this->WriteObject($client, $result);
                break;
            case "sendsync":
                $numbers = $this->ReadArray($client);
                $deletedNumbers = $this->ReadArray($client);
                $syncType = $this->ReadInt($client);
                $result = $this->SendSync($numbers, $deletedNumbers, $syncType);
                $this->WriteObject($client, $result);
                break;
            case "checkcredentials":
                $res = $this->CheckCredentials();
                $this->WriteObject ($client, $res);
                break;
            case "coderegister":
                $code = $this->ReadLine($client);
                $res = $this->CodeRegister($code);
                $this->WriteObject ($client, $res);
                break;
            case "coderequest":
                $method = $this->ReadLine($client);
                $res = $this->CodeRequest($method);
                $this->WriteObject ($client, $res);
                break;
            case "quit":
                return true;
        }
        return false;
    }

    function CreateRequestId ()
    {
        return $this->wa->createRequestId ();
    }

    function PeekEvents ($client, $forcePeek = false)
    {
        // Ensure PollMessages was called.
        if (!$this->everPolledMessages || $forcePeek) {
            $this->SafePollMessage($forcePeek);
        }
        // Flush any server events
        $event = $this->PopEvent ();
        while (!empty($event))
        {
            $name = $event ["name"];
            $data = $event ["data"];
            $this->WriteLine ($client, $name);
            $this->WriteObject ($client, $data);
            $event = $this->PopEvent ();
        }

        $this->forcedEventFlushed = true;
    }

    function GetEventIndex ($id)
    {
        if ($this->eventQueue->isEmpty() || $id == null)
            return -1;

        $result = null;
        $this->eventQueue->rewind();
        
        while ($this->eventQueue->valid()) {
            $event = $this->eventQueue->current();
            $data  = $event ["data"];

            if (!isset($data ["id"])) {
                $this->eventQueue->next();
                continue;
            }

            if ($data ["id"] == $id) {
                $index = $this->eventQueue->key();
                return $index;
            }
            $this->eventQueue->next();
        }
        $this->eventQueue->rewind();
        return -1;
    }

    function GetEventFromIndex ($index)
    {
        if ($index == null ||  $index == -1 || !$this->eventQueue->offsetExists ($index))
            return null;
        return $this->eventQueue->offsetGet ($index);
    }

    function ReplaceEventAtIndex ($index, $event)
    {
        $this->eventQueue->offsetSet ($index, $event);
    }

    function GetEventResult ($id)
    {
        if ($this->eventQueue->isEmpty()) {
            return null;
        }

        $result = null;
        $streamError = null;
        $this->eventQueue->rewind();
        
        while ($this->eventQueue->valid()) {
            $event = $this->eventQueue->current();
            $data  = $event ["data"];

            if (!isset($data ["id"])) {
                $this->eventQueue->next();
                continue;
            }


            if ($data ["id"] == $id) {
                if (empty($data ["code"]))
                    $data["code"] = "200";
                
                $result = $data;
                $index = $this->eventQueue->key();
                $this->eventQueue->offsetUnset($index);
                break;
            }
            //
            // If some stream error happened, we will return the result as 500 - Stream error
            // we do not break the loop because maybe we got a result before the Stream error
            if ($event ["name"] == "onstreamerror")
                $streamError = $data;
            $this->eventQueue->next();
        }

        $this->eventQueue->rewind();

        if ($result == null && $streamError != null)
            $result = $streamError;
        return $result;
    }

    function GetEventResultsFromName ($name)
    {
        if ($this->eventQueue->isEmpty())
            return null;

        $indexesToRemove = array();
        $results         = array();
        $streamError = null;

        $this->eventQueue->rewind();
        
        while ($this->eventQueue->valid()) {

            $event = $this->eventQueue->current();

            $eventName  = $event ["name"];
            $data       = $event ["data"];

            if ($eventName == $name) {
                if (empty($data ["code"]))
                    $data["code"] = "200";
                if (!in_array($results, $data, true)) {
                    $results [] = $data;
                    $indexesToRemove [] = $this->eventQueue->key();
                }
            }
            //
            // If some stream error happened, we will return the result as 500 - Stream error
            // we do not break the loop because maybe we got a result before the Stream error
            if ($eventName == "onstreamerror")
                $streamError = $data;
            $this->eventQueue->next();
        }

        foreach (array_reverse($indexesToRemove) as $index)
            $this->eventQueue->offsetUnset($index);

        $this->eventQueue->rewind();

        if (count ($results) == 0 && $streamError != null)
            $results [] = $streamError;

        return $results;
    }

    function SendActiveStatus ()
    {
        $this->isActive = true;
        $this->wa->sendActiveStatus();
    }

    function SendOfflineStatus()
    {
        $this->wa->sendOfflineStatus();
        $this->isActive = false;
    }

    function SendGetGroups ()
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendGetGroups($id);

        return $this->GetEventResult ($id);
    }

    function SendChangeNumber($number)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendChangeNumber($number, $this->wa->getIdentity(), $id);

        return $this->GetEventResult ($id);
    }

    function SendGetClientConfig()
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendGetClientConfig($id);
        
        return $this->GetEventResult ($id);
    }

    function SendGetGroupV2Info ($groupId)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendGetGroups($groupId, $id);

        return $this->GetEventResult ($id);
    }

    function SendGetPrivacyBlockedList ()
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendGetPrivacyBlockedList($id);

        return $this->GetEventResult ($id);
    }

    function SendGetPrivacySettings ()
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendGetPrivacySettings($id);

        return $this->GetEventResult ($id);
    }

    function SendSetPrivacySettings ($category, $value)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendSetPrivacySettings($category, $value, $id);

        return $this->GetEventResult ($id);
    }

    function SendGetProfilePicture ($number, $type)
    {
        $id = $this->CreateRequestId ();

        if ($type == "preview")
            $this->wa->sendGetProfilePicture($number, false, $id);
        else
            $this->wa->sendGetProfilePicture($number, true, $id);

        return $this->GetEventResult ($id);
    }

    function SendGetServerProperties ()
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendGetServerProperties($id);

        return $this->GetEventResult ($id);
    }

    function SendExtendAccount ()
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendExtendAccount($id);

        return $this->GetEventResult ($id);
    }

    function SendGetBroadcastLists ()
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendGetBroadcastLists($id);

        return $this->GetEventResult ($id);
    }

    function SendRemoveAccount ($lg, $lc, $feedback)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendRemoveAccount($lg, $lc, $feedback, $id);

        return $this->GetEventResult ($id);
    }

    function SendGetStatuses ($jids)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendGetStatuses($jids, $id);

        return $this->GetEventResult ($id);
    }

    function SendGroupsChatCreate ($subject, $participants)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendGroupsChatCreate($subject, $participants, $id);

        return $this->GetEventResult ($id);
    }

    function SendSetGroupSubject ($gjid, $subject)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendSetGroupSubject ($gjid, $subject, $id);

        return $this->GetEventResult ($id);
    }

    function SendGroupsLeave  ($gjid)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendGroupsLeave ($gjid,  $id);

        return $this->GetEventResult ($id);
    }

    function  SendGroupsParticipantAdd ($groupId, $participant)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendGroupsParticipantsAdd($groupId, $participant, $id);

        return $this->GetEventResult ($id);
    }

    function SendGroupsParticipantRemove ($groupId, $participant)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendGroupsParticipantsRemove($groupId, $participant, $id);

        return $this->GetEventResult ($id);
    }

    function SendPromoteParticipant ($gId, $participant)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendPromoteParticipants ($gId, $participant, $id);

        return $this->GetEventResult ($id);
    }

    function SendDemoteParticipant ($gId, $participant)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendDemoteParticipants($gId, $participant, $id);

        return $this->GetEventResult ($id);
    }

    function CreateMessageId ()
    {
        return $this->wa->createMessageId ();
    }

    function SendMessage ($target, $message, $id = null)
    {
        if (empty($id))
            $id = $this->CreateMessageId ();

        $this->wa->sendMessage($target , $message, false,  $id);

        return $this->GetEventResult ($id); 
    }

    function SendMessageRead ($to, $id)
    {
        $this->wa->sendMessageRead($to, $id);
    }

    function SendMessageReadBatch ($to, $ids)
    {
        if (!is_array($ids)) {
            $this->SendMessageRead($to, $ids);
            return;
        }

        foreach ($ids as $id) {
            $this->SendMessageRead($to, $id);
        }
    }

    function SendGroupMessageRead ($to, $id, $participant)
    {
        $this->wa->sendMessageRead($to, $id, "read", $participant);
    }

    function SendGroupMessageReadBatch ($to, $ids, $participant)
    {
        if (!is_array($ids)) {
            $this->SendGroupMessageRead($to, $ids, $participant);
            return;
        }

        foreach ($ids as $id) {
            $this->SendGroupMessageRead($to, $id, $participant);
        }
    }

    function SendGroupMessageDelivered ($to, $id, $participant)
    {
        $this->wa->sendMessageRead($to, $id, null, $participant);
    }

    function SendGroupMessageDeliveredBatch ($to, $ids, $participant)
    {
        if (!is_array($ids)) {
            $this->SendGroupMessageDelivered($to, $ids, $participant);
            return;
        }

        foreach ($ids as $id) {
            $this->SendGroupMessageDelivered($to, $id, $participant);
        }
    }

    function SendMessageDelivered ($to, $id)
    {
        $this->wa->sendMessageRead($to, $id, null);
    }

    function SendMessageDeliveredBatch ($to, $ids)
    {
        if (!is_array($ids)) {
            $this->SendMessageDelivered($to, $ids);
            return;
        }

        foreach ($ids as $id) {
            $this->SendMessageDelivered($to, $id);
        }
    }

    function SendMessageComposing($to)
    {
        $this->wa->sendMessageComposing($to);
    }

    function SendMessagePaused($to)
    {
        $this->wa->sendMessagePaused($to);
    }

    function SendMessageAudio($to, $imageURL, $voice = false, $id = null, $storeURLmedia = false, $file_size = 0, $file_hash = "")
    {
         if (empty($id))
            $id = $this->CreateMessageId ();

        if (empty($imageURL) || !CheckFile::Exists($imageURL))
            return array('message' => "Could not find ". $imageURL,'code' => "400");

        $messageId = $this->wa->sendMessageAudio($to, $imageURL, $storeURLmedia, $file_size, $file_hash, $voice, $id);

        $event_result = $this->GetEventResult ($id);
        if ($event_result != null)
            return $event_result;
        if ($messageId != null)
            return array('message' => "Error sending audio",'code' => "500");
        return array('message' => "Error sending audio",'code' => "400");
    }

    function SendMessageImage($to, $imageURL, $caption = "", $id = null, $storeURLmedia = false, $file_size = 0, $file_hash = "")
    {
        if (empty($id))
            $id = $this->CreateMessageId ();

        if (empty($imageURL) || !CheckFile::Exists($imageURL))
            return array('message' => "Could not find (". $imageURL.")" ,'code' => "400");

        $messageId = $this->wa->sendMessageImage($to, $imageURL, $storeURLmedia, $file_size, $file_hash, $caption, $id);

        $event_result = $this->GetEventResult ($id);
        if ($event_result != null)
            return $event_result;
        if ($messageId != null)
            return array('message' => "Error sending image",'code' => "500");
        return array('message' => "Error sending image",'code' => "400");
    }

    function SendMessageLocation($to, $latitude, $longitude, $name = "", $url = "", $id = null)
    {
        if (empty($id))
            $id = $this->CreateMessageId ();

        $this->wa->sendMessageLocation($to, $longitude, $latitude, $name, $url, $id);

        return $this->GetEventResult ($id); 
    }

    function SendMessageVideo($to, $imageURL, $caption = "", $id = null, $storeURLmedia = false, $file_size = 0, $file_hash = "")
    {
        if (empty($id))
            $id = $this->CreateMessageId ();

        if (empty($imageURL) || !CheckFile::Exists($imageURL))
            return array('message' => "Could not find (". $imageURL.")", 'code' => "400");

        $messageId = $this->wa->sendMessageVideo($to, $imageURL, $storeURLmedia, $file_size, $file_hash, $caption, $id);

        $event_result = $this->GetEventResult ($id);
        if ($event_result != null)
            return $event_result;
        if ($messageId != null)
            return array('message' => "Error sending video",'code' => "500");
        return array('message' => "Error sending video",'code' => "400");
    }
    // This method update the Nickname
    // There is no way to update the nickname if we are not in the "Online" status
    // in this case we need to cleanup the connection and reconnect again using a new nickname
    function UpdateNickname($nickname)
    {
        if ($this->isActive) {
            $this->wa->sendAvailableForChat($nickname);
            return;
        }
        $this->Cleanup();
        $this->Connect($this->number, $nickname, $this->autoReplyMode);
    }

    function SendGetPresences($to)
    {
        if (!$this->isActive)
            return array('message' => "You need to be online to subscribe presences ",'code' => "400");

        if (!is_array($to))
            $numbers = array($to);
        else
            $numbers = $to;

        foreach ($numbers as $number)
            $this->wa->sendPresenceSubscription($number);

        $this->SafePollMessage ();

        foreach ($numbers as $number)
            $this->wa->sendPresenceUnsubscription($number);

        $results = $this->GetEventResultsFromName ("onpresence");

        return $results;
    }

    function SendSetGroupPicture($gjid, $path)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendSetGroupPicture($gjid, $path, $id);

        return $this->GetEventResult ($id);
    }

    function SendSetPrivacyBlockedList($blockedJids)
    {
        $this->wa->sendSetPrivacyBlockedList($blockedJids);
    }

    function SendSetProfilePicture($path)
    {
        if (empty($path) || !file_exists($path))
            return array('message' => "Could not find ". $path,'code' => "400");

        $id = $this->CreateRequestId ();

        $this->wa->sendSetProfilePicture($path, $id);

        return $this->GetEventResult ($id);
    }

    function SendRemoveProfilePicture()
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendRemoveProfilePicture($id);

        return $this->GetEventResult ($id);
    }

    function SendStatusUpdate($txt)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendStatusUpdate($txt, $id);

        return $this->GetEventResult ($id);
    }

    function SendVcard($to, $vCard, $name, $id = null)
    {
        if (empty($id))
            $id = $this->CreateMessageId ();

        $this->wa->sendVcard($to, $name, $vCard, $id);

        return $this->GetEventResult ($id);
    }

    function SendSync(array $numbers, array $deletedNumbers = null, $syncType = 3)
    {
        $id = $this->CreateRequestId ();

        $this->wa->sendSync($numbers, $deletedNumbers, $syncType , $id);

        return $this->GetEventResult ($id);
    }

    function CheckCredentials()
    {
        $registration = $this->GetRegistration();
        if ($registration == null)
            return array('message' => "Error checking credentials, no number set",'code' => "500");

        $id = "check_credentials";

        try {
            $registration->checkCredentials($id);
        }
        catch (Exception $e)
        {
            $error = $e->getMessage();
        }

        $result = $this->GetEventResult ($id);

        if (!empty($error)) {
            if (empty($result ["code"]))
                $result ["code"] = "400";
            $result ["error"] = $error;
        }

        return $result;
    }

    function CodeRegister($code)
    {
        $registration = $this->GetRegistration();
        if ($registration == null)
            return array('message' => "Error registering code, no number set",'code' => "500");

        $code = str_replace("-", "", $code);
        $code = trim($code);

        $id = "code_register";
        $error = null;

        try {
            $registration->codeRegister($code, $id);
        } 
        catch (Exception $e)
        {
            $error = $e->getMessage();
        }

        $result = $this->GetEventResult ($id);

        if (!empty($error)) {
            if (empty($result ["code"]))
                $result ["code"] = "400";
            $result ["error"] = $error;
        }

        return $result;
    }

    function CodeRequest($method)
    {
        $registration = $this->GetRegistration();
        if ($registration == null)
            return array('message' => "Error registering code, no number set",'code' => "500");

        $id = "code_request";
        $error = null;

        try {
            $registration->codeRequest($id, $method);
        } 
        catch (Exception $e)
        {
            $error = $e->getMessage();
        }

        $result = $this->GetEventResult ($id);

        if (!empty($error)) {
            if (empty($result ["code"]))
                $result ["code"] = "400";
            $result ["error"] = $error;
        }
        return $result;
    }
}

?>
