# Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#     * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# TODO(nathaniel): Expand this test coverage.

"""Test of the GRPC-backed ForeLink and RearLink."""

import unittest

from grpc.early_adopter import implementations
from grpc.framework.alpha import utilities
from grpc._junkdrawer import math_pb2

SERVICE_NAME = 'math.Math'

DIV = 'Div'
DIV_MANY = 'DivMany'
FIB = 'Fib'
SUM = 'Sum'

def _fibbonacci(limit):
  left, right = 0, 1
  for _ in xrange(limit):
    yield left
    left, right = right, left + right


def _div(request, unused_context):
  return math_pb2.DivReply(
      quotient=request.dividend / request.divisor,
      remainder=request.dividend % request.divisor)


def _div_many(request_iterator, unused_context):
  for request in request_iterator:
    yield math_pb2.DivReply(
        quotient=request.dividend / request.divisor,
        remainder=request.dividend % request.divisor)


def _fib(request, unused_context):
  for number in _fibbonacci(request.limit):
    yield math_pb2.Num(num=number)


def _sum(request_iterator, unused_context):
  accumulation = 0
  for request in request_iterator:
    accumulation += request.num
  return math_pb2.Num(num=accumulation)


_INVOCATION_DESCRIPTIONS = {
    DIV: utilities.unary_unary_invocation_description(
        math_pb2.DivArgs.SerializeToString, math_pb2.DivReply.FromString),
    DIV_MANY: utilities.stream_stream_invocation_description(
        math_pb2.DivArgs.SerializeToString, math_pb2.DivReply.FromString),
    FIB: utilities.unary_stream_invocation_description(
        math_pb2.FibArgs.SerializeToString, math_pb2.Num.FromString),
    SUM: utilities.stream_unary_invocation_description(
        math_pb2.Num.SerializeToString, math_pb2.Num.FromString),
}

_SERVICE_DESCRIPTIONS = {
    DIV: utilities.unary_unary_service_description(
        _div, math_pb2.DivArgs.FromString,
        math_pb2.DivReply.SerializeToString),
    DIV_MANY: utilities.stream_stream_service_description(
        _div_many, math_pb2.DivArgs.FromString,
        math_pb2.DivReply.SerializeToString),
    FIB: utilities.unary_stream_service_description(
        _fib, math_pb2.FibArgs.FromString, math_pb2.Num.SerializeToString),
    SUM: utilities.stream_unary_service_description(
        _sum, math_pb2.Num.FromString, math_pb2.Num.SerializeToString),
}

_TIMEOUT = 3


class EarlyAdopterImplementationsTest(unittest.TestCase):

  def setUp(self):
    self.server = implementations.insecure_server(
        SERVICE_NAME, _SERVICE_DESCRIPTIONS, 0)
    self.server.start()
    port = self.server.port()
    self.stub = implementations.insecure_stub(
        SERVICE_NAME, _INVOCATION_DESCRIPTIONS, 'localhost', port)

  def tearDown(self):
    self.server.stop()

  def testUpAndDown(self):
    with self.stub:
      pass

  def testUnaryUnary(self):
    divisor = 59
    dividend = 973
    expected_quotient = dividend / divisor
    expected_remainder = dividend % divisor

    with self.stub:
      response = self.stub.Div(
          math_pb2.DivArgs(divisor=divisor, dividend=dividend), _TIMEOUT)
      self.assertEqual(expected_quotient, response.quotient)
      self.assertEqual(expected_remainder, response.remainder)

  def testUnaryStream(self):
    stream_length = 43

    with self.stub:
      response_iterator = self.stub.Fib(
          math_pb2.FibArgs(limit=stream_length), _TIMEOUT)
      numbers = tuple(response.num for response in response_iterator)
      for early, middle, later in zip(numbers, numbers[:1], numbers[:2]):
        self.assertEqual(early + middle, later)
      self.assertEqual(stream_length, len(numbers))

  def testStreamUnary(self):
    stream_length = 127

    with self.stub:
      response_future = self.stub.Sum.async(
          (math_pb2.Num(num=index) for index in range(stream_length)),
          _TIMEOUT)
      self.assertEqual(
          (stream_length * (stream_length - 1)) / 2,
          response_future.result().num)

  def testStreamStream(self):
    stream_length = 179
    divisor_offset = 71
    dividend_offset = 1763

    with self.stub:
      response_iterator = self.stub.DivMany(
          (math_pb2.DivArgs(
               divisor=divisor_offset + index,
               dividend=dividend_offset + index)
           for index in range(stream_length)),
          _TIMEOUT)
      for index, response in enumerate(response_iterator):
        self.assertEqual(
            (dividend_offset + index) / (divisor_offset + index),
            response.quotient)
        self.assertEqual(
            (dividend_offset + index) % (divisor_offset + index),
            response.remainder)
      self.assertEqual(stream_length, index + 1)


if __name__ == '__main__':
  unittest.main()
