# -*- coding: utf-8 -*- #
# Copyright 2017 Google LLC. All Rights Reserved.
#
# 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.

"""Utilities for gcloud ml video-intelligence commands."""


import os

from googlecloudsdk.api_lib.storage import storage_util
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core.util import files
from googlecloudsdk.core.util import iso_duration
from googlecloudsdk.core.util import times

VIDEO_API = 'videointelligence'
VIDEO_API_VERSION = 'v1'


INPUT_ERROR_MESSAGE = ('[{}] is not a valid format for video input. Must be a '
                       'local path or a Google Cloud Storage URI '
                       '(format: gs://bucket/file).')

OUTPUT_ERROR_MESSAGE = ('[{}] is not a valid format for result output. Must be '
                        'a Google Cloud Storage URI '
                        '(format: gs://bucket/file).')

SEGMENT_ERROR_MESSAGE = ('Could not get video segments from [{0}]. '
                         'Please make sure you give the desired '
                         'segments in the form: START1:END1,START2:'
                         'END2, etc.: [{1}]')


class Error(exceptions.Error):
  """Base error class for this module."""


class SegmentError(Error):
  """Error for poorly formatted video segment messages."""


class VideoUriFormatError(Error):
  """Error if the video input URI is invalid."""


class AudioTrackError(Error):
  """Error if the audio tracks setting is invalid."""


def ValidateAndParseSegments(given_segments):
  """Get VideoSegment messages from string of form START1:END1,START2:END2....

  Args:
    given_segments: [str], the list of strings representing the segments.

  Raises:
    SegmentError: if the string is malformed.

  Returns:
    [GoogleCloudVideointelligenceXXXVideoSegment], the messages
      representing the segments or None if no segments are specified.
  """
  if not given_segments:
    return None

  messages = apis.GetMessagesModule(VIDEO_API, VIDEO_API_VERSION)
  segment_msg = messages.GoogleCloudVideointelligenceV1VideoSegment
  segment_messages = []
  segments = [s.split(':') for s in given_segments]
  for segment in segments:
    if len(segment) != 2:
      raise SegmentError(SEGMENT_ERROR_MESSAGE.format(
          ','.join(given_segments), 'Missing start/end segment'))
    start, end = segment[0], segment[1]
    # v1beta2 requires segments as a duration string representing the
    # count of seconds and fractions of seconds to nanosecond resolution
    # e.g. offset "42.596413s". To perserve backward compatibility with v1beta1
    # we will parse any segment timestamp with out a duration unit as an
    # int representing microseconds.
    try:
      start_duration = _ParseSegmentTimestamp(start)
      end_duration = _ParseSegmentTimestamp(end)
    except ValueError as ve:
      raise SegmentError(SEGMENT_ERROR_MESSAGE.format(
          ','.join(given_segments), ve))

    sec_fmt = '{}s'
    segment_messages.append(segment_msg(
        endTimeOffset=sec_fmt.format(end_duration.total_seconds),
        startTimeOffset=sec_fmt.format(start_duration.total_seconds)))
  return segment_messages


def _ParseSegmentTimestamp(timestamp_string):
  """Parse duration formatted segment timestamp into a Duration object.

  Assumes string with no duration unit specified (e.g. 's' or 'm' etc.) is
  an int representing microseconds.

  Args:
    timestamp_string: str, string to convert

  Raises:
    ValueError: timestamp_string is not a properly formatted duration, not a
    int or int value is <0

  Returns:
    Duration object represented by timestamp_string
  """
  # Assume timestamp_string passed as int number of microseconds if no unit
  # e.g. 4566, 100, etc.
  try:
    microseconds = int(timestamp_string)
  except ValueError:
    try:
      duration = times.ParseDuration(timestamp_string)
      if duration.total_seconds < 0:
        raise times.DurationValueError()
      return duration
    except (times.DurationSyntaxError, times.DurationValueError):
      raise ValueError('Could not parse timestamp string [{}]. Timestamp must '
                       'be a properly formatted duration string with time '
                       'amount and units (e.g. 1m3.456s, 2m, 14.4353s)'.format(
                           timestamp_string))
  else:
    log.warning("Time unit missing ('s', 'm','h') for segment timestamp [{}], "
                "parsed as microseconds.".format(timestamp_string))

  if microseconds < 0:
    raise ValueError('Could not parse duration string [{}]. Timestamp must be'
                     'greater than >= 0)'.format(timestamp_string))

  return iso_duration.Duration(microseconds=microseconds)


def ValidateOutputUri(output_uri):
  """Validates given output URI against validator function.

  Args:
    output_uri: str, the output URI for the analysis.

  Raises:
    VideoUriFormatError: if the URI is not valid.

  Returns:
    str, The same output_uri.
  """
  if output_uri and not storage_util.ObjectReference.IsStorageUrl(output_uri):
    raise VideoUriFormatError(OUTPUT_ERROR_MESSAGE.format(output_uri))
  return output_uri


def UpdateRequestWithInput(unused_ref, args, request):
  """The Python hook for yaml commands to inject content into the request."""
  path = args.input_path
  if os.path.isfile(path):
    request.inputContent = files.ReadBinaryFileContents(path)
  elif storage_util.ObjectReference.IsStorageUrl(path):
    request.inputUri = path
  else:
    raise VideoUriFormatError(INPUT_ERROR_MESSAGE.format(path))
  return request


# Argument Processors


def AudioTrackProcessor(tracks):
  """Verify at most two tracks, convert to [int, int]."""
  if len(tracks) > 2:
    raise AudioTrackError('Can not specify more than two audio tracks.')
  return tracks
