import os

from matplotlib import get_data_path, rcParams
from matplotlib._mathtext_data import tex2uni
from matplotlib.ft2font import FT2Font, KERNING_DEFAULT

# Used only by the PS backend
bakoma_fonts = []

if rcParams['mathtext.unicode'] == True:
    _path = get_data_path()
    faces = ('mit', 'cal', 'tt', 'cal')
    filenamesd = dict(
                    [(face, os.path.join(_path, rcParams['mathtext.'
                        + face])) for face in faces])
    #~ fonts = dict(
                #~ [ (face, FT2Font(filenamesd[face])) for
                    #~ face in faces]
                #~ )
    fonts = {}
else:
    # This should be removed ASAP
    raise TexParseError("mathtext.unicode matplotlibrc set to 'False'")
#~ Fonts = UnicodeFonts()


esc_char = '\\'
# Grouping delimiters
begin_group_char = '{'
end_group_char = '}'
dec_delim = '.'
word_delim = ' '
scripts = ("superscript", "subscript")
commands = {}

# _textclass can be unicode or str
_textclass = unicode

# The topmost environment
environment = {
# We start with zero scriptdepth (should be incremented by a Scripted
# instance)
'scriptdepth' : 0, 
'face' : 'mit',
'fontsize' : 12,
'dpi' : 100,
}

# Matches a scriptdepth to a factor that should be multiplied against
# the toplevel fontsize to obtain the script dependent fontsize
scriptfactors = {
    0 : 1,
    1: 0.7,
    2: 0.5,
}

# Maximum number of nestings (groups within groups)
max_depth = 10

# General helper functions
def get_unicode_index(symbol):
    """get_unicode_index(symbol) -> integer
    
    Return the integer index (from the Unicode table) of symbol.
    symbol can be a single unicode character, a TeX command (i.e. r'\pi'),
    or a Type1 symbol name (i.e. 'phi').
    
    """
    try:    # This will succeed if symbol is a single unicode char
        return ord(symbol)
    except TypeError:
        pass
    try:    # Is symbol a TeX symbol (i.e. \alpha)
        return tex2uni[symbol]
    except KeyError:
        pass
    try:    # Is symbol a Type1 name (i.e. degree)? If not raise error
        return type12uni[symbol]
    except KeyError:
        message = """'%(symbol)s' is not a valid Unicode character or
TeX/Type1 symbol"""%locals()
        raise ValueError, message

class TexParseError(Exception):
    pass

class Group(list):
    """A class that corresponds to a TeX group ({}). 
    """
    def __init__(self, texlist):
        list.__init__(self, texlist)
        #~ for i, subelement in enumerate(self):
            #~ if isinstance(subelement, list):
                #~ self[i] = Group(self[i])

    def __getitem__(self, index):
        try:
            return list.__getitem__(self, index)
        except IndexError:
            return None

    def get_width(self, env):
        if self:
            w = sum([i.get_width(env) for i in self])
        else:
            w = 0
        return w

    def get_height(self, env):
        if self:
            h = max([i.get_height(env) for i in self])
        else:
            h = 0
        return h

    def render(self, env, x, y, output="BMP"):
        for item in self:
            item.render(env, x, y, output)
            x += item.get_width(env)

class Scripted:
    """Used for creating elements that have sub/superscripts"""
    def __init__(self, nucleus=Group([]), type="ord", subscript=Group([]),
        superscript=Group([])):
        self.nucleus = nucleus
        self.type = type
        self.subscript = subscript
        self.superscript = superscript
    
    def __repr__(self):
        tmp = (repr(i) for i in [self.nucleus, self.type, self.subscript,
                    self.superscript])
        tmp = tuple(tmp)
        return "Scripted(nucleus=%s, type=%s, \
subscript=%s, superscript=%s)"%tmp

    def get_width(self, env):
        nw = self.nucleus.get_width(env)

        _env = env.copy()
        _env['scriptdepth'] += 1
        subw = self.subscript.get_width(_env)
        supw = self.superscript.get_width(_env)
        return max(subw, supw) + nw

    def get_height(self, env):
        nh = self.nucleus.get_height(env)

        _env = env.copy()
        _env['scriptdepth'] += 1
        subh = self.subscript.get_height(_env)
        suph = self.superscript.get_height(_env)
        return subh + suph + nh

    def render(self, env, x, y, output="BMP"):
        self.nucleus.render(env, x, y, output)
        nw = self.nucleus.get_width(env)
        nh = self.nucleus.get_height(env)
        
        _env = env.copy()
        _env['scriptdepth'] += 1
        # TO-DO - replace 0.5 with smth useful
        self.subscript.render(env, x + nw, y + nh + 0.5, output)
        self.superscript.render(env, x + nw, y + nh - 0.5, output)

class TexStringClass(_textclass):
    """A class based on unicode or str that implements the methods needed
    for rendering TeX. This is arguably the most important class.
    
    """
    def get_width(self, env):
        return self.get_width_height(env)[0]

    def get_height(self, env):
        return self.get_width_height(env)[1]

    def get_width_height(self, env):
        trash, font = self.get_font(env)
        # The other argument is angle
        font.set_text(_textclass(self), 0)
        w, h = font.get_width_height()
        return w/64.0, h/64.0

    def render(self, env, x, y, output="BMP"):
        if output == "BMP":
            print "Rendering: ", repr(self)
            fontindex, font = self.get_font(env)
            fonts[fontindex][1] = x
            fonts[fontindex][2] = y
            if is_command(self):
                uniindex = tex2uni[self.strip(esc_char)]
                glyph = font.load_char(uniindex)
                font.set_bitmap_size(glyph.width, glyph.height)
                font.draw_glyph_to_bitmap(0, 0, glyph)
            else:
                font.set_text(_textclass(self), 0)
                font.draw_glyphs_to_bitmap()

    def get_font(self, env):
        face = env['face']
        fontsize = env['fontsize'] * scriptfactors[env['scriptdepth']]
        dpi = env['dpi']
        font = FT2Font(filenamesd[face])
        font.set_size(fontsize, dpi)
        x, y = 0, 0
        if fonts:
            #print fonts.keys()
            fontindex = max(fonts.keys()) + 1
        else:
            fontindex = 1
        fonts[fontindex] = [font, x, y]
        return fontindex, font

# Helper functions, mainly used by the parser
def debug_tok(tok):
    print tok
    #pass

def is_command(tok):
    pass

def remove_comments(texstring):
    # TO-DO
    return texstring

def group_split(texstring):
    """Splits the string into three parts based on the grouping delimiters,
    and returns them as a list.
    """
    if texstring == begin_group_char + end_group_char:
        return '', [], ''
    length = len(texstring)
    i = texstring.find(begin_group_char)
    if i == -1:
        return texstring, '', ''
    pos_begin = i
    count = 1
    num_groups = 0
    while count != 0:
        i = i + 1
        # First we check some things
        if num_groups > max_depth:
            message = "Maximum number of nestings reached. Too many groups"
            raise TexParseError(message)
        if i == length:
            message = "Group not closed properly"
            raise TexParseError(message)

        if texstring[i] == end_group_char:
            count -= 1
        elif texstring[i] == begin_group_char:
            num_groups += 1
            count += 1
    before = texstring[:pos_begin]
    if pos_begin + 1 == i:
        grouping = []
    else:
        grouping = texstring[pos_begin + 1:i]
    after = texstring[i + 1:]
    return before, grouping, after

def break_up_commands(texstring):
    """Breaks up a string (mustn't contain any groupings) into a list
    of commands and pure text.
    """
    result = []
    if not texstring:
        return result
    _texstrings = texstring.split(esc_char)
    for i, _texstring in enumerate(_texstrings):
        _command, _puretext = split_command(_texstring)
        if i == 0 and _texstrings[0]:
            # Case when the first command is a not a command but text
            result.append(_command)
            result.extend(_puretext)
            continue
        if _command:
            result.append(esc_char + _command)
        if _puretext:
            if _puretext[0] == word_delim:
                _puretext = _puretext[1:]
            result.extend(_puretext)
    return result

def split_command(texstring):
    """Splits a texstring into a command part and a pure text (as a list) part"""
    if not texstring:
        return "", []
    _puretext = []
    _command, _rest = get_first_word(texstring)
    if not _command:
        _command = texstring[0]
        _rest = texstring[1:]
    while True:
        _word, _rest = get_first_word(_rest)
        if _word:
            _puretext.append(_word)
        if _rest:
            _puretext.extend(_rest[0])
            if len(_rest) == 1:
                break
            _rest = _rest[1:]
        else:
            break
    return _command, _puretext

def get_first_word(texstring):
    _word = ""
    i = 0
    _length = len(texstring)
    if _length == 0:
        return "", ""
    if texstring[0].isalpha():
        while _length > i and texstring[i].isalpha():
            _word += texstring[i]
            i = i + 1
    elif texstring[0].isdigit():
        while _length > i and (texstring[i].isdigit()):
            _word += texstring[i]
            i = i + 1
        
    return _word, texstring[i:]

def to_list(texstring):
    """Parses the normalized tex string and returns a list. Used recursively.
    """
    result = []
    if not texstring:
        return result
    # Checking for groupings: begin_group_char...end_group_char
    before, grouping, after = group_split(texstring)
    #print before, '\n', grouping, '\n', after

    if before:
        result.extend(break_up_commands(before))
    if grouping or grouping == []:
        result.append(to_list(grouping))
    if after:
        result.extend(to_list(after))

    return result

def normalize_tex(texstring):
    """Normalizes the whole TeX expression (that is: prepares it for
    parsing)"""
    texstring = remove_comments(texstring)
    # Removing the escaped escape character (replacing it)
    texstring = texstring.replace(esc_char + esc_char, esc_char + 'backslash')
    
    # Removing the escaped scope/grouping characters
    texstring = texstring.replace(esc_char + begin_group_char, esc_char + 'lbrace')
    texstring = texstring.replace(esc_char + end_group_char, esc_char + 'rbrace')

    # Now we should have a clean expression, so we check if all the grouping
    # are OK (every begin_group_char should have a matching end_group_char)
    # TO-DO

    # Removing the escaped space-like characters. Unescaped space in TeX is
    # not important
    # Replacing all space-like characters with a single space word_delim
    texstring = word_delim.join(texstring.split())
    texstring = texstring.replace(esc_char + word_delim, esc_char + 'space'
                                    + word_delim)

    # Dealing with "syntactic sugar" goes here (i.e. '_', '^' etc.)
    texstring = texstring.replace(esc_char + '_', esc_char + 'underscore' + word_delim)
    i = texstring.find('_' + word_delim)
    if i != -1:
        raise TexParseError('Subscripting with space not allowed')
    texstring = texstring.replace('_', esc_char + 'subscript' + word_delim)

    texstring = texstring.replace(esc_char + '^', esc_char + 'circumflex' + word_delim)
    i = texstring.find('^' + word_delim)
    if i != -1:
        raise TexParseError('Superscripting with space not allowed')
    texstring = texstring.replace('^', esc_char + 'superscript' + word_delim)

    # Removing unnecessary white space
    texstring = word_delim.join(texstring.split())

    return texstring

def check_valid(parsed):
    # First we check if sub/superscripts are properly ordered
    for i in xrange(0, len(parsed)/4*4, 4):
        four = parsed[i:i+4]
        if four.count(esc_char + "superscript") > 1:
            raise TexParseError("Double superscript")
        if four.count(esc_char + "subscript") > 1:
            raise TexParseError("Double subscript")


def add_script(nucleus, scripttype, script):
    """Ads scripts to nucleus. Nucleus can be a Scripted instance already.
    scripttype is either "superscript" or "subscript", and script is the
    actual value that is going to be displayed
    """
    if not isinstance(nucleus, Scripted):
        scripted = Scripted(nucleus=nucleus)
    else:
        scripted = nucleus
    setattr(scripted, scripttype, script)
    return scripted

def is_command(item):
    try:
        return item.startswith(esc_char)
    except AttributeError:
        return False

def is_scriptcommand(s):
    return is_command(s) and (s.strip(esc_char) in scripts)

def handle_commands(texgroup):
    """Scans the entire texgroup to handle the commands. Commands may modify
    the group (elements) in some way. Example: '_' (subscript) modifies the
    element just before, and after it, etc. Should be evaluated before box
    generation. Throws an exception if the command is unknown.
    
    """
    # The reversing is needed (at least it seems so to me) because TeX
    # looks ahead when it evaluates a command, so this allows command
    # arguments to be on the "stack" when a command gets encountered
    texgroup.reverse()
    texgroup = Group(texgroup)
    result = Group([])
    i = 0
    length = len(texgroup)
    while i < length:
        item = texgroup[i]
        #print "Current item", item
        # Dirty. Needed for handling scripts; that is for setting the nucleus
        # of a Scripted element.
        # Checking if item is not \subscript or \superscript
        if isinstance(item, basestring):
            item = TexStringClass(item)
        if not (is_scriptcommand(item)):
            prev = result[-1]
            if isinstance(prev, Scripted):
                next = texgroup[i + 1]
                if not is_scriptcommand(next):
                    # We finally add a nucl. to the scripted instance
                    #print "Adding nucleus", item
                    prev.nucleus = item
                    result[-1] = prev
                    i += 1
                    continue
        # Handling of lists/Groups
        if isinstance(item, list):
            item = handle_commands(item)
            result.append(item)
            i += 1
            continue
        elif is_command(item):
            # Handling of TeX commands
            # For now only 'subscript', 'superscript' are supported, others
            # can be added as elif cases
            command = item.strip(esc_char)
            if command in ('subscript', 'superscript'):
                scripttype = command
                try:
                    script = result.pop()
                except IndexError:
                    raise TexParseError("%s just before %s"%
                                    (scripttype, end_group_char))
                if isinstance(result[-1], Scripted):
                    scripted = result.pop()
                else:
                    scripted = Scripted()
                setattr(scripted, scripttype, script)
                result.append(scripted)
                i += 1
            elif command not in tex2uni:
                raise TexParseError("Unknown command: " + command)
            else:
                result.append(item)
                i += 1
        elif item == " ":
            i += 1
        else:
            # Handling of strings and TeX symbols
            result.append(item)
            i += 1
    result.reverse()
    return result

def parse_tex(texstring):
    texstring = normalize_tex(texstring)
    #print texstring
    _parsed = to_list(texstring)
    #print _parsed
    check_valid(_parsed)
    _parsed = Group(_parsed)
    #print _parsed
    _parsed = handle_commands(_parsed)
    #print "Expression", _parsed
    return _parsed
    #~ for subelement in expression:
        #~ if isinstance(subelement, Group):
                #~ print "Child", subelement
                #~ print "Parent", subelement.parent

# Definitions of TeX commands
def math_parse_s_ft2font(s, dpi, fontsize, x, y, angle=0):
    s = s[1:-1]
    parsed = parse_tex(s)
    env = environment.copy()
    env["dpi"] = dpi
    env["fontsize"] = fontsize
    w = parsed.get_width(env)
    h = parsed.get_height(env)
    #print "Expresion width and height: ", w, h
    #w, h = 100, 100
    #~ for key in fonts:
        #~ fonts[key].set_bitmap_size(w, h)
    parsed.render(env, x, y)
    #~ fonts['mit'].set_text(s, 0)
    #~ fonts['mit'].draw_glyphs_to_bitmap()
    #_fonts = fonts.values()
    font_x_y_list = fonts.values()
    #print font_x_y_list
    return font_x_y_list
    #return w, h, _fonts

def math_get_width_height(s, dpi, fontsize, x, y, angle=0):
    s = s[1:-1]
    parsed = parse_tex(s)
    env = environment.copy()
    env["dpi"] = dpi
    env["fontsize"] = fontsize
    w = parsed.get_width(env)
    h = parsed.get_height(env)
    return w, h

def math_parse_s_ft2font1(s, dpi, fontsize, angle=0):
    """Used only for testing"""
    w, h = 100, 100
    _path = get_data_path()
    filename = os.path.join(_path, rcParams['mathtext.' + 'mit'])
    font = FT2Font(filename)
    font.set_text(s, 0)
    font.draw_glyphs_to_bitmap()
    return w, h, [font]
# Commands with side effects (setters)
# Setters are commands that set some variable in the TeX environment.
# Exapmle: '\rm' is a setter because it sets the font in the current group
# to be roman, and only from the occurence of the command to the end of
# the group. Very dirty. They should be evaluated at "runtime", that is, when
# the generation of boxes is done.
#
# Disrupter commands (disrupters). They modify the group (elements) in some
# way. Example: '_' (subscript) modifies the element just before, and
# after it, etc.
# Should be evaluated before box generation

if __name__ == '__main__':
    #texstring = r"\\{ \horse\   Hello\^ ^ a^b_c}"
    #texstring = r"  asdf { \horse{}tralala1234\ \zztop{} \ Hello\^^a^{b_c}}"
    #texstring = r"{}{} { }"
    #texstring = r"{{{_ }}}"
    #texstring = r"\horse{}"
    #texstring = r"\horse;,.?)_)(*(*^*%&$$%{} Haha! Kako je frajeru?"
    #texstring = r"a_2\trav 32"
    #texstring = r"a_24{\sum_4^5} _3"
    texstring = r"1_2^{4^5}32 5"
    parsed = parse_tex(texstring)
    
    #print is_scriptcommand('\\subscript')