import operator
from types import StringType, UnicodeType, TupleType, ListType, DictType

class ConsoleMessage(object):
    def __init__(self, type_, attr, contents):
        if type(type_) not in [StringType, UnicodeType]:
            raise TypeError("type_ must be a string")
        if type(attr) is not DictType:
            raise TypeError("attr must be a dict")
        if type(contents) is not ListType:
            raise TypeError("contents must be a list")
        
        self.type_ = type_
        self.attr = attr
        self.contents = contents
        
    def __eq__(self, other):
        return (isinstance(other, self.__class__)
            and self.type_ == other.type_
            and self.attr == other.attr
            and (len(self.contents) == len(other.contents)
                 and all(map(operator.eq, self.contents, other.contents))))
        
    def to_dict(self):
        def make_dict(m):
            if isinstance(m, ConsoleMessage):
                return {'type': m.type_, 'attr': m.attr, 'contents': map(make_dict, m.contents)}
            else:
                return m
        return make_dict(self)
        
    @staticmethod
    def construct(message):
        """A helper function to simplify the creation of the ``ConsoleMessage``
        instances.
        
        Some examples of the arguments that this function does accept:
            ('div', 'system', [
                'This is system message with some ', ('span', 'bold', 'bold text'), ' in it.'
            ])
            
            ('ul', None, [
                ('li', 'First item'),
                ('li', 'Second item'),
                ('li', ('a', {'href': '#third_item'}, 'Third clickable item'))
            ])
            
        If the argument is a string, ``construct`` does nothing but to return
        the argument unchanged (i.e. returns a string).
        """
        if type(message) in [StringType, UnicodeType]:
            return message
        elif isinstance(message, ConsoleMessage):
            return message
        elif type(message) is TupleType:
            type_ = None
            attr = None
            contents = None
            
            try:
                type_, attr, contents = message
            except ValueError:
                attr = None
                try:
                    type_, contents = message
                except ValueError:
                    type_, = message
                
            if type_ is None:
                raise ValueError("type must not be None")
            if attr is None:
                attr = {}
            if type(attr) in [StringType, UnicodeType]:
                attr = {'class': attr}
            if contents is None:
                contents = []
            if type(contents) in [StringType, UnicodeType, TupleType]:
                contents = [contents]
            if type(contents) is not ListType:
                raise TypeError("wrong contents type")
            
            return ConsoleMessage(type_, attr, map(ConsoleMessage.construct, contents))
        else:
            raise TypeError("message type must be a tuple or a string")
            
        
def message(*args):
    return ConsoleMessage.construct(args)
    
