from __future__ import print_function

import jsons
import logging
import os

# Should match size defined in platform/platform_tests/downloader_tests/downloader_test.cpp
BIG_FILE_SIZE = 47684


class Payload:
    def __init__(self, message, response_code=200, headers={}):
        self.__response_code = response_code
        self.__message = message if type(message) is bytes else message.encode('utf8')
        self.__headers = headers


    def response_code(self):
        """
        Response code to send to the client.
        """
        return self.__response_code


    def message(self):
        """
        The message to send to the client.
        """
        return self.__message


    def length(self):
        """
        The length of the response.
        """
        return len(self.message())


    def headers(self):
        """
        The headers to be sent to the client. Please, note, that these do not include
        the Content-Length header, which you need to send separately.
        """
        return self.__headers


    def __repr__(self):
        return "{}: {}: {}".format(self.response_code(), self.length(), self.message())


class ResponseProviderMixin:
    """
A mixin (basically, an interface) that the web-server that we might use relies on.

In this implementation, the job of the web-server is just to get the request
(the url and the headers), and to send the response as it knows how. It isn't
its job to decide how to respond to what request. It is the job of the
ResponseProvider.

In your web-server you should initialize the ResponseProvider, and ask it for

response_for_url_and_headers(url, headers)

Which will return a Payload object that the server must send as response.

The server might be notified when a particular request has been received:

got_pinged(self) - someone sent a ping request. The Response provider will
respond with "pong" and call this method of the server. You might want to
increment the count of active users, for ping is the request that new instances
of servers send to check if other servers are currently serving.

kill(self) - someone sent the kill request, which means that that someone
no longer needs this server to serve. You might want to decrement the count of
active users and/or stop the server.
"""

    def dispatch_response(self, payload):
        """
    Define this mehtod to dispatch the response received from the ResponseProvider
    """
        raise NotImplementedError()


    def got_pinged(self):
        """
    A ping request has been received. In most scenarios it means that the number of
    users of this server has increased by 1.
    """
        raise NotImplementedError()


    def kill(self):
        """
    Someone no longer needs this server. Decrement the number of users and stop
    the server if the number fell to 0.
    """
        raise NotImplementedError()


class ResponseProvider:
    def __init__(self, delegate):
        self.headers = list()
        self.delegate = delegate
        self.byterange = None
        self.is_chunked = False
        self.response_code = 200


    def pong(self):
        self.delegate.got_pinged()
        return Payload("pong")


    def my_id(self):
        return Payload(str(os.getpid()))


    def strip_query(self, url):
        query_start = url.find("?")
        if (query_start > 0):
            return url[:query_start]
        return url


    def response_for_url_and_headers(self, url, headers):
        self.headers = headers
        self.chunk_requested()
        url = self.strip_query(url)
        try:
            return {
                "/unit_tests/1.txt": self.test1,
                "/unit_tests/notexisting_unittest": self.test_404,
                "/unit_tests/permanent": self.test_301,
                "/unit_tests/47kb.file": self.test_47_kb,
                # Following two URIs are used to test downloading failures on different platforms.
                "/unit_tests/mac/1234/Uruguay.mwm": self.test_404,
                "/unit_tests/linux/1234/Uruguay.mwm": self.test_404,
                "/ping": self.pong,
                "/kill": self.kill,
                "/id": self.my_id,
                "/partners/time": self.partners_time,
                "/partners/price": self.partners_price,
                "/booking/hotelAvailability": self.partners_hotel_availability,
                "/booking/deals": self.partners_hotels_with_deals,
                "/booking/blockAvailability": self.partners_block_availability,
                "/partners/taxi_info": self.partners_yandex_taxi_info,
                "/partners/get-offers-in-bbox/": self.partners_rent_nearby,
                "/partners/CalculateByCoords": self.partners_calculate_by_coords,
                "/gallery/v2/search/": self.promo_gallery_city,
                "/single/empty/gallery/v2/search/": self.promo_gallery_city_single_empty,
                "/single/gallery/v2/search/": self.promo_gallery_city_single,
                "/partners/oauth/token": self.freenow_auth_token,
                "/partners/service-types": self.freenow_service_types,
                "/gallery/v2/map": self.guides_on_map_gallery,
                "/partners/get_supported_tariffs": self.citymobil_supported_tariffs,
                "/partners/calculate_price": self.citymobil_calculate_price,
            }.get(url, self.test_404)()
        except Exception as e:
            logging.error("test_server: Can't build server response", exc_info=e)
            return self.test_404()


    def chunk_requested(self):
        if "range" in self.headers:
            self.is_chunked = True
            self.response_code = 206
            meaningful_string = self.headers["range"][6:]
            first, last = meaningful_string.split("-")
            self.byterange = (int(first), int(last))


    def trim_message(self, message):
        if not self.is_chunked:
            return message
        return message[self.byterange[0]: self.byterange[1] + 1]


    def test1(self):
        init_message = "Test1"
        message = self.trim_message(init_message)
        size = len(init_message)
        self.check_byterange(size)
        headers = self.chunked_response_header(size)

        return Payload(message, self.response_code, headers)


    def test_404(self):
        return Payload("", response_code=404)


    def test_301(self):
        return Payload("", 301, {"Location" : "google.com"})


    def check_byterange(self, size):
        if self.byterange is None:
            self.byterange = (0, size)

    def chunked_response_header(self, size):
        return {
            "Content-Range" : "bytes {start}-{end}/{out_of}".format(start=self.byterange[0],
            end=self.byterange[1], out_of=size)
        }


    def test_47_kb(self):
        self.check_byterange(BIG_FILE_SIZE)
        headers = self.chunked_response_header(BIG_FILE_SIZE)
        message = self.trim_message(self.message_for_47kb_file())

        return Payload(message, self.response_code, headers)


    def message_for_47kb_file(self):
        message = []
        for i in range(0, BIG_FILE_SIZE + 1):
            message.append(i // 256)
            message.append(i % 256)

        return bytes(message)


    # Partners_api_tests
    def partners_time(self):
        return Payload(jsons.PARTNERS_TIME)


    def partners_price(self):
        return Payload(jsons.PARTNERS_PRICE)

    def partners_hotel_availability(self):
        return Payload(jsons.HOTEL_AVAILABILITY)

    def partners_hotels_with_deals(self):
        return Payload(jsons.HOTELS_WITH_DEALS)

    def partners_block_availability(self):
        return Payload(jsons.BLOCK_AVAILABILITY)

    def partners_yandex_taxi_info(self):
        return Payload(jsons.PARTNERS_TAXI_INFO)

    def partners_rent_nearby(self):
        return Payload(jsons.PARTNERS_RENT_NEARBY)

    def partners_calculate_by_coords(self):
        return Payload(jsons.PARTNERS_CALCULATE_BY_COORDS)

    def promo_gallery_city(self):
        return Payload(jsons.PROMO_GALLERY_CITY)

    def promo_gallery_city_single_empty(self):
        return Payload(jsons.PROMO_GALLERY_CITY_SINGLE_EMPTY)

    def promo_gallery_city_single(self):
        return Payload(jsons.PROMO_GALLERY_CITY_SINGLE)

    def freenow_auth_token(self):
        return Payload(jsons.FREENOW_AUTH_TOKEN)

    def freenow_service_types(self):
        return Payload(jsons.FREENOW_SERVICE_TYPES)

    def guides_on_map_gallery(self):
        return Payload(jsons.GUIDES_ON_MAP_GALLERY)

    def citymobil_supported_tariffs(self):
        return Payload(jsons.CITYMOBIL_SUPPORTED_TARIFFS)

    def citymobil_calculate_price(self):
        return Payload(jsons.CITYMOBIL_CALCULATE_PRICE)

    def kill(self):
        logging.debug("Kill called in ResponseProvider")
        self.delegate.kill()
        return Payload("Bye...")
