# -*- coding: utf-8 -*-

#
# Copyright (c) 2009, 2010 Jack Kaliko <efrim@azylum.org> 
#
#  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 3 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, see <http://www.gnu.org/licenses/>.
#

"""
SimaStr()

Special unicode() subclass to perform fuzzy match on specific strings with
known noise.

Artist names often contain a leading 'The ' which might, or might not be
present. Some other noise sources in artist name are 'and' words :
    'and'/'&'/'n'/'N'.

The SimaStr() object removes these words and compute equality on "stripped"
strings.

>>> from simastr import SimaStr
>>> art0 = SimaStr(u'The Desert Sessions & PJ Harvey')
>>> art1 = SimaStr(u'Desert Sessions And PJ Harvey')
>>> art0 == art1
>>> True
>>> art0 == u'Desert Sessions And PJ Harvey'
>>> True
>>>

Current stripped word patterns (usually English followed by French andx
Spanish alternatives)
    leading (case-insensitive):
            "the","le","la","les","el","los"
    middle:
            "[Aa]nd","&","[Nn]'?","[Ee]t"
    trailing:
            combination of "[- !?\.]+" "\(? ?[Ll]ive ?\)?"


Possibility to access to stripped string :

>>> art0 = SimaStr(u'The Desert Sessions & PJ Harvey')
>>> art.stripped
>>> print (art0, art0.stripped)
>>> (u'The Desert Sessions & PJ Harvey', u'Desert Sessions PJ Harvey')

TODO:
    * Have a look to difflib.SequenceMatcher to find possible improvements
    * Find a way to allow users patterns.
"""

__revison__ = u'$Revision$'[11:-2]
__author__ = u'$Author: kaliko $'[9:-2]
__version__ = u'0.2'

# IMPORTS
from re import (compile, U, I)


class SimaStr(unicode):
    """
    Specific string object for artist names and song titles.
    Here follows some class variables for regex to run on strings.
    """
    regexp_dict = dict()

    # Leading patterns: The Le Les
    # case-insensitive matching for this RE
    regexp_dict.update({'lead': '(the|l[ae][s]?|los|el)'})

    # Middle patterns: And & Et N
    regexp_dict.update({'mid': '(And|&|and|[Nn]\'?|et)'})

    # Trailing patterns: ! ? live
    # TODO: add "concert" key word
    #       add "Live at <somewhere>"
    regexp_dict.update({'trail': '([- !?\.]|\(? ?[Ll]ive ?\)?)'})

    reg_lead = compile('^(?P<lead>%(lead)s )(?P<root0>.*)$' % regexp_dict, I | U)
    reg_midd = compile('^(?P<root0>.*)(?P<mid> %(mid)s )(?P<root1>.*)' % regexp_dict, U)
    reg_trail = compile('^(?P<root0>.*?)(?P<trail>%(trail)s+$)' % regexp_dict, U)

    def __init__(self, fuzzstr):
        """
        """
        unicode().__init__(fuzzstr)
        self.orig = unicode(fuzzstr)
        self.stripped = unicode(fuzzstr.strip())
        # TODO: remove self.lastcomp
        #self.lastcomp = str()
        # fuzzy computation
        self._get_root()

    def _get_root(self):
        """
        Remove all patterns in string.
        """
        sea = SimaStr.reg_lead.search(self.stripped)
        if sea:
            #print sea.groupdict()
            self.stripped = sea.group('root0')

        sea = SimaStr.reg_midd.search(self.stripped)
        if sea:
            #print sea.groupdict()
            self.stripped = unicode().join([sea.group('root0'), ' ',
                                        sea.group('root1')])

        sea = SimaStr.reg_trail.search(self.stripped)
        if sea:
            #print sea.groupdict()
            self.stripped = sea.group('root0')

    def __hash__(self):
        return hash(self.stripped)

    def __eq__(self, other):
        #self.lastcomp = other
        if not isinstance(other, SimaStr):
            return hash(self) == hash(SimaStr(other))
        return hash(self) == hash(other)

    def __ne__(self, other):
        #self.lastcomp = other
        if not isinstance(other, SimaStr):
            return hash(self) != hash(SimaStr(other))
        return hash(self) != hash(other)


# Script starts here
if __name__ == "__main__":
    import time
    print SimaStr(u'Kétanoue')
    #from leven import levenshtein_ratio
    CASES
    CASES_LIST = list([
        dict({
                    'got': u'Guns N\' Roses (lize)!! !',
                'look for': u'Guns And Roses'}),
        dict({
                     'got': u'Jesus & Mary Chains',
                'look for': u'The Jesus and Mary Chains - live'}),
        dict({
                         'got': u'Desert Sessions',
                    'look for': u'The Desert Sessions'}),
        dict({
                         'got': u'Têtes Raides',
                    'look for': u'Les Têtes Raides'}),
        dict({
                         'got': u'Noir Désir',
                    'look for': u'Noir Désir'}),
        dict({
                         'got': u'No Future',
                    'look for': u'Future'})])

    for case in CASES_LIST[:]:
        str0 = case.get('got')
        str1 = case.get('look for')
        fz_str0 = SimaStr(str0)
        fz_str1 = SimaStr(str1)
        print fz_str0, '\n', fz_str1
        print fz_str0.stripped == fz_str1.stripped
        #print levenshtein_ratio(fz_str0.lower(), fz_str1.lower())
        time.sleep(1)

# VIM MODLINE
# vim: ai ts=4 sw=4 sts=4 expandtab
