<?php
namespace Api\PhpUtils\Kafka;

class KafkaProducer extends Singleton
{
    private $log_level = LOG_WARNING;
    private $delivery_callback = false;
    private $context = false;
    private $conf = array(
        'socket.timeout.ms'                  => 50,
        'socket.blocking.max.ms'             => 50,
        'queue.buffering.max.ms'             => 1,
        'topic.metadata.refresh.sparse'      => true,
        'topic.metadata.refresh.interval.ms' => 600,
        'socket.nagle.disable'               => true,
        'internal.termination.signal'        => SIGIO,
        'request.required.acks'              => 1,
    );

    private $produce;

    /**
     * KafkaProducer constructor.
     *
     * @param bool $delivery_callback $delivery_callback [发送后的回调函数]
     * @param bool $context 上下文参数
     */
    protected function __construct($delivery_callback = false, $context = false, $conf = false)
    {
        if (is_callable($delivery_callback))
        {
            $this->delivery_callback = $delivery_callback;
        }
        $this->context = $context;

        $this->log_level = LOG_WARNING;
        if ($conf != false)
        {
            $this->conf = $conf;
        }

        pcntl_sigprocmask(SIG_BLOCK, array(SIGIO));

        $this->ensure_connect();
    }

    /**
     * 连接kafka
     *
     * @return bool
     */
    private function ensure_connect()
    {
        $conf = new \RdKafka\Conf();
        if (!empty($this->conf))
        {
            foreach ($this->conf as $key => $value)
            {
                $conf->set($key, $value);
            }
        }
        //Set delivery report callback
        $context = $this->context;
        if ($this->delivery_callback != false)
        {
            $conf->setDrMsgCb($this->delivery_callback);
        }
        else
        {
            $conf->setDrMsgCb(
                function ($kafka, $message) use ($context) {
                    if ($message->err)
                    {
                        $msg = $message->errstr() . ' topic : ' . $message->topic_name . (empty($context) ? (' payload : ' . $message->payload) : (' context: ' . json_encode($context)));
                        //LogUtil::ERROR('kafka produce callback error', $msg);
                    }
                    else
                    {
                        $msg = ' topic : ' . $message->topic_name . (empty($context) ? (' payload : ' . $message->payload) : (' context: ' . json_encode($context)));
                        //LogUtil::INFO('kafka produce callback success', $msg);
                    }
                });
        }
        //Set error callback
        $conf->setErrorCb(
            function ($kafka, $err, $reason) {
                //LogUtil::WARNING('kafka error callback', rd_kafka_err2str($err).' reason: '.$reason);
                if ($err == RD_KAFKA_RESP_ERR__ALL_BROKERS_DOWN)
                {
                    return false;
                }
            });
        $this->produce = new \RdKafka\Producer($conf);
        //$this->produce->setLogLevel($this->log_level);
    }

    /**
     * 单条发送
     * 因为rdkafka使用异步发送发送消息，因此本函数无法捕获发送是否成功，如需捕获需使用callback方式，但不适合fpm
     * @param string $broker_list kafka集群地址
     * @param [str] $topic
     * @param [str] $message
     * @param [str] $key
     * @return bool
     */
    public function produce($broker_list, $topic, $message, $key = NULL)
    {
        $this->produce->addBrokers($broker_list);
        $topic = $this->produce->newTopic($topic);
        /**
         * Producing messages
         * The first argument is the partition. RD_KAFKA_PARTITION_UA stands for unassigned, and lets librdkafka choose the partition.
         * 第一个参数是分区，RD_KAFKA_PARTITION_UA 表示未分配，并且由 librdkafka 选择分区。
         * The second argument are message flags and should be either 0 or RD_KAFKA_MSG_F_BLOCK to block produce on full queue.
         * 第二个参数是消息标志，为 0 或 RD_KAFKA_MSG_F_BLOCK，当队列满了时阻止生产消息。
         * The message payload can be anything.
         * 消息可以是任何内容。
         */
        $topic->produce(RD_KAFKA_PARTITION_UA, 0, $message, $key);

        $outQlen = $this->produce->getOutQLen();

        while ($outQlen > 0)
        {
            $outQlen = $this->produce->getOutQLen();

            $this->produce->poll(5);
        }

        return true;
    }
}