import types


class RangeError(Exception):
  """
   raised when a value is out of legal range
  """
  pass


class Angle:
  """
  Encapsulates an Integer 0 <= angle < 360
  """
  def __init__(self, value=0):
    """ Angle([0 <= value:Integer = 0 < 360]) constructor.
    """
    self.setValue(value)

  def __str__(self):
    """ __str__() -> String representation of an Angle.

     Example: str(Angle(20)) returns "<Angle:20>"
    """
    return "<Angle:%d>" % (self.getValue())

  def getValue(self):
    """ getValue() -> Integer in [0,360[

     Get the Angle value.
    """
    return self.__data

  def setValue(self, value=0):
    """ setValue([0 <= value:Integer = 0 < 360]) ->

     Set the Angle value. 
     The value is kept in a private variable __data
     Raises:
       TypeError if value is not an Integer
       RangeError if value not in [0, 360[ range
    """
    if type(value) != types.IntType:
      raise TypeError, "Angle value must be an Integer"
    if not (0 <= value < 360):
      raise RangeError, "Angle value must be in [0, 360[ range"

    self.__data = value

  def __add__(self, angle):
    """ __add__(angle:Angle or Integer) --> Angle object, sum of self and angle
     Raises: 
       TypeError if angle argument is not an Integer nor an Angle
       RangeError if an Integer angle argument is not in [0, 360[ range
    """
    if type(angle) == types.IntType:
      if (0 <= angle < 360):
        return Angle(self.getValue()+angle)
      else:
        raise RangeError, "angle value must be in [0, 360[ range"
    elif type(angle) == type(Angle()):
      return Angle(self.getValue()+angle.getValue())
    else:
      raise TypeError, "angle argument must be either Integer or Angle"


class Aircraft:
  """
  Encapsulates Aircraft properties.
  """

  # Class variable keeping track of total number of aircraft
  # Access by means of Aircraft.NumAircraft
  NumAircraft = 0

  def __init__(self, heading=None):
    """
     __init__([heading:Angle|[0,360[ = Angle(0)]) Aircraft constructor.
    """
    if heading is None:
      self.__heading = Angle(0) # default heading (mutable)
    elif type(heading) == type(Angle()):
      self.__heading = heading
    else:
      raise TypeError, "heading must be of Angle type"
    Aircraft.NumAircraft += 1         # class attribute
    self.__UID = Aircraft.NumAircraft # Unique aircraft ID

  def __str__(self):
    """ __str__() -> String representation of an Aircraft instance.

     Example: str(Aircraft(Angle(20))) returns "<Aircraft:3:<Angle:20>>"
    """
    return "<Aircraft:%d:%s>" % (self.getUID(),str(self.getHeading()))

  def getUID(self):
    """ getUID() -> Unique aircraft ID (Integer)
    """
    return self.__UID

  def getHeading(self):
    """ getHeading() -> Angle
    """
    return self.__heading

  def turn(self, angle=None):
    """ turn([angle:Angle|[0,360[ = Angle(0)]) adds angle to heading.
    """
    if angle is None:
      self.__heading = self.getHeading() + Angle(0)
    else:
      # type/range checking of angle is done in the Angle class
      self.__heading = self.getHeading() + angle


class Glider(Aircraft):
  """
  Encapsulates Glider (a special kind of Aircraft) properties.
  """

  def __init__(self, heading=None):
    """
     __init__([heading:Angle|[0,360[ = Angle(0)]) Glider constructor.
    """
    Aircraft.__init__(self, heading) # superclass' constructor
    self.__towlineAttached = 0       # no towline attached

  def __str__(self):
    """ __str__() -> String representation of a Glider instance.

     Example: str(Glider(Angle(20))) returns 
              "<Glider:<Aircraft:3:<Angle:20>>:Not Attached>"
    """
    if self.towlineAttached():
      self.__attachedString = "Attached"
    else:
      self.__attachedString = "Not Attached"
    return "<Glider:%s:%s>" % (Aircraft.__str__(self),self.__attachedString)

  def towlineAttached(self):
    """ towlineAttached() -> 0|1  (whether towline is attached).
    """
    return self.__towlineAttached

  def attachTowline(self):
    """ attachTowline() attaches towline.
    """
    if self.towlineAttached():
      print "Warning: towline is already attached"
    else:
      self.__towlineAttached = 1

  def detachTowline(self):
    """ detachTowline() detaches towline.
    """
    if not self.towlineAttached():
      print "Warning: no towline to detache"
    else:
      self.__towlineAttached = 0


class PassengerVehicle:
  """
  Encapsulates an infinite capacity
  Passenger Vehicle's properties (number of passengers).
  """
  def __init__(self, passengers=0):
    """
     __init__([passengers:PosInteger = 0]) PassengerVehicle constructor.
    """
    self.__checkPassengerType(passengers)
    self.__passengers = passengers

  def __checkPassengerType(self, passengers):
    """ __checkPassengerType(passengers)
      Raises TypeError if non-Integer or negative argument
    """
    if type(passengers) != types.IntType:
      raise TypeError, "only Integer number of passengers allowed"
    elif passengers < 0:
      raise TypeError, "only non-negative number of passengers allowed"

  def __str__(self):
    """ __str__() -> String representation of a PassengerVehicle instance.

     Example: str(PassengerVehicle(10)) returns 
              "<PassengerVehicle:10>"
    """
    return "<PassengerVehicle:%d>" % (self.numPassengers())

  def numPassengers(self):
    """ numPassengers() -> number of passengers (Integer)
    """
    return self.__passengers

  def board(self, passengers=0):
    """ board([passengers:PosInteger = 0]) board passengers
    """
    self.__checkPassengerType(passengers)
    self.__passengers += passengers

  def unload(self, passengers=0):
    """ board([passengers:PosInteger = 0]) unload passengers
    """
    self.__checkPassengerType(passengers)
    if passengers > self.numPassengers():
      print "Cannot unload more passengers than present on vehicle"
    else:
      self.__passengers -= passengers

  def isEmpty(self):
    """ isEmpty() -> 1|0 empty status of the vehicle
    """
    return self.numPassengers() == 0


class PassengerAircraft(Aircraft, PassengerVehicle):
  """ Encapsulates properties of both Aircraft and PassengerVehicle.
      (multiple inheritance)
  """
  def __init__(self):
    """ __init__() PassengerAircraft constructor.
    """
    Aircraft.__init__(self)          # Aircraft baseclass constructor
    PassengerVehicle.__init__(self)  # PassengerVehicle baseclass constructor


  def __str__(self):
    """ __str__() -> String representation of a PassengerAircraft instance.

     Example: str(PassengerAircraft()) for the third instantiated Aircraft 
       returns "<PassengerAircraft:<Aircraft:3:<Angle:20>>:<PassengerVehicle:0>>"
    """
    return "<PassengerAircraft:%s:%s>" % (Aircraft.__str__(self),
                                          PassengerVehicle.__str__(self))