<?php


namespace App\Library;


class SMPP
{


    public $socket;
    public $seq = 0;
    public $debug = 0;
    public $data_coding = 0;
    public $timeout = 2;

    /**
     * @param $id
     * @param $data
     *
     * @return array|false|string
     */
    function send_pdu($id, $data): bool|array|string
    {
        if ( ! $this->socket) {
            return "Send PDU: Connection closed!";
        }

        // increment sequence
        $this->seq += 1;
        // PDU = PDU_header + PDU_content
        $pdu = pack('NNNN', strlen($data) + 16, $id, 0, $this->seq).$data;
        // send PDU

        fputs($this->socket, $pdu);

        // Get response length
        $data = fread($this->socket, 4);
        if ( ! $data) {
            return "Send PDU: Connection closed!";
        }
        $tmp = unpack('Nlength', $data);

        $command_length = $tmp['length'];
        if ($command_length < 12) {
            return false;
        }

        // Get response
        $data = fread($this->socket, $command_length - 4);
        $pdu  = unpack('Nid/Nstatus/Nseq', $data);
        if ($this->debug) {
            print "\n< R PDU (id,status,seq): ".join(" ", $pdu);
        }

        return $pdu;
    }


    /**
     * @param $host
     * @param $port
     * @param $system_id
     * @param $password
     *
     * @return bool|string
     */
    function open($host, $port, $system_id, $password): bool|string
    {

        // Open the socket

        $this->socket = @fsockopen($host, $port, $errno, $err_str, $this->timeout);

        if ( ! $this->socket) {
            return "$err_str ($errno)<br />";
        }
        if (function_exists('stream_set_timeout')) {
            stream_set_timeout($this->socket, $this->timeout);
        } // function exists for php4.3+
        if ($this->debug) {
            print "\n> Connected";
        }


        // Send Bind operation
        $data = sprintf("%s\0%s\0", $system_id, $password); // system_id, password
        $data .= sprintf("%s\0%c", "", 0x34);  // system_type, interface_version
        $data .= sprintf("%c%c%s\0", 5, 0, ""); // addr_ton, addr_npi, address_range

        $ret = $this->send_pdu(2, $data);
        if ($this->debug) {
            print "\n> Bind done!";
        }

        if (is_array($ret) && array_key_exists('status', $ret)){
            return ($ret['status'] == 0);
        }

        print "\n> Please check your server firewall";

        return false;
    }

    /**
     * @param $source_addr
     * @param $destination_addr
     * @param $short_message
     * @param string $optional
     *
     * @return bool
     */
    function submit_sm($source_addr, $destination_addr, $short_message, string $optional = ''): bool
    {

        $data = sprintf("%s\0", ""); // service_type
        $data .= sprintf("%c%c%s\0", 5, 0, $source_addr); // source_addr_ton, source_addr_npi, source_addr
        $data .= sprintf("%c%c%s\0", 1, 1, $destination_addr); // dest_addr_ton, dest_addr_npi, destination_addr
        $data .= sprintf("%c%c%c", 0, 0, 0); // esm_class, protocol_id, priority_flag
        $data .= sprintf("%s\0%s\0", "", ""); // schedule_delivery_time, validity_period
        $data .= sprintf("%c%c", 0, 0); // registered_delivery, replace_if_present_flag
        $data .= sprintf("%c%c", $this->data_coding, 0); // data_coding, sm_default_msg_id
        $data .= sprintf("%c%s", strlen($short_message), $short_message); // sm_length, short_message
        $data .= $optional;

        $ret = $this->send_pdu(4, $data);
        if (isset($ret['status'])) {
            return ($ret['status'] == 0);
        }

        return false;

    }

    /**
     * @return bool|string
     */
    function close(): bool|string
    {
        if ( ! $this->socket) {
            return "Send PDU: Connection closed!";
        }

        $this->send_pdu(6, "");
        fclose($this->socket);

        return true;
    }


    /**
     * @param $source_addr
     * @param $destination_addr
     * @param $short_message
     * @param int $utf
     * @param int $flash
     *
     * @return bool
     */
    function send_long($source_addr, $destination_addr, $short_message, int $utf = 0, int $flash = 0): bool
    {

        if ($utf) {
            $this->data_coding = 0x08;
        }

        if ($flash) {
            $this->data_coding = $this->data_coding | 0x10;
        }


        $size = strlen($short_message);
        if ($utf) {
            $size += 20;
        }

        if ($size < 160) { // Only one part :)
            $this->submit_sm($source_addr, $destination_addr, $short_message);

        } else { // Multipart
            $sar_msg_ref_num    = rand(1, 255);
            $sar_total_segments = ceil(strlen($short_message) / 130);

            for ($sar_segment_seq_num = 1; $sar_segment_seq_num <= $sar_total_segments; $sar_segment_seq_num++) {
                $part          = substr($short_message, 0, 130);
                $short_message = substr($short_message, 130);

                $optional = pack('nnn', 0x020C, 2, $sar_msg_ref_num);
                $optional .= pack('nnc', 0x020E, 1, $sar_total_segments);
                $optional .= pack('nnc', 0x020F, 1, $sar_segment_seq_num);

                if ($this->submit_sm($source_addr, $destination_addr, $part, $optional) === false) {
                    return false;
                }

            }
        }

        return true;

    }

}
