# Copyright 2016 Google Inc.
#
# 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.

# pylint: disable=too-many-arguments
"""Annotations management for Vision API responses."""

import six

from google.cloud.vision.color import ImagePropertiesAnnotation
from google.cloud.vision.crop_hint import CropHint
from google.cloud.vision.entity import EntityAnnotation
from google.cloud.vision.face import Face
from google.cloud.vision.safe_search import SafeSearchAnnotation
from google.cloud.vision.text import TextAnnotation
from google.cloud.vision.web import WebDetection


_CROP_HINTS_ANNOTATION = 'cropHintsAnnotation'
_FACE_ANNOTATIONS = 'faceAnnotations'
_FULL_TEXT_ANNOTATION = 'fullTextAnnotation'
_IMAGE_PROPERTIES_ANNOTATION = 'imagePropertiesAnnotation'
_SAFE_SEARCH_ANNOTATION = 'safeSearchAnnotation'
_WEB_DETECTION = 'webDetection'

_KEY_MAP = {
    _CROP_HINTS_ANNOTATION: 'crop_hints',
    _FACE_ANNOTATIONS: 'faces',
    _FULL_TEXT_ANNOTATION: 'full_texts',
    _IMAGE_PROPERTIES_ANNOTATION: 'properties',
    'labelAnnotations': 'labels',
    'landmarkAnnotations': 'landmarks',
    'logoAnnotations': 'logos',
    _SAFE_SEARCH_ANNOTATION: 'safe_searches',
    'textAnnotations': 'texts',
    _WEB_DETECTION: 'web',
}


class Annotations(object):
    """Helper class to bundle annotation responses.

    :type crop_hints: list
    :param crop_hints: List of
                  :class:`~google.cloud.vision.crop_hint.CropHintsAnnotation`.

    :type faces: list
    :param faces: List of :class:`~google.cloud.vision.face.Face`.

    :type full_texts: list
    :param full_texts: List of
                       :class:`~google.cloud.vision.text.TextAnnotation`.

    :type properties: list
    :param properties:
        List of :class:`~google.cloud.vision.color.ImagePropertiesAnnotation`.

    :type labels: list
    :param labels: List of
                   :class:`~google.cloud.vision.entity.EntityAnnotation`.

    :type landmarks: list
    :param landmarks: List of
                      :class:`~google.cloud.vision.entity.EntityAnnotation.`

    :type logos: list
    :param logos: List of
                  :class:`~google.cloud.vision.entity.EntityAnnotation`.

    :type safe_searches: list
    :param safe_searches:
        List of :class:`~google.cloud.vision.safe_search.SafeSearchAnnotation`

    :type texts: list
    :param texts: List of
                  :class:`~google.cloud.vision.entity.EntityAnnotation`.

    :type web: list
    :param web: List of :class:`~google.cloud.vision.web.WebDetection`.
    """
    def __init__(self, crop_hints=(), faces=(), full_texts=(), properties=(),
                 labels=(), landmarks=(), logos=(), safe_searches=(),
                 texts=(), web=()):
        self.crop_hints = crop_hints
        self.faces = faces
        self.full_texts = full_texts
        self.properties = properties
        self.labels = labels
        self.landmarks = landmarks
        self.logos = logos
        self.safe_searches = safe_searches
        self.texts = texts
        self.web = web

    @classmethod
    def from_api_repr(cls, response):
        """Factory: construct an instance of ``Annotations`` from a response.

        :type response: dict
        :param response: Vision API response object.

        :rtype: :class:`~google.cloud.vision.annotations.Annotations`
        :returns: An instance of ``Annotations`` with detection types loaded.
        """
        annotations = {
            _KEY_MAP[feature_type]: _entity_from_response_type(
                feature_type, annotation)
            for feature_type, annotation in six.iteritems(response)
            if feature_type in _KEY_MAP
        }
        return cls(**annotations)

    @classmethod
    def from_pb(cls, response):
        """Factory: construct an instance of ``Annotations`` from protobuf.

        :type response: :class:`~google.cloud.proto.vision.v1.\
                        image_annotator_pb2.AnnotateImageResponse`
        :param response: ``AnnotateImageResponse`` from protobuf call.

        :rtype: :class:`~google.cloud.vision.annotations.Annotations`
        :returns: ``Annotations`` instance populated from gRPC response.
        """
        annotations = _process_image_annotations(response)
        return cls(**annotations)


def _process_image_annotations(image):
    """Helper for processing annotation types from protobuf.

    :type image: :class:`~google.cloud.proto.vision.v1.image_annotator_pb2.\
                 AnnotateImageResponse`
    :param image: ``AnnotateImageResponse`` from protobuf.

    :rtype: dict
    :returns: Dictionary populated with entities from response.
    """
    return {
        'crop_hints': _make_crop_hints_from_pb(image.crop_hints_annotation),
        'faces': _make_faces_from_pb(image.face_annotations),
        'full_texts': _make_full_text_from_pb(image.full_text_annotation),
        'labels': _make_entity_from_pb(image.label_annotations),
        'landmarks': _make_entity_from_pb(image.landmark_annotations),
        'logos': _make_entity_from_pb(image.logo_annotations),
        'properties': _make_image_properties_from_pb(
            image.image_properties_annotation),
        'safe_searches': _make_safe_search_from_pb(
            image.safe_search_annotation),
        'texts': _make_entity_from_pb(image.text_annotations),
        'web': _make_web_detection_from_pb(image.web_detection)
    }


def _make_crop_hints_from_pb(crop_hints):
    """Create list of ``CropHint`` objects from a protobuf response.

    :type crop_hints: list
    :param crop_hints: List of
                       :class:`google.cloud.grpc.vision.v1.\
                       image_annotator_pb2.CropHintsAnnotation`

    :rtype: list
    :returns: List of ``CropHint`` objects.
    """
    return [CropHint.from_pb(hint) for hint in crop_hints.crop_hints]


def _make_entity_from_pb(annotations):
    """Create an entity from a protobuf response.

    :type annotations:
    :class:`~google.cloud.proto.vision.v1.image_annotator_pb2.EntityAnnotation`
    :param annotations: protobuf instance of ``EntityAnnotation``.

    :rtype: list
    :returns: List of ``EntityAnnotation``.
    """
    return [EntityAnnotation.from_pb(annotation) for annotation in annotations]


def _make_faces_from_pb(faces):
    """Create face objects from a protobuf response.

    :type faces:
    :class:`~google.cloud.proto.vision.v1.image_annotator_pb2.FaceAnnotation`
    :param faces: Protobuf instance of ``FaceAnnotation``.

    :rtype: list
    :returns: List of ``Face``.
    """
    return [Face.from_pb(face) for face in faces]


def _make_full_text_from_pb(full_text):
    """Create text annotation object from protobuf response.

    :type full_text: :class:`~google.cloud.proto.vision.v1.\
                     text_annotation_pb2.TextAnnotation`
    :param full_text: Protobuf instance of ``TextAnnotation``.

    :rtype: :class:`~google.cloud.vision.text.TextAnnotation`
    :returns: Instance of ``TextAnnotation``.
    """
    return TextAnnotation.from_pb(full_text)


def _make_image_properties_from_pb(image_properties):
    """Create ``ImageProperties`` object from a protobuf response.

    :type image_properties: :class:`~google.cloud.proto.vision.v1.\
                            image_annotator_pb2.ImagePropertiesAnnotation`
    :param image_properties: Protobuf instance of
                             ``ImagePropertiesAnnotation``.

    :rtype: list or ``None``
    :returns: List of ``ImageProperties`` or ``None``.
    """
    return ImagePropertiesAnnotation.from_pb(image_properties)


def _make_safe_search_from_pb(safe_search):
    """Create ``SafeSearchAnnotation`` object from a protobuf response.

    :type safe_search: :class:`~google.cloud.proto.vision.v1.\
                            image_annotator_pb2.SafeSearchAnnotation`
    :param safe_search: Protobuf instance of ``SafeSearchAnnotation``.

    :rtype: :class: `~google.cloud.vision.safe_search.SafeSearchAnnotation`
    :returns: Instance of ``SafeSearchAnnotation``.
    """
    return SafeSearchAnnotation.from_pb(safe_search)


def _make_web_detection_from_pb(annotation):
    """Create ``WebDetection`` object from a protobuf response.

    :type annotation: :class:`~google.cloud.proto.vision.v1.web_detection_pb2\
                      .WebDetection`
    :param annotation: Protobuf instance of ``WebDetection``.

    :rtype: :class: `~google.cloud.vision.web.WebDetection`
    :returns: Instance of ``WebDetection``.
    """
    return WebDetection.from_pb(annotation)


def _entity_from_response_type(feature_type, results):
    """Convert a JSON result to an entity type based on the feature.

    :rtype: list
    :returns: List containing any of
              :class:`~google.cloud.vision.entity.EntityAnnotation`,
              :class:`~google.cloud.vision.face.Face`

              or one of

              :class:`~google.cloud.vision.safe_search.SafeSearchAnnotation`,
              :class:`~google.cloud.vision.color.ImagePropertiesAnnotation`.
    """
    detected_objects = []
    if feature_type == _FACE_ANNOTATIONS:
        detected_objects.extend(
            Face.from_api_repr(face) for face in results)
    elif feature_type == _IMAGE_PROPERTIES_ANNOTATION:
        return ImagePropertiesAnnotation.from_api_repr(results)
    elif feature_type == _SAFE_SEARCH_ANNOTATION:
        return SafeSearchAnnotation.from_api_repr(results)
    elif feature_type == _WEB_DETECTION:
        return WebDetection.from_api_repr(results)
    elif feature_type == _CROP_HINTS_ANNOTATION:
        crop_hints = results.get('cropHints', [])
        detected_objects.extend(
            CropHint.from_api_repr(result) for result in crop_hints)
    elif feature_type == _FULL_TEXT_ANNOTATION:
        return TextAnnotation.from_api_repr(results)
    else:
        for result in results:
            detected_objects.append(EntityAnnotation.from_api_repr(result))
    return detected_objects
