#!/usr/bin/python2.5

# Copyright (C) 2007 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.

from gtfsobjectbase import GtfsObjectBase
import problems as problems_module
import util

class Transfer(GtfsObjectBase):
  """Represents a transfer in a schedule"""
  _REQUIRED_FIELD_NAMES = ['from_stop_id', 'to_stop_id', 'transfer_type']
  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['min_transfer_time']
  _TABLE_NAME = 'transfers'
  _ID_COLUMNS = ['from_stop_id', 'to_stop_id']

  def __init__(self, schedule=None, from_stop_id=None, to_stop_id=None, transfer_type=None,
               min_transfer_time=None, field_dict=None):
    self._schedule = None
    if field_dict:
      self.__dict__.update(field_dict)
    else:
      self.from_stop_id = from_stop_id
      self.to_stop_id = to_stop_id
      self.transfer_type = transfer_type
      self.min_transfer_time = min_transfer_time

    if getattr(self, 'transfer_type', None) in ("", None):
      # Use the default, recommended transfer, if attribute is not set or blank
      self.transfer_type = 0
    else:
      try:
        self.transfer_type = util.NonNegIntStringToInt(self.transfer_type)
      except (TypeError, ValueError):
        pass

    if hasattr(self, 'min_transfer_time'):
      try:
        self.min_transfer_time = util.NonNegIntStringToInt(self.min_transfer_time)
      except (TypeError, ValueError):
        pass
    else:
      self.min_transfer_time = None
    if schedule is not None:
      # Note from Tom, Nov 25, 2009: Maybe calling __init__ with a schedule
      # should output a DeprecationWarning. A schedule factory probably won't
      # use it and other GenericGTFSObject subclasses don't support it.
      schedule.AddTransferObject(self)

  def ValidateFromStopIdIsPresent(self, problems):
    if util.IsEmpty(self.from_stop_id):
      problems.MissingValue('from_stop_id')
      return False
    return True

  def ValidateToStopIdIsPresent(self, problems):
    if util.IsEmpty(self.to_stop_id):
      problems.MissingValue('to_stop_id')
      return False
    return True

  def ValidateTransferType(self, problems):
    if not util.IsEmpty(self.transfer_type):
      if (not isinstance(self.transfer_type, int)) or \
          (self.transfer_type not in range(0, 4)):
        problems.InvalidValue('transfer_type', self.transfer_type)
        return False
    return True

  def ValidateMinimumTransferTime(self, problems):
    if not util.IsEmpty(self.min_transfer_time):
      if self.transfer_type != 2:
        problems.MinimumTransferTimeSetWithInvalidTransferType(
            self.transfer_type)

      # If min_transfer_time is negative, equal to or bigger than 24h, issue
      # an error. If smaller than 24h but bigger than 3h issue a warning.
      # These errors are not blocking, and should not prevent the transfer
      # from being added to the schedule.
      if (isinstance(self.min_transfer_time, int)):
        if self.min_transfer_time < 0:
          problems.InvalidValue('min_transfer_time', self.min_transfer_time,
                                reason="This field cannot contain a negative " \
                                       "value.")
        elif self.min_transfer_time >= 24*3600:
          problems.InvalidValue('min_transfer_time', self.min_transfer_time,
                                reason="The value is very large for a " \
                                       "transfer time and most likely " \
                                       "indicates an error.")
        elif self.min_transfer_time >= 3*3600:
          problems.InvalidValue('min_transfer_time', self.min_transfer_time,
                                type=problems_module.TYPE_WARNING,
                                reason="The value is large for a transfer " \
                                       "time and most likely indicates " \
                                       "an error.")
      else:
        # It has a value, but it is not an integer
        problems.InvalidValue('min_transfer_time', self.min_transfer_time,
                              reason="If present, this field should contain " \
                                "an integer value.")
        return False
    return True

  def GetTransferDistance(self):
    from_stop = self._schedule.stops[self.from_stop_id]
    to_stop = self._schedule.stops[self.to_stop_id]
    distance = util.ApproximateDistanceBetweenStops(from_stop, to_stop)
    return distance

  def ValidateFromStopIdIsValid(self, problems):
    if self.from_stop_id not in self._schedule.stops.keys():
      problems.InvalidValue('from_stop_id', self.from_stop_id)
      return False
    return True

  def ValidateToStopIdIsValid(self, problems):
    if self.to_stop_id not in self._schedule.stops.keys():
      problems.InvalidValue('to_stop_id', self.to_stop_id)
      return False
    return True

  def ValidateTransferDistance(self, problems):
    distance = self.GetTransferDistance()

    if distance > 10000:
      problems.TransferDistanceTooBig(self.from_stop_id,
                                      self.to_stop_id,
                                      distance)
    elif distance > 2000:
      problems.TransferDistanceTooBig(self.from_stop_id,
                                      self.to_stop_id,
                                      distance,
                                      type=problems_module.TYPE_WARNING)

  def ValidateTransferWalkingTime(self, problems):
    if util.IsEmpty(self.min_transfer_time):
      return

    if self.min_transfer_time < 0:
      # Error has already been reported, and it does not make sense
      # to calculate walking speed with negative times.
      return

    distance = self.GetTransferDistance()
    # If min_transfer_time + 120s isn't enough for someone walking very fast
    # (2m/s) then issue a warning.
    #
    # Stops that are close together (less than 240m appart) never trigger this
    # warning, regardless of min_transfer_time.
    FAST_WALKING_SPEED= 2 # 2m/s
    if self.min_transfer_time + 120 < distance / FAST_WALKING_SPEED:
      problems.TransferWalkingSpeedTooFast(from_stop_id=self.from_stop_id,
                                           to_stop_id=self.to_stop_id,
                                           transfer_time=self.min_transfer_time,
                                           distance=distance)

  def ValidateBeforeAdd(self, problems):
    result = True
    result = self.ValidateFromStopIdIsPresent(problems) and result
    result = self.ValidateToStopIdIsPresent(problems) and result
    result = self.ValidateTransferType(problems) and result
    result = self.ValidateMinimumTransferTime(problems) and result
    return result

  def ValidateAfterAdd(self, problems):
    valid_stop_ids = True
    valid_stop_ids = self.ValidateFromStopIdIsValid(problems) and valid_stop_ids
    valid_stop_ids = self.ValidateToStopIdIsValid(problems) and valid_stop_ids
    # We need both stop IDs to be valid to able to validate their distance and
    # the walking time between them
    if valid_stop_ids:
      self.ValidateTransferDistance(problems)
      self.ValidateTransferWalkingTime(problems)

  def Validate(self,
               problems=problems_module.default_problem_reporter):
    if self.ValidateBeforeAdd(problems) and self._schedule:
      self.ValidateAfterAdd(problems)

  def _ID(self):
    return tuple(self[i] for i in self._ID_COLUMNS)

  def AddToSchedule(self, schedule, problems):
    schedule.AddTransferObject(self, problems)
