# This file is part of the MapProxy project.
# Copyright (C) 2010 Omniscale <http://omniscale.de>
#
# 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.

"""
Retrieve tiles from different tile servers (TMS/TileCache/etc.).
"""

import sys
from typing import Optional

from mapproxy.layer.cache_map_layer import CacheMapLayer
from mapproxy.layer.map_layer import MapLayer
from mapproxy.image import BaseImageResult
from mapproxy.image.opts import ImageOptions
from mapproxy.source import SourceError
from mapproxy.client.http import HTTPClientError
from mapproxy.source import InvalidSourceQuery
from mapproxy.layer import BlankImageError
from mapproxy.extent import map_extent_from_grid
from mapproxy.util.py import reraise_exception
from mapproxy.query import MapQuery
from mapproxy.util.coverage import Coverage

import logging

log = logging.getLogger('mapproxy.source.tile')
log_config = logging.getLogger('mapproxy.config')


class TiledSource(MapLayer):
    def __init__(self, grid, client, coverage: Optional[Coverage] = None, image_opts=None, error_handler=None,
                 res_range=None):
        super().__init__(image_opts=image_opts)
        self.grid = grid
        self.client = client
        self.image_opts = image_opts or ImageOptions()
        self.coverage = coverage
        self.extent = coverage.extent if coverage else map_extent_from_grid(grid)
        self.res_range = res_range
        self.error_handler = error_handler

    def get_map(self, query: MapQuery) -> BaseImageResult:
        if self.grid.tile_size != query.size:
            ex = InvalidSourceQuery(
                'tile size of cache and tile source do not match: %s != %s'
                % (self.grid.tile_size, query.size)
            )
            log_config.error(ex)
            raise ex

        if self.grid.srs != query.srs:
            ex = InvalidSourceQuery(
                'SRS of cache and tile source do not match: %r != %r'
                % (self.grid.srs, query.srs)
            )
            log_config.error(ex)
            raise ex

        if self.res_range and not self.res_range.contains(query.bbox, query.size,
                                                          query.srs):
            raise BlankImageError()
        if self.coverage and not self.coverage.intersects(query.bbox, query.srs):
            raise BlankImageError()

        _bbox, grid, tiles = self.grid.get_affected_tiles(query.bbox, query.size)

        if grid != (1, 1):
            raise InvalidSourceQuery('BBOX does not align to tile')

        tile_coord = next(tiles)

        try:
            return self.client.get_tile(tile_coord, format=query.format)
        except HTTPClientError as e:
            if self.error_handler:
                resp = self.error_handler.handle(e.response_code, query)
                if resp:
                    return resp
            log.warning('could not retrieve tile (client: %s): %s', self.client, e, exc_info=True)
            raise reraise_exception(SourceError(e.args[0]), sys.exc_info())


class CacheSource(CacheMapLayer):
    def __init__(self, tile_manager, extent=None, image_opts=None,
                 max_tile_limit=None, tiled_only=False):
        super().__init__(tile_manager, extent=extent, image_opts=image_opts, max_tile_limit=max_tile_limit)
        self.supports_meta_tiles = not tiled_only
        self.tiled_only = tiled_only

    def get_map(self, query: MapQuery) -> BaseImageResult:
        if self.tiled_only:
            query.tiled_only = True
        return super().get_map(query)
