import string
from otp.otpbase import OTPLocalizer
from direct.directnotify import DirectNotifyGlobal
from pandac.PandaModules import NSError
from pandac.PandaModules import TextEncoder, TextNode
notify = DirectNotifyGlobal.directNotify.newCategory('NameCheck')

def filterString(str, filter):
    result = ''
    for char in str:
        if char in filter:
            result = result + char

    return result


def justLetters(str):
    letters = ''
    for c in str:
        if c.isalpha():
            letters = letters + c

    return letters


def justUpper(str):
    upperCaseLetters = ''
    for c in str:
        if c.upper() != c.lower():
            if c == c.upper():
                upperCaseLetters = upperCaseLetters + c

    return upperCaseLetters


def wordList(str):
    words = str.split()
    result = []
    for word in words:
        subWords = word.split('-')
        for sw in subWords:
            if sw:
                result.append(sw)

    return result


def checkName(name, otherCheckFuncs = [], font = None):

    def longEnough(name):
        if len(name) < 2:
            notify.info('name is too short')
            return OTPLocalizer.NCTooShort

    def emptyName(name):
        if name.strip() == '':
            notify.info('name is empty')
            return OTPLocalizer.NCTooShort

    def printableChars(name):
        for char in name:
            if ord(char) < 128 and char not in string.printable:
                notify.info('name contains non-printable char #%s' % ord(char))
                return OTPLocalizer.NCGeneric

    validAsciiChars = set(".,'-" + string.letters + string.whitespace)

    def _validCharacter(c, validAsciiChars = validAsciiChars, font = font):
        if c in validAsciiChars:
            return True
        if c.isalpha() or c.isspace():
            return True
        return False

    def badCharacters(name, _validCharacter = _validCharacter):
        for char in name:
            if not _validCharacter(char):
                if char in string.digits:
                    notify.info('name contains digits')
                    return OTPLocalizer.NCNoDigits
                else:
                    notify.info('name contains bad char: %s' % TextEncoder().encodeWtext(char))
                    return OTPLocalizer.NCBadCharacter % TextEncoder().encodeWtext(char)

    def fontHasCharacters(name, font = font):
        if font:
            tn = TextNode('NameCheck')
            tn.setFont(font)
            for c in name:
                if not tn.hasCharacter(c.decode('utf-8')):
                    notify.info('name contains bad char: %s' % TextEncoder().encodeWtext(c))
                    return OTPLocalizer.NCBadCharacter % TextEncoder().encodeWtext(c)

    def hasLetters(name):
        words = wordList(name)
        for word in words:
            letters = justLetters(word)
            if len(letters) == 0:
                notify.info('word "%s" has no letters' % TextEncoder().encodeWtext(word))
                return OTPLocalizer.NCNeedLetters

    def hasVowels(name):

        def perWord(word):
            if '.' in word:
                return None
            for char in word:
                if ord(char) >= 128:
                    return None

            letters = filterString(word, string.letters)
            if len(letters) > 2:
                vowels = filterString(letters, 'aeiouyAEIOUY')
                if len(vowels) == 0:
                    notify.info('word "%s" has no vowels' % TextEncoder().encodeWtext(word))
                    return OTPLocalizer.NCNeedVowels
            return None

        for word in wordList(name):
            problem = perWord(word)
            if problem:
                return problem

    def monoLetter(name):

        def perWord(word):
            word = word
            letters = justLetters(word)
            if len(letters) > 2:
                letters = TextEncoder().decodeText(TextEncoder.lower(TextEncoder().encodeWtext(letters)))
                filtered = filterString(letters, letters[0])
                if filtered == letters:
                    notify.info('word "%s" uses only one letter' % TextEncoder().encodeWtext(word))
                    return OTPLocalizer.NCGeneric

        for word in wordList(name):
            problem = perWord(word)
            if problem:
                return problem

    def checkDashes(name):
        def validDash(index, name=name):
            if index == 0 or i == len(name)-1:
                return 0
            if not name[i-1].isalpha():
                return 0
            if not name[i+1].isalpha():
                return 0
            return 1

        i=0
        while 1:
            i = name.find('-', i, len(name))
            if i < 0:
                return None
            if not validDash(i):
                notify.info('name makes invalid use of dashes')
                return OTPLocalizer.NCDashUsage
            i += 1

    def checkCommas(name):
        def validComma(index, name=name):
            if index == 0 or i == len(name)-1:
                return OTPLocalizer.NCCommaEdge
            if name[i-1].isspace():
                return OTPLocalizer.NCCommaAfterWord
            if not name[i+1].isspace():
                return OTPLocalizer.NCCommaUsage
            return None

        i=0
        while 1:
            i = name.find(',', i, len(name))
            if i < 0:
                return None
            problem = validComma(i)
            if problem:
                notify.info('name makes invalid use of commas')
                return problem
            i += 1

    def checkPeriods(name):
        words = wordList(name)
        for word in words:
            if word[-1] == ',':
                word = word[:-1]
            numPeriods = word.count('.')
            if not numPeriods:
                continue
            letters = justLetters(word)
            numLetters = len(letters)
            if word[-1] != '.':
                notify.info('word "%s" does not end in a period' % TextEncoder().encodeWtext(word))
                return OTPLocalizer.NCPeriodUsage
            if numPeriods > 2:
                notify.info('word "%s" has too many periods' % TextEncoder().encodeWtext(word))
                return OTPLocalizer.NCPeriodUsage
            if numPeriods == 2:
                if not (word[1] == '.' and word[3] == '.'):
                    notify.info('word "%s" does not fit the J.T. pattern' % TextEncoder().encodeWtext(word))
                    return OTPLocalizer.NCPeriodUsage

        return None

    def checkApostrophes(name):
        words = wordList(name)
        for word in words:
            numApos = word.count("'")
            if numApos > 2:
                notify.info('word "%s" has too many apostrophes.' % TextEncoder().encodeWtext(word))
                return OTPLocalizer.NCApostrophes

        numApos = name.count("'")
        if numApos > 3:
            notify.info('name has too many apostrophes.')
            return OTPLocalizer.NCApostrophes

    def tooManyWords(name):
        if len(wordList(name)) > 4:
            notify.info('name has too many words')
            return OTPLocalizer.NCTooManyWords

    def allCaps(name):
        letters = justLetters(name)
        if len(letters) > 2:
            upperLetters = TextEncoder().decodeText(TextEncoder.upper(TextEncoder().encodeWtext(letters)))
            for i in xrange(len(upperLetters)):
                if not upperLetters[0].isupper():
                    return

            if upperLetters == letters:
                notify.info('name is all caps')
                return OTPLocalizer.NCAllCaps

    def mixedCase(name):
        words = wordList(name)
        for word in words:
            if len(word) > 2:
                capitals = justUpper(word)
                if len(capitals) > 2:
                    notify.info('name has mixed case')
                    return OTPLocalizer.NCMixedCase

    def checkJapanese(name):
        asciiSpace = range(32, 33)
        asciiDigits = range(48, 64)
        hiragana = range(12353, 12448)
        katakana = range(12449, 12544)
        halfwidthKatakana = range(65381, 65440)
        halfwidthCharacter = set(asciiSpace + halfwidthKatakana)
        allowedUtf8 = set(asciiSpace + hiragana + katakana + halfwidthKatakana)

        te = TextEncoder()
        dc = 0.0

        for char in (ord(char) for char in te.decodeText(name)):
            if char not in allowedUtf8:
                if char in asciiDigits:
                    notify.info('name contains not allowed ascii digits')
                    return OTPLocalizer.NCNoDigits
                else:
                    notify.info('name contains not allowed utf8 char: 0x%04x' % char)
                    return OTPLocalizer.NCBadCharacter % te.encodeWtext(unichr(char))
            elif char in halfwidthCharacter:
                dc += 0.5
            else:
                dc += 1

        if dc < 2:
            notify.info('name is too short: %0.1f' % dc)
            return OTPLocalizer.NCTooShort
        elif dc > 8:
            notify.info('name has been occupied more than eight display cells: %0.1f' % dc)
            return OTPLocalizer.NCGeneric

    def repeatedChars(name):
        count = 1
        lastChar = None
        i = 0
        while i < len(name):
            char = name[i]
            i += 1
            if char == lastChar:
                count += 1
            else:
                count = 1
            lastChar = char
            if count > 2:
                notify.info('character %s is repeated too many times' % TextEncoder().encodeWtext(char))
                return OTPLocalizer.NCRepeatedChar % TextEncoder().encodeWtext(char)

        return

    checks = [printableChars,
     badCharacters,
     fontHasCharacters,
     longEnough,
     emptyName,
     hasLetters,
     hasVowels,
     monoLetter,
     checkDashes,
     checkCommas,
     checkPeriods,
     checkApostrophes,
     tooManyWords,
     allCaps,
     mixedCase,
     repeatedChars] + otherCheckFuncs
    symmetricChecks = []
    name = TextEncoder().decodeText(name)
    notify.info('checking name "%s"...' % TextEncoder().encodeWtext(name))
    for check in checks:
        problem = check(name[:])
        if not problem and check in symmetricChecks:
            nName = name[:]
            bName.reverse()
            problem = check(bName)
            print 'problem = %s' % problem
        if problem:
            return problem

    return None


severity = notify.getSeverity()
notify.setSeverity(NSError)
for i in xrange(32):
    pass

for c in '!"#$%&()*+/:;<=>?@[\\]^_`{|}~':
    pass

notify.setSeverity(severity)
del severity