#include <mbgl/map/map.hpp>
#include <mbgl/map/camera.hpp>
#include <mbgl/map/view.hpp>
#include <mbgl/map/backend.hpp>
#include <mbgl/map/transform.hpp>
#include <mbgl/map/transform_state.hpp>
#include <mbgl/annotation/annotation_manager.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/style/source.hpp>
#include <mbgl/style/layer.hpp>
#include <mbgl/style/observer.hpp>
#include <mbgl/style/transition_options.hpp>
#include <mbgl/style/update_parameters.hpp>
#include <mbgl/style/query_parameters.hpp>
#include <mbgl/renderer/painter.hpp>
#include <mbgl/storage/file_source.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/util/projection.hpp>
#include <mbgl/util/math.hpp>
#include <mbgl/util/exception.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/mapbox.hpp>
#include <mbgl/util/tile_coordinate.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/math/log2.hpp>

namespace mbgl {

using namespace style;

enum class RenderState : uint8_t {
    Never,
    Partial,
    Fully,
};

struct StillImageRequest {
    StillImageRequest(View& view_, Map::StillImageCallback&& callback_)
        : view(view_), callback(std::move(callback_)) {
    }

    View& view;
    Map::StillImageCallback callback;
};

class Map::Impl : public style::Observer {
public:
    Impl(Map&,
         Backend&,
         float pixelRatio,
         FileSource&,
         Scheduler&,
         MapMode,
         GLContextMode,
         ConstrainMode,
         ViewportMode);

    void onSourceAttributionChanged(style::Source&, const std::string&) override;
    void onUpdate(Update) override;
    void onStyleLoaded() override;
    void onStyleError() override;
    void onResourceError(std::exception_ptr) override;

    void update();
    void render(View&);

    void loadStyleJSON(const std::string&);

    Map& map;
    Backend& backend;
    FileSource& fileSource;
    Scheduler& scheduler;

    RenderState renderState = RenderState::Never;
    Transform transform;

    const MapMode mode;
    const GLContextMode contextMode;
    const float pixelRatio;

    MapDebugOptions debugOptions { MapDebugOptions::NoDebug };

    Update updateFlags = Update::Nothing;
    util::AsyncTask asyncUpdate;

    std::unique_ptr<AnnotationManager> annotationManager;
    std::unique_ptr<Painter> painter;
    std::unique_ptr<Style> style;

    std::string styleURL;
    std::string styleJSON;
    bool styleMutated = false;
    bool cameraMutated = false;

    std::unique_ptr<AsyncRequest> styleRequest;

    std::unique_ptr<StillImageRequest> stillImageRequest;
    size_t sourceCacheSize;
    TimePoint timePoint;
    bool loading = false;
};

Map::Map(Backend& backend,
         const Size size,
         const float pixelRatio,
         FileSource& fileSource,
         Scheduler& scheduler,
         MapMode mapMode,
         GLContextMode contextMode,
         ConstrainMode constrainMode,
         ViewportMode viewportMode)
    : impl(std::make_unique<Impl>(*this,
                                  backend,
                                  pixelRatio,
                                  fileSource,
                                  scheduler,
                                  mapMode,
                                  contextMode,
                                  constrainMode,
                                  viewportMode)) {
    impl->transform.resize(size);
}

Map::Impl::Impl(Map& map_,
                Backend& backend_,
                float pixelRatio_,
                FileSource& fileSource_,
                Scheduler& scheduler_,
                MapMode mode_,
                GLContextMode contextMode_,
                ConstrainMode constrainMode_,
                ViewportMode viewportMode_)
    : map(map_),
      backend(backend_),
      fileSource(fileSource_),
      scheduler(scheduler_),
      transform([this](MapChange change) { backend.notifyMapChange(change); },
                constrainMode_,
                viewportMode_),
      mode(mode_),
      contextMode(contextMode_),
      pixelRatio(pixelRatio_),
      asyncUpdate([this] { update(); }),
      annotationManager(std::make_unique<AnnotationManager>(pixelRatio)) {
}

Map::~Map() {
    impl->backend.activate();

    impl->styleRequest = nullptr;

    // Explicit resets currently necessary because these abandon resources that need to be
    // cleaned up by context.reset();
    impl->style.reset();
    impl->annotationManager.reset();
    impl->painter.reset();

    impl->backend.deactivate();
}

void Map::renderStill(View& view, StillImageCallback callback) {
    if (!callback) {
        Log::Error(Event::General, "StillImageCallback not set");
        return;
    }

    if (impl->mode != MapMode::Still) {
        callback(std::make_exception_ptr(util::MisuseException("Map is not in still image render mode")));
        return;
    }

    if (impl->stillImageRequest) {
        callback(std::make_exception_ptr(util::MisuseException("Map is currently rendering an image")));
        return;
    }

    if (!impl->style) {
        callback(std::make_exception_ptr(util::MisuseException("Map doesn't have a style")));
        return;
    }

    if (impl->style->getLastError()) {
        callback(impl->style->getLastError());
        return;
    }

    impl->stillImageRequest = std::make_unique<StillImageRequest>(view, std::move(callback));
    impl->updateFlags |= Update::RenderStill;
    impl->asyncUpdate.send();
}

void Map::render(View& view) {
    if (!impl->style) {
        return;
    }

    if (impl->renderState == RenderState::Never) {
        impl->backend.notifyMapChange(MapChangeWillStartRenderingMap);
    }

    impl->backend.notifyMapChange(MapChangeWillStartRenderingFrame);

    const Update flags = impl->transform.updateTransitions(Clock::now());

    impl->render(view);

    impl->backend.notifyMapChange(isFullyLoaded() ?
        MapChangeDidFinishRenderingFrameFullyRendered :
        MapChangeDidFinishRenderingFrame);

    if (!isFullyLoaded()) {
        impl->renderState = RenderState::Partial;
    } else if (impl->renderState != RenderState::Fully) {
        impl->renderState = RenderState::Fully;
        impl->backend.notifyMapChange(MapChangeDidFinishRenderingMapFullyRendered);
        if (impl->loading) {
            impl->loading = false;
            impl->backend.notifyMapChange(MapChangeDidFinishLoadingMap);
        }
    }

    // Triggers an asynchronous update, that eventually triggers a view
    // invalidation, causing renderSync to be called again if in transition.
    if (flags != Update::Nothing) {
        impl->onUpdate(flags);
    }
}

void Map::triggerRepaint() {
    impl->backend.invalidate();
}

void Map::Impl::update() {
    if (!style) {
        updateFlags = Update::Nothing;
    }

    if (updateFlags == Update::Nothing || (mode == MapMode::Still && !stillImageRequest)) {
        return;
    }

    // This time point is used to:
    // - Calculate style property transitions;
    // - Hint style sources to notify when all its tiles are loaded;
    timePoint = Clock::now();

    if (style->loaded && updateFlags & Update::AnnotationStyle) {
        annotationManager->updateStyle(*style);
        updateFlags |= Update::Classes;
    }

    if (updateFlags & Update::AnnotationData) {
        annotationManager->updateData();
    }

    if (updateFlags & Update::Layout) {
        style->relayout();
    }

    if (updateFlags & Update::Classes) {
        style->cascade(timePoint, mode);
    }

    if (updateFlags & Update::Classes || updateFlags & Update::RecalculateStyle) {
        style->recalculate(transform.getZoom(), timePoint, mode);
    }

    style::UpdateParameters parameters(pixelRatio,
                                       debugOptions,
                                       transform.getState(),
                                       scheduler,
                                       fileSource,
                                       mode,
                                       *annotationManager,
                                       *style);

    style->updateTiles(parameters);

    if (mode == MapMode::Continuous) {
        backend.invalidate();
    } else if (stillImageRequest && style->isLoaded()) {
        // TODO: determine whether we need activate/deactivate
        backend.activate();
        render(stillImageRequest->view);
        backend.deactivate();
    }

    updateFlags = Update::Nothing;
}

void Map::Impl::render(View& view) {
    if (!painter) {
        painter = std::make_unique<Painter>(backend.getContext(), transform.getState());
    }

    FrameData frameData { timePoint,
                          pixelRatio,
                          mode,
                          contextMode,
                          debugOptions };

    painter->render(*style,
                    frameData,
                    view,
                    annotationManager->getSpriteAtlas());

    if (mode == MapMode::Still) {
        auto request = std::move(stillImageRequest);
        request->callback(nullptr);
    }

    painter->cleanup();

    if (style->hasTransitions()) {
        updateFlags |= Update::RecalculateStyle;
        asyncUpdate.send();
    } else if (painter->needsAnimation()) {
        updateFlags |= Update::Repaint;
        asyncUpdate.send();
    }
}

#pragma mark - Style

void Map::setStyleURL(const std::string& url) {
    if (impl->styleURL == url) {
        return;
    }

    impl->loading = true;

    impl->backend.notifyMapChange(MapChangeWillStartLoadingMap);

    impl->styleRequest = nullptr;
    impl->styleURL = url;
    impl->styleJSON.clear();
    impl->styleMutated = false;

    impl->style = std::make_unique<Style>(impl->fileSource, impl->pixelRatio);

    impl->styleRequest = impl->fileSource.request(Resource::style(impl->styleURL), [this](Response res) {
        // Once we get a fresh style, or the style is mutated, stop revalidating.
        if (res.isFresh() || impl->styleMutated) {
            impl->styleRequest.reset();
        }

        // Don't allow a loaded, mutated style to be overwritten with a new version.
        if (impl->styleMutated && impl->style->loaded) {
            return;
        }

        if (res.error) {
            if (res.error->reason == Response::Error::Reason::NotFound &&
                util::mapbox::isMapboxURL(impl->styleURL)) {
                Log::Error(Event::Setup, "style %s could not be found or is an incompatible legacy map or style", impl->styleURL.c_str());
            } else {
                Log::Error(Event::Setup, "loading style failed: %s", res.error->message.c_str());
            }
            impl->onStyleError();
            impl->onResourceError(std::make_exception_ptr(std::runtime_error(res.error->message)));
        } else if (res.notModified || res.noContent) {
            return;
        } else {
            impl->loadStyleJSON(*res.data);
        }
    });
}

void Map::setStyleJSON(const std::string& json) {
    if (impl->styleJSON == json) {
        return;
    }

    impl->loading = true;

    impl->backend.notifyMapChange(MapChangeWillStartLoadingMap);

    impl->styleURL.clear();
    impl->styleJSON.clear();
    impl->styleMutated = false;

    impl->style = std::make_unique<Style>(impl->fileSource, impl->pixelRatio);

    impl->loadStyleJSON(json);
}

void Map::Impl::loadStyleJSON(const std::string& json) {
    style->setObserver(this);
    style->setJSON(json);
    styleJSON = json;

    // force style cascade, causing all pending transitions to complete.
    style->cascade(Clock::now(), mode);

    if (!cameraMutated) {
        // Zoom first because it may constrain subsequent operations.
        map.setZoom(map.getDefaultZoom());
        map.setLatLng(map.getDefaultLatLng());
        map.setBearing(map.getDefaultBearing());
        map.setPitch(map.getDefaultPitch());
    }

    updateFlags |= Update::Classes | Update::RecalculateStyle | Update::AnnotationStyle;
    asyncUpdate.send();
}

std::string Map::getStyleURL() const {
    return impl->styleURL;
}

std::string Map::getStyleJSON() const {
    return impl->styleJSON;
}

#pragma mark - Transitions

void Map::cancelTransitions() {
    impl->transform.cancelTransitions();
    impl->onUpdate(Update::Repaint);
}

void Map::setGestureInProgress(bool inProgress) {
    impl->transform.setGestureInProgress(inProgress);
    impl->onUpdate(Update::Repaint);
}

bool Map::isGestureInProgress() const {
    return impl->transform.isGestureInProgress();
}

bool Map::isRotating() const {
    return impl->transform.isRotating();
}

bool Map::isScaling() const {
    return impl->transform.isScaling();
}

bool Map::isPanning() const {
    return impl->transform.isPanning();
}

#pragma mark -

CameraOptions Map::getCameraOptions(optional<EdgeInsets> padding) const {
    return impl->transform.getCameraOptions(padding);
}

void Map::jumpTo(const CameraOptions& camera) {
    impl->cameraMutated = true;
    impl->transform.jumpTo(camera);
    impl->onUpdate(camera.zoom ? Update::RecalculateStyle : Update::Repaint);
}

void Map::easeTo(const CameraOptions& camera, const AnimationOptions& animation) {
    impl->cameraMutated = true;
    impl->transform.easeTo(camera, animation);
    impl->onUpdate(camera.zoom ? Update::RecalculateStyle : Update::Repaint);
}

void Map::flyTo(const CameraOptions& camera, const AnimationOptions& animation) {
    impl->cameraMutated = true;
    impl->transform.flyTo(camera, animation);
    impl->onUpdate(Update::RecalculateStyle);
}

#pragma mark - Position

void Map::moveBy(const ScreenCoordinate& point, const Duration& duration) {
    impl->cameraMutated = true;
    impl->transform.moveBy(point, duration);
    impl->onUpdate(Update::Repaint);
}

void Map::setLatLng(const LatLng& latLng, const Duration& duration) {
    impl->cameraMutated = true;
    setLatLng(latLng, optional<ScreenCoordinate> {}, duration);
}

void Map::setLatLng(const LatLng& latLng, optional<EdgeInsets> padding, const Duration& duration) {
    impl->cameraMutated = true;
    impl->transform.setLatLng(latLng, padding, duration);
    impl->onUpdate(Update::Repaint);
}

void Map::setLatLng(const LatLng& latLng, optional<ScreenCoordinate> anchor, const Duration& duration) {
    impl->cameraMutated = true;
    impl->transform.setLatLng(latLng, anchor, duration);
    impl->onUpdate(Update::Repaint);
}

LatLng Map::getLatLng(optional<EdgeInsets> padding) const {
    return impl->transform.getLatLng(padding);
}

void Map::resetPosition(optional<EdgeInsets> padding) {
    impl->cameraMutated = true;
    CameraOptions camera;
    camera.angle = 0;
    camera.pitch = 0;
    camera.center = LatLng(0, 0);
    camera.padding = padding;
    camera.zoom = 0;
    impl->transform.jumpTo(camera);
    impl->onUpdate(Update::RecalculateStyle);
}


#pragma mark - Scale

void Map::scaleBy(double ds, optional<ScreenCoordinate> anchor, const Duration& duration) {
    impl->cameraMutated = true;
    impl->transform.scaleBy(ds, anchor, duration);
    impl->onUpdate(Update::RecalculateStyle);
}

void Map::setScale(double scale, optional<ScreenCoordinate> anchor, const Duration& duration) {
    impl->cameraMutated = true;
    impl->transform.setScale(scale, anchor, duration);
    impl->onUpdate(Update::RecalculateStyle);
}

double Map::getScale() const {
    return impl->transform.getScale();
}

void Map::setZoom(double zoom, const Duration& duration) {
    impl->cameraMutated = true;
    setZoom(zoom, {}, duration);
}

void Map::setZoom(double zoom, optional<EdgeInsets> padding, const Duration& duration) {
    impl->cameraMutated = true;
    impl->transform.setZoom(zoom, padding, duration);
    impl->onUpdate(Update::RecalculateStyle);
}

double Map::getZoom() const {
    return impl->transform.getZoom();
}

void Map::setLatLngZoom(const LatLng& latLng, double zoom, const Duration& duration) {
    impl->cameraMutated = true;
    setLatLngZoom(latLng, zoom, {}, duration);
}

void Map::setLatLngZoom(const LatLng& latLng, double zoom, optional<EdgeInsets> padding, const Duration& duration) {
    impl->cameraMutated = true;
    impl->transform.setLatLngZoom(latLng, zoom, padding, duration);
    impl->onUpdate(Update::RecalculateStyle);
}

CameraOptions Map::cameraForLatLngBounds(const LatLngBounds& bounds, optional<EdgeInsets> padding) const {
    return cameraForLatLngs({
        bounds.northwest(),
        bounds.southwest(),
        bounds.southeast(),
        bounds.northeast(),
    }, padding);
}

CameraOptions Map::cameraForLatLngs(const std::vector<LatLng>& latLngs, optional<EdgeInsets> padding) const {
    CameraOptions options;
    if (latLngs.empty()) {
        return options;
    }

    // Calculate the bounds of the possibly rotated shape with respect to the viewport.
    ScreenCoordinate nePixel = {-INFINITY, -INFINITY};
    ScreenCoordinate swPixel = {INFINITY, INFINITY};
    double viewportHeight = getSize().height;
    for (LatLng latLng : latLngs) {
        ScreenCoordinate pixel = pixelForLatLng(latLng);
        swPixel.x = std::min(swPixel.x, pixel.x);
        nePixel.x = std::max(nePixel.x, pixel.x);
        swPixel.y = std::min(swPixel.y, viewportHeight - pixel.y);
        nePixel.y = std::max(nePixel.y, viewportHeight - pixel.y);
    }
    double width = nePixel.x - swPixel.x;
    double height = nePixel.y - swPixel.y;

    // Calculate the zoom level.
    double minScale = INFINITY;
    if (width > 0 || height > 0) {
        double scaleX = double(getSize().width) / width;
        double scaleY = double(getSize().height) / height;
        if (padding && *padding) {
            scaleX -= (padding->left + padding->right) / width;
            scaleY -= (padding->top + padding->bottom) / height;
        }
        minScale = util::min(scaleX, scaleY);
    }
    double zoom = util::log2(getScale() * minScale);
    zoom = util::clamp(zoom, getMinZoom(), getMaxZoom());

    // Calculate the center point of a virtual bounds that is extended in all directions by padding.
    ScreenCoordinate centerPixel = nePixel + swPixel;
    if (padding && *padding) {
        ScreenCoordinate paddedNEPixel = {
            padding->right / minScale,
            padding->top / minScale,
        };
        ScreenCoordinate paddedSWPixel = {
            padding->left / minScale,
            padding->bottom / minScale,
        };
        centerPixel = centerPixel + paddedNEPixel - paddedSWPixel;
    }
    centerPixel /= 2.0;

    // CameraOptions origin is at the top-left corner.
    centerPixel.y = viewportHeight - centerPixel.y;

    options.center = latLngForPixel(centerPixel);
    options.zoom = zoom;
    return options;
}

void Map::resetZoom() {
    impl->cameraMutated = true;
    setZoom(0);
}

void Map::setMinZoom(const double minZoom) {
    impl->transform.setMinZoom(minZoom);
    if (getZoom() < minZoom) {
        setZoom(minZoom);
    }
}

double Map::getMinZoom() const {
    return impl->transform.getState().getMinZoom();
}

void Map::setMaxZoom(const double maxZoom) {
    impl->transform.setMaxZoom(maxZoom);
    if (getZoom() > maxZoom) {
        setZoom(maxZoom);
    }
}

double Map::getMaxZoom() const {
    return impl->transform.getState().getMaxZoom();
}

#pragma mark - Size

void Map::setSize(const Size size) {
    impl->transform.resize(size);
    impl->onUpdate(Update::Repaint);
}

Size Map::getSize() const {
    return impl->transform.getState().getSize();
}

#pragma mark - Rotation

void Map::rotateBy(const ScreenCoordinate& first, const ScreenCoordinate& second, const Duration& duration) {
    impl->cameraMutated = true;
    impl->transform.rotateBy(first, second, duration);
    impl->onUpdate(Update::Repaint);
}

void Map::setBearing(double degrees, const Duration& duration) {
    impl->cameraMutated = true;
    setBearing(degrees, EdgeInsets(), duration);
}

void Map::setBearing(double degrees, optional<ScreenCoordinate> anchor, const Duration& duration) {
    impl->cameraMutated = true;
    impl->transform.setAngle(-degrees * util::DEG2RAD, anchor, duration);
    impl->onUpdate(Update::Repaint);
}

void Map::setBearing(double degrees, optional<EdgeInsets> padding, const Duration& duration) {
    impl->cameraMutated = true;
    impl->transform.setAngle(-degrees * util::DEG2RAD, padding, duration);
    impl->onUpdate(Update::Repaint);
}

double Map::getBearing() const {
    return -impl->transform.getAngle() * util::RAD2DEG;
}

void Map::resetNorth(const Duration& duration) {
    impl->cameraMutated = true;
    impl->transform.setAngle(0, duration);
    impl->onUpdate(Update::Repaint);
}

#pragma mark - Pitch

void Map::setPitch(double pitch, const Duration& duration) {
    impl->cameraMutated = true;
    setPitch(pitch, {}, duration);
}

void Map::setPitch(double pitch, optional<ScreenCoordinate> anchor, const Duration& duration) {
    impl->cameraMutated = true;
    impl->transform.setPitch(pitch * util::DEG2RAD, anchor, duration);
    impl->onUpdate(Update::Repaint);
}

double Map::getPitch() const {
    return impl->transform.getPitch() * util::RAD2DEG;
}

#pragma mark - North Orientation

void Map::setNorthOrientation(NorthOrientation orientation) {
    impl->transform.setNorthOrientation(orientation);
    impl->onUpdate(Update::Repaint);
}

NorthOrientation Map::getNorthOrientation() const {
    return impl->transform.getNorthOrientation();
}

#pragma mark - Constrain mode

void Map::setConstrainMode(mbgl::ConstrainMode mode) {
    impl->transform.setConstrainMode(mode);
    impl->onUpdate(Update::Repaint);
}

ConstrainMode Map::getConstrainMode() const {
    return impl->transform.getConstrainMode();
}

#pragma mark - Viewport mode

void Map::setViewportMode(mbgl::ViewportMode mode) {
    impl->transform.setViewportMode(mode);
    impl->onUpdate(Update::Repaint);
}

ViewportMode Map::getViewportMode() const {
    return impl->transform.getViewportMode();
}

#pragma mark - Projection

double Map::getMetersPerPixelAtLatitude(double lat, double zoom) const {
    return Projection::getMetersPerPixelAtLatitude(lat, zoom);
}

ProjectedMeters Map::projectedMetersForLatLng(const LatLng& latLng) const {
    return Projection::projectedMetersForLatLng(latLng);
}

LatLng Map::latLngForProjectedMeters(const ProjectedMeters& projectedMeters) const {
    return Projection::latLngForProjectedMeters(projectedMeters);
}

ScreenCoordinate Map::pixelForLatLng(const LatLng& latLng) const {
    return impl->transform.latLngToScreenCoordinate(latLng);
}

LatLng Map::latLngForPixel(const ScreenCoordinate& pixel) const {
    return impl->transform.screenCoordinateToLatLng(pixel);
}

#pragma mark - Annotations

void Map::addAnnotationIcon(const std::string& name, std::shared_ptr<const SpriteImage> sprite) {
    impl->annotationManager->addIcon(name, sprite);
}

void Map::removeAnnotationIcon(const std::string& name) {
    impl->annotationManager->removeIcon(name);
}

double Map::getTopOffsetPixelsForAnnotationIcon(const std::string& name) {
    return impl->annotationManager->getTopOffsetPixelsForIcon(name);
}

AnnotationID Map::addAnnotation(const Annotation& annotation) {
    auto result = impl->annotationManager->addAnnotation(annotation, getMaxZoom());
    impl->onUpdate(Update::AnnotationStyle | Update::AnnotationData);
    return result;
}

void Map::updateAnnotation(AnnotationID id, const Annotation& annotation) {
    impl->onUpdate(impl->annotationManager->updateAnnotation(id, annotation, getMaxZoom()));
}

void Map::removeAnnotation(AnnotationID annotation) {
    impl->annotationManager->removeAnnotation(annotation);
    impl->onUpdate(Update::AnnotationStyle | Update::AnnotationData);
}

#pragma mark - Feature query api

std::vector<Feature> Map::queryRenderedFeatures(const ScreenCoordinate& point, const optional<std::vector<std::string>>& layerIDs) {
    if (!impl->style) return {};

    return impl->style->queryRenderedFeatures({
        { point },
        impl->transform.getState(),
        layerIDs
    });
}

std::vector<Feature> Map::queryRenderedFeatures(const ScreenBox& box, const optional<std::vector<std::string>>& layerIDs) {
    if (!impl->style) return {};

    return impl->style->queryRenderedFeatures({
        {
            box.min,
            { box.max.x, box.min.y },
            box.max,
            { box.min.x, box.max.y },
            box.min
        },
        impl->transform.getState(),
        layerIDs
    });
}

AnnotationIDs Map::queryPointAnnotations(const ScreenBox& box) {
    auto features = queryRenderedFeatures(box, {{ AnnotationManager::PointLayerID }});
    std::set<AnnotationID> set;
    for (auto &feature : features) {
        assert(feature.id);
        assert(*feature.id <= std::numeric_limits<AnnotationID>::max());
        set.insert(static_cast<AnnotationID>(feature.id->get<uint64_t>()));
    }
    AnnotationIDs ids;
    ids.reserve(set.size());
    std::move(set.begin(), set.end(), std::back_inserter(ids));
    return ids;
}

#pragma mark - Style API

style::Source* Map::getSource(const std::string& sourceID) {
    if (impl->style) {
        impl->styleMutated = true;
        return impl->style->getSource(sourceID);
    }
    return nullptr;
}

void Map::addSource(std::unique_ptr<style::Source> source) {
    if (impl->style) {
        impl->styleMutated = true;
        impl->style->addSource(std::move(source));
    }
}

std::unique_ptr<Source> Map::removeSource(const std::string& sourceID) {
    if (impl->style) {
        impl->styleMutated = true;
        return impl->style->removeSource(sourceID);
    }
    return nullptr;
}

style::Layer* Map::getLayer(const std::string& layerID) {
    if (impl->style) {
        impl->styleMutated = true;
        return impl->style->getLayer(layerID);
    }
    return nullptr;
}

void Map::addLayer(std::unique_ptr<Layer> layer, const optional<std::string>& before) {
    if (!impl->style) {
        return;
    }

    impl->styleMutated = true;
    impl->backend.activate();

    impl->style->addLayer(std::move(layer), before);
    impl->onUpdate(Update::Classes);

    impl->backend.deactivate();
}

std::unique_ptr<Layer> Map::removeLayer(const std::string& id) {
    if (!impl->style) {
        return nullptr;
    }

    impl->styleMutated = true;
    impl->backend.activate();

    auto removedLayer = impl->style->removeLayer(id);
    impl->onUpdate(Update::Classes);

    impl->backend.deactivate();

    return removedLayer;
}

void Map::addImage(const std::string& name, std::unique_ptr<const SpriteImage> image) {
    if (!impl->style) {
        return;
    }

    impl->styleMutated = true;
    impl->style->spriteAtlas->setSprite(name, std::move(image));
    impl->style->spriteAtlas->updateDirty();

    impl->onUpdate(Update::Repaint);
}

void Map::removeImage(const std::string& name) {
    if (!impl->style) {
        return;
    }

    impl->styleMutated = true;
    impl->style->spriteAtlas->removeSprite(name);
    impl->style->spriteAtlas->updateDirty();

    impl->onUpdate(Update::Repaint);
}

#pragma mark - Defaults

std::string Map::getStyleName() const {
    if (impl->style) {
        return impl->style->getName();
    }
    return {};
}

LatLng Map::getDefaultLatLng() const {
    if (impl->style) {
        return impl->style->getDefaultLatLng();
    }
    return {};
}

double Map::getDefaultZoom() const {
    if (impl->style) {
        return impl->style->getDefaultZoom();
    }
    return {};
}

double Map::getDefaultBearing() const {
    if (impl->style) {
        return impl->style->getDefaultBearing();
    }
    return {};
}

double Map::getDefaultPitch() const {
    if (impl->style) {
        return impl->style->getDefaultPitch();
    }
    return {};
}

#pragma mark - Toggles

void Map::setDebug(MapDebugOptions debugOptions) {
    impl->debugOptions = debugOptions;
    impl->onUpdate(Update::Repaint);
}

void Map::cycleDebugOptions() {
#if not MBGL_USE_GLES2
    if (impl->debugOptions & MapDebugOptions::StencilClip)
        impl->debugOptions = MapDebugOptions::NoDebug;
    else if (impl->debugOptions & MapDebugOptions::Overdraw)
        impl->debugOptions = MapDebugOptions::StencilClip;
#else
    if (impl->debugOptions & MapDebugOptions::Overdraw)
        impl->debugOptions = MapDebugOptions::NoDebug;
#endif // MBGL_USE_GLES2
    else if (impl->debugOptions & MapDebugOptions::Collision)
        impl->debugOptions = MapDebugOptions::Overdraw;
    else if (impl->debugOptions & MapDebugOptions::Timestamps)
        impl->debugOptions = impl->debugOptions | MapDebugOptions::Collision;
    else if (impl->debugOptions & MapDebugOptions::ParseStatus)
        impl->debugOptions = impl->debugOptions | MapDebugOptions::Timestamps;
    else if (impl->debugOptions & MapDebugOptions::TileBorders)
        impl->debugOptions = impl->debugOptions | MapDebugOptions::ParseStatus;
    else
        impl->debugOptions = MapDebugOptions::TileBorders;

    impl->onUpdate(Update::Repaint);
}

MapDebugOptions Map::getDebug() const {
    return impl->debugOptions;
}

bool Map::isFullyLoaded() const {
    return impl->style ? impl->style->isLoaded() : false;
}

void Map::addClass(const std::string& className) {
    if (impl->style && impl->style->addClass(className)) {
        impl->onUpdate(Update::Classes);
    }
}

void Map::removeClass(const std::string& className) {
    if (impl->style && impl->style->removeClass(className)) {
        impl->onUpdate(Update::Classes);
    }
}

void Map::setClasses(const std::vector<std::string>& classNames) {
    if (impl->style) {
        impl->style->setClasses(classNames);
        impl->onUpdate(Update::Classes);
    }
}

style::TransitionOptions Map::getTransitionOptions() const {
    if (impl->style) {
        return impl->style->getTransitionOptions();
    }
    return {};
}

void Map::setTransitionOptions(const style::TransitionOptions& options) {
    if (impl->style) {
        impl->style->setTransitionOptions(options);
    }
}

bool Map::hasClass(const std::string& className) const {
    return impl->style ? impl->style->hasClass(className) : false;
}

std::vector<std::string> Map::getClasses() const {
    if (impl->style) {
        return impl->style->getClasses();
    }
    return {};
}

void Map::setSourceTileCacheSize(size_t size) {
    if (size != impl->sourceCacheSize) {
        impl->sourceCacheSize = size;
        if (!impl->style) return;
        impl->style->setSourceTileCacheSize(size);
        impl->backend.invalidate();
    }
}

void Map::onLowMemory() {
    if (impl->painter) {
        impl->backend.activate();
        impl->painter->cleanup();
        impl->backend.deactivate();
    }
    if (impl->style) {
        impl->style->onLowMemory();
        impl->backend.invalidate();
    }
}

void Map::Impl::onSourceAttributionChanged(style::Source&, const std::string&) {
    backend.notifyMapChange(MapChangeSourceDidChange);
}

void Map::Impl::onUpdate(Update flags) {
    updateFlags |= flags;
    asyncUpdate.send();
}
    
void Map::Impl::onStyleLoaded() {
    backend.notifyMapChange(MapChangeDidFinishLoadingStyle);
}

void Map::Impl::onStyleError() {
    backend.notifyMapChange(MapChangeDidFailLoadingMap);
}

void Map::Impl::onResourceError(std::exception_ptr error) {
    if (mode == MapMode::Still && stillImageRequest) {
        auto request = std::move(stillImageRequest);
        request->callback(error);
    }
}

void Map::dumpDebugLogs() const {
    Log::Info(Event::General, "--------------------------------------------------------------------------------");
    Log::Info(Event::General, "MapContext::styleURL: %s", impl->styleURL.c_str());
    if (impl->style) {
        impl->style->dumpDebugLogs();
    } else {
        Log::Info(Event::General, "no style loaded");
    }
    Log::Info(Event::General, "--------------------------------------------------------------------------------");
}

} // namespace mbgl
