#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  classwork.py
#  
#  Copyright 2012 Michael Malicoat <michael@circusmachina.com>
#  
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#  

## @file
#  This file defines and implements the base class, from which all others
#  are derived.


## The error message used when AnObject.init() returns False
oErrorInitializing = ("Failed to construct and initialize an instance of "
                      "{className}")

## The base class, from which all others are derived.
class AnObject(object):
    
    ## Initialize a new object instance.
    #
    #  This method can be overridden by descendants of AnObject, where it is 
    #  used to set the attributes of the class instance and prepare it for use.
    #  Why not use the inherent `__init__()` function for this?  Because the 
    #  author dislikes ugly leading underscores.
    #
    #  As implemented by AnObject, this routine will always return `True`.
    #  If a descendant class overrides this method such that it 
    #  returns `False`, then the default constructor (inherited from AnObject)
    #  will cause an exception to be raised.
    def init(Self):            
        return True
        
    ## Construct and initialize a new instance of AnObject.
    #
    #  This method calls AnObject.init() and, if that routine returns `False`
    #  to indicate that initialization of the new instance was unsuccesful, then
    #  an exception will be raised.
    def __init__(Self):
        if Self.init() == False:
            raise Exception(oErrorInitializing.format(className=
              Self.__class__.__name__))
    
    ## Destroy the object instance.
    #
    #  While this method can be used to clean up after an object instance, it
    #  cannot reliably do so at a given time because of the way Python manages
    #  reference counting.
    def __del__(Self):
        pass

    ## Construct a "pretty" display name for the class instance, based on
    #  the name of its class.  
    #    
    #  This routine assumes that:
    #     -# The class name starts with "A" or "An"
    #     -# The class name will look pretty with a space between each upper-
    #        case character of the class name
    #    
    #   A class named "ABalloonAnimal" would be rendered as "a balloon animal" 
    #   by this method.
    #
    #  This method is primarily designed to be used for debugging output, such
    #  as to log files.
    def displayName(Self):       
        # Get the class name
        myName = Self.__class__.__name__
        # Store the resulting display name
        result = ""
        index = 0
        
        for char in myName:
            # Insert a space into the output for each capital letter in the name
            if (char.isupper()) and (index > 0):
                if myName[index + 1].islower():
                    result += " "
            
            # Remove articles
            if (result == "a ") or (result == "an "):
                result = ""
            
            result += char.lower()
            index += 1
        
        return result   
        
    ## Determine whether the class instance is related to the specified 
    #  class.
    #    
    #  This method simply calls `isinstance()` to determine whether the class
    #  instance is related to the specified class, either directly (it is an
    #  instance of that class) or by way of inheritance.  The method is 
    #  defined purely for aesthetic reasons, as it makes more sense, from an
    #  object-oriented view, to query the class to determine its inheritance
    #  rather than to query using a function that is external to the class.
    def implements(Self, ThisClass):
        return isinstance(Self, ThisClass)
        
    # IsPrintable implementation -----------------------------------------------    
    ## Return a string representation of the class, suitable for output to 
    #  a console or text-based device.
    #
    #  This method is provided for aesthetics, as the author dislikes the 
    #  inherent ugliness of leading underscores and so wishes to avoid defining
    #  the __str__() method.  This method is called by the `__str__()` method
    #  for all descendants of AnObject, and so is indirectly called whenever the
    #  `str()` function is used on a descendant of AnObject.  
    #
    #  In the base implementation, this method simply returns the value of a
    #  call to AnObject.displayName().
    def toString(Self):        
        return Self.displayName()

    ## Return a string representation of the class.
    #    
    #  This method simply calls the toString() method.  It is designed to 
    #  allow descendants of AnObject to implement the AnObject.toString() method 
    #  rather than the `__str__()` method, as the former is easier to type (and 
    #  less ugly) than the latter. 
    def __str__(Self):        
        return Self.toString()    
        
    # Properties ---------------------------------------------------------------
    ## Return the name of the class from which the instance was created.
    #
    #  This method simply returns the value of `Self.__class__.__name__` and is
    #  provided for aesthetic reasons (the author dislikes leading underscores).
    def classname(Self):        
        return Self.__class__.__name__
        
    ## Return the name of the class.
    #
    #  The base implementation of this method merely returns the display name
    #  of the class; other implementations may return a different value.
    def name(Self):
        return Self.displayName()
