<?php
/*
 *
 * Copyright 2018 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
/**
 * Interface exported by the server.
 */
require_once(dirname(__FILE__).'/../../lib/Grpc/BaseStub.php');
require_once(dirname(__FILE__).'/../../lib/Grpc/AbstractCall.php');
require_once(dirname(__FILE__).'/../../lib/Grpc/UnaryCall.php');
require_once(dirname(__FILE__).'/../../lib/Grpc/ClientStreamingCall.php');
require_once(dirname(__FILE__).'/../../lib/Grpc/Interceptor.php');
require_once(dirname(__FILE__).'/../../lib/Grpc/CallInvoker.php');
require_once(dirname(__FILE__).'/../../lib/Grpc/Internal/InterceptorChannel.php');

class SimpleRequest
{
    private $data;
    public function __construct($data)
    {
        $this->data = $data;
    }
    public function setData($data)
    {
        $this->data = $data;
    }
    public function serializeToString()
    {
        return $this->data;
    }
}

class InterceptorClient extends Grpc\BaseStub
{

    /**
     * @param string $hostname hostname
     * @param array $opts channel options
     * @param Channel|InterceptorChannel $channel (optional) re-use channel object
     */
    public function __construct($hostname, $opts, $channel = null)
    {
        parent::__construct($hostname, $opts, $channel);
    }

    /**
     * A simple RPC.
     * @param SimpleRequest $argument input argument
     * @param array $metadata metadata
     * @param array $options call options
     */
    public function UnaryCall(
        SimpleRequest $argument,
        $metadata = [],
        $options = []
    ) {
        return $this->_simpleRequest(
            '/phony_method',
            $argument,
            [],
            $metadata,
            $options
        );
    }

    /**
     * A client-to-server streaming RPC.
     * @param array $metadata metadata
     * @param array $options call options
     */
    public function StreamCall(
        $metadata = [],
        $options = []
    ) {
        return $this->_clientStreamRequest('/phony_method', [], $metadata, $options);
    }
}


class ChangeMetadataInterceptor extends Grpc\Interceptor
{
    public function interceptUnaryUnary($method,
                                        $argument,
                                        $deserialize,
                                        $continuation,
                                        array $metadata = [],
                                        array $options = [])
    {
        $metadata["foo"] = array('interceptor_from_unary_request');
        return $continuation($method, $argument, $deserialize, $metadata, $options);
    }
    public function interceptStreamUnary($method,
                                         $deserialize,
                                         $continuation,
                                         array $metadata = [],
                                         array $options = [])
    {
        $metadata["foo"] = array('interceptor_from_stream_request');
        return $continuation($method, $deserialize, $metadata, $options);
    }
}

class ChangeMetadataInterceptor2 extends Grpc\Interceptor
{
    public function interceptUnaryUnary($method,
                                        $argument,
                                        $deserialize,
                                        $continuation,
                                        array $metadata = [],
                                        array $options = [])
    {
        if (array_key_exists('foo', $metadata)) {
            $metadata['bar'] = array('ChangeMetadataInterceptor should be executed first');
        } else {
            $metadata["bar"] = array('interceptor_from_unary_request');
        }
        return $continuation($method, $argument, $deserialize, $metadata, $options);
    }
    public function interceptStreamUnary($method,
                                         $deserialize,
                                         $continuation,
                                         array $metadata = [],
                                         array $options = [])
    {
        if (array_key_exists('foo', $metadata)) {
            $metadata['bar'] = array('ChangeMetadataInterceptor should be executed first');
        } else {
            $metadata["bar"] = array('interceptor_from_stream_request');
        }
        return $continuation($method, $deserialize, $metadata, $options);
    }
}

class ChangeRequestCall
{
    private $call;

    public function __construct($call)
    {
        $this->call = $call;
    }
    public function getCall()
    {
        return $this->call;
    }

    public function write($request)
    {
        $request->setData('intercepted_stream_request');
        $this->getCall()->write($request);
    }

    public function wait()
    {
        return $this->getCall()->wait();
    }
}

class ChangeRequestInterceptor extends Grpc\Interceptor
{
    public function interceptUnaryUnary($method,
                                        $argument,
                                        $deserialize,
                                        $continuation,
                                        array $metadata = [],
                                        array $options = [])
    {
        $argument->setData('intercepted_unary_request');
        return $continuation($method, $argument, $deserialize, $metadata, $options);
    }
    public function interceptStreamUnary($method,
                                         $deserialize,
                                         $continuation,
                                         array $metadata = [],
                                         array $options = [])
    {
        return new ChangeRequestCall(
            $continuation($method, $deserialize, $metadata, $options)
        );
    }
}

class StopCallInterceptor extends Grpc\Interceptor
{
    public function interceptUnaryUnary($method,
                                        $argument,
                                        $deserialize,
                                        $continuation,
                                        array $metadata = [],
                                        array $options = [])
    {
        $metadata["foo"] = array('interceptor_from_request_response');
    }
    public function interceptStreamUnary($method,
                                         $deserialize,
                                         $continuation,
                                         array $metadata = [],
                                         array $options = [])
    {
        $metadata["foo"] = array('interceptor_from_request_response');
    }
}

class InterceptorTest extends \PHPUnit\Framework\TestCase
{
    private $server;
    private $port;
    private $channel;

    public function setUp(): void
    {
        $this->server = new Grpc\Server([]);
        $this->port = $this->server->addHttp2Port('0.0.0.0:0');
        $this->channel = new Grpc\Channel('localhost:'.$this->port, [
            'force_new' => true,
            'credentials' => Grpc\ChannelCredentials::createInsecure()]);
        $this->server->start();
    }

    public function tearDown(): void
    {
        $this->channel->close();
        unset($this->server);
    }


    public function testClientChangeMetadataOneInterceptor()
    {
        $req_text = 'client_request';
        $channel_matadata_interceptor = new ChangeMetadataInterceptor();
        $intercept_channel = Grpc\Interceptor::intercept($this->channel, $channel_matadata_interceptor);
        $client = new InterceptorClient('localhost:'.$this->port, [
            'force_new' => true,
            'credentials' => Grpc\ChannelCredentials::createInsecure(),
        ], $intercept_channel);
        $req = new SimpleRequest($req_text);
        $unary_call = $client->UnaryCall($req);
        $event = $this->server->requestCall();
        $this->assertSame('/phony_method', $event->method);
        $this->assertSame(['interceptor_from_unary_request'], $event->metadata['foo']);

        $stream_call = $client->StreamCall();
        $stream_call->write($req);
        $event = $this->server->requestCall();
        $this->assertSame('/phony_method', $event->method);
        $this->assertSame(['interceptor_from_stream_request'], $event->metadata['foo']);

        unset($unary_call);
        unset($stream_call);
        unset($server_call);
    }

    public function testClientChangeMetadataTwoInterceptor()
    {
        $req_text = 'client_request';
        $channel_matadata_interceptor = new ChangeMetadataInterceptor();
        $channel_matadata_intercepto2 = new ChangeMetadataInterceptor2();
        // test intercept separately.
        $intercept_channel1 = Grpc\Interceptor::intercept($this->channel, $channel_matadata_interceptor);
        $intercept_channel2 = Grpc\Interceptor::intercept($intercept_channel1, $channel_matadata_intercepto2);
        $client = new InterceptorClient('localhost:'.$this->port, [
            'force_new' => true,
            'credentials' => Grpc\ChannelCredentials::createInsecure(),
        ], $intercept_channel2);

        $req = new SimpleRequest($req_text);
        $unary_call = $client->UnaryCall($req);
        $event = $this->server->requestCall();
        $this->assertSame('/phony_method', $event->method);
        $this->assertSame(['interceptor_from_unary_request'], $event->metadata['foo']);
        $this->assertSame(['interceptor_from_unary_request'], $event->metadata['bar']);

        $stream_call = $client->StreamCall();
        $stream_call->write($req);
        $event = $this->server->requestCall();
        $this->assertSame('/phony_method', $event->method);
        $this->assertSame(['interceptor_from_stream_request'], $event->metadata['foo']);
        $this->assertSame(['interceptor_from_stream_request'], $event->metadata['bar']);

        unset($unary_call);
        unset($stream_call);
        unset($server_call);

        // test intercept by array.
        $intercept_channel3 = Grpc\Interceptor::intercept($this->channel,
            [$channel_matadata_intercepto2, $channel_matadata_interceptor]);
        $client = new InterceptorClient('localhost:'.$this->port, [
            'force_new' => true,
            'credentials' => Grpc\ChannelCredentials::createInsecure(),
        ], $intercept_channel3);

        $req = new SimpleRequest($req_text);
        $unary_call = $client->UnaryCall($req);
        $event = $this->server->requestCall();
        $this->assertSame('/phony_method', $event->method);
        $this->assertSame(['interceptor_from_unary_request'], $event->metadata['foo']);
        $this->assertSame(['interceptor_from_unary_request'], $event->metadata['bar']);

        $stream_call = $client->StreamCall();
        $stream_call->write($req);
        $event = $this->server->requestCall();
        $this->assertSame('/phony_method', $event->method);
        $this->assertSame(['interceptor_from_stream_request'], $event->metadata['foo']);
        $this->assertSame(['interceptor_from_stream_request'], $event->metadata['bar']);

        unset($unary_call);
        unset($stream_call);
        unset($server_call);
    }

    public function testClientChangeRequestInterceptor()
    {
        $req_text = 'client_request';
        $change_request_interceptor = new ChangeRequestInterceptor();
        $intercept_channel = Grpc\Interceptor::intercept($this->channel,
            $change_request_interceptor);
        $client = new InterceptorClient('localhost:'.$this->port, [
            'force_new' => true,
            'credentials' => Grpc\ChannelCredentials::createInsecure(),
        ], $intercept_channel);

        $req = new SimpleRequest($req_text);
        $unary_call = $client->UnaryCall($req);

        $event = $this->server->requestCall();
        $this->assertSame('/phony_method', $event->method);
        $server_call = $event->call;
        $event = $server_call->startBatch([
            Grpc\OP_RECV_MESSAGE => true,
        ]);
        $this->assertSame('intercepted_unary_request', $event->message);
        $event = $server_call->startBatch([
            Grpc\OP_SEND_INITIAL_METADATA => [],
            Grpc\OP_SEND_STATUS_FROM_SERVER => [
                'metadata' => [],
                'code' => Grpc\STATUS_OK,
                'details' => '',
            ],
            Grpc\OP_RECV_CLOSE_ON_SERVER => true,
        ]);

        $stream_call = $client->StreamCall();
        $stream_call->write($req);
        $event = $this->server->requestCall();
        $this->assertSame('/phony_method', $event->method);
        $server_call = $event->call;
        $event = $server_call->startBatch([
            Grpc\OP_RECV_MESSAGE => true,
        ]);
        $this->assertSame('intercepted_stream_request', $event->message);
        $event = $server_call->startBatch([
            Grpc\OP_SEND_INITIAL_METADATA => [],
            Grpc\OP_SEND_STATUS_FROM_SERVER => [
                'metadata' => [],
                'code' => Grpc\STATUS_OK,
                'details' => '',
            ],
            Grpc\OP_RECV_CLOSE_ON_SERVER => true,
        ]);

        unset($unary_call);
        unset($stream_call);
        unset($server_call);
    }

    public function testClientChangeStopCallInterceptor()
    {
        $req_text = 'client_request';
        $channel_request_interceptor = new StopCallInterceptor();
        $intercept_channel = Grpc\Interceptor::intercept($this->channel,
            $channel_request_interceptor);
        $client = new InterceptorClient('localhost:'.$this->port, [
            'force_new' => true,
            'credentials' => Grpc\ChannelCredentials::createInsecure(),
        ], $intercept_channel);

        $req = new SimpleRequest($req_text);
        $unary_call = $client->UnaryCall($req);
        $this->assertNull($unary_call);


        $stream_call = $client->StreamCall();
        $this->assertNull($stream_call);

        unset($unary_call);
        unset($stream_call);
        unset($server_call);
    }

    public function testGetInterceptorChannelConnectivityState()
    {
        $channel = new Grpc\Channel(
            'localhost:0',
            [
                'force_new' => true,
                'credentials' => Grpc\ChannelCredentials::createInsecure()
            ]
        );
        $interceptor_channel = Grpc\Interceptor::intercept($channel, new Grpc\Interceptor());
        $state = $interceptor_channel->getConnectivityState();
        $this->assertEquals(0, $state);
        $channel->close();
    }

    public function testInterceptorChannelWatchConnectivityState()
    {
        $channel = new Grpc\Channel(
            'localhost:0',
            [
                'force_new' => true,
                'credentials' => Grpc\ChannelCredentials::createInsecure()
            ]
        );
        $interceptor_channel = Grpc\Interceptor::intercept($channel, new Grpc\Interceptor());
        $now = Grpc\Timeval::now();
        $deadline = $now->add(new Grpc\Timeval(100*1000));
        $state = $interceptor_channel->watchConnectivityState(1, $deadline);
        $this->assertTrue($state);
        unset($time);
        unset($deadline);
        $channel->close();
    }

    public function testInterceptorChannelClose()
    {
        $channel = new Grpc\Channel(
            'localhost:0',
            [
                'force_new' => true,
                'credentials' => Grpc\ChannelCredentials::createInsecure()
            ]
        );
        $interceptor_channel = Grpc\Interceptor::intercept($channel, new Grpc\Interceptor());
        $this->assertNotNull($interceptor_channel);
        $channel->close();
    }

    public function testInterceptorChannelGetTarget()
    {
        $channel = new Grpc\Channel(
            'localhost:8888',
            [
                'force_new' => true,
                'credentials' => Grpc\ChannelCredentials::createInsecure()
            ]
        );
        $interceptor_channel = Grpc\Interceptor::intercept($channel, new Grpc\Interceptor());
        $target = $interceptor_channel->getTarget();
        $this->assertTrue(is_string($target));
        $channel->close();
    }
}
