#!/usr/bin/python
# -*- coding: cp1252 -*-
import os, sys, distutils.file_util
from backprop import *

def ScanDirectory( Path ):
    '''
    Gibt Liste der Verzeichnisse in "Path", Liste der Textdateien in "Path"
    und Liste der Textdateien in Unterverzeichnissen zurück.
    '''
    Dirs, RootFiles, DirFiles = [], [], []
    for FileName in os.listdir(Path):
        if (FileName[-5:].find('.') == -1):       
            Dirs.append(FileName)
        elif (FileName[-4:] == '.txt') and (os.path.getsize(FileName)>500):
            RootFiles.append(FileName)
    for Dir in Dirs:
        for FileName in os.listdir(os.path.join(Path,Dir)):
            if (FileName[-4:] == '.txt') and \
               (os.path.getsize(os.path.join(Path,Dir,FileName)) > 500):
                DirFiles.append(os.path.join(Dir,FileName))
    return (Dirs, RootFiles, DirFiles)

def GetFileText( Path ):
    '''
    Öffnet Textdatei, gibt Text als Unicode-String zurück (Kleinbuchstaben)
    '''
    f = open(Path, 'r')
    try:
        Text = unicode(f.read(),'iso-8859-1')
    except UnicodeDecodeError:
        Text = unicode(f.read(),'us-ascii')
    Text = Text.lower()
    f.close()
    return Text

def GetCharCount( Text, IgnoreChars = ' \n\'"_,.-/1234567890`' ):
    '''
    Gibt Dictionary-Objekt mit den verschiedenen Zeichen des Textes als
    Keys und der Anzahl des Vorkommens als Values zurück.
    '''
    Chars = {}
    for Char in Text:
        if Char not in IgnoreChars:
            if Chars.has_key(Char):
                Chars[Char] += 1
            else:
                Chars[Char]  = 1
    return Chars

def GetCharStats( FileList, Path ):
    '''
    Erstellt CharStats (ein Dict-Objekt mit Dateinamen als Keys,
    GetCharCount-Dicts als Values) und CharNums (Dict-Objekt mit
    Dateinamen als Keys, Gesamtzahl Zeichen der Datei als Values).
    '''
    CharStats, CharNums = {}, {}
    print "Text-Dateien einlesen ..."
    for FileName in FileList:
        print unicode(FileName,'iso-8859-1')
        FileText = GetFileText(os.path.join(Path,FileName))
        CharNums[FileName]  = len(FileText)
        if (CharNums[FileName] == 0): CharNums[FileName] = 1
        CharStats[FileName] = GetCharCount(FileText)
    return (CharStats, CharNums)

def GetTopChars( CharStats, n=25 ):
    '''
    Erstellt Liste der n am häufigsten vorkommenden Zeichen in CharStats.
    '''
    # CharStats in AllChars-Dictionary aufsummieren.
    AllChars = {}
    for (FileName, CharDict) in CharStats.items():
        for (Char, Count) in CharDict.items():
            if AllChars.has_key(Char):
                AllChars[Char] += Count
            else:
                AllChars[Char] = Count
    # In Liste umwandeln, nach Anzahl Zeichen sortieren,
    # die ersten n Zeichen (ohne Anzahl) zurückgeben.
    TopChars = [(Count, Char) for (Char, Count) in AllChars.items()]
    TopChars.sort(reverse=1)
    TopChars = [Char for (Count, Char) in TopChars][:n]
    return TopChars

def CleanCharStats( CharStats, TopChars ):
    '''
    Entfernt nicht in TopChars vorkommende Zeichen aus CharStats,
    ergänzt Zeichen, die in TopChars aber nicht in CharStats vorkommen.
    '''
    for (FileName, CharDict) in CharStats.items():
        for (Char, Count) in CharDict.items():
            if Char not in TopChars:
                del CharDict[Char]
        for TopChar in TopChars:
            if TopChar not in [Char for (Char, Count) in CharDict.items()]:
                CharDict[TopChar] = 0.0
    return CharStats

def GetNNInputs(FileList, CharStats, CharNums ):
    '''
    Erstellt Liste mit den Eingaben (Anteil pro Zeichens am Text) für
    ein neuronales Netz, das Dateien in FileList kategorisieren soll.
    '''
    NNInputs  = []
    for FileName in FileList:
        CharItems = CharStats[FileName].items()
        CharItems.sort()
        NNInputs.append([float(Count)/CharNums[FileName] \
                         for (Char, Count) in CharItems])
    return NNInputs

def GetNNOutputs( FileList, CategoryNames ):
    '''
    Erstellt eine Liste mit den Ausgaben (z.B. bei Kategorie 1: [1,0,0,0])
    für ein neuronales Netz, das die Dateien in FileList trainieren soll.
    '''
    NNOutputs = []
    for FileName in FileList:
        TempOut = [0.0] * len(CategoryNames)
        TempOut[CategoryNames.index(FileName[:FileName.find(os.sep)])] =1.0
        NNOutputs.append(TempOut)
    return NNOutputs

def ScaleInputs( Inputs ):
    '''
    Skaliert jeden Faktor der Inputs-Listen auf den Bereich [-1;1].
    '''
    for m in range(len(Inputs[0])):
        Min = 10**10
        Max = -10**10
        for n in range(len(Inputs)):
            if Inputs[n][m] > Max: Max = Inputs[n][m]
            if Inputs[n][m] < Min: Min = Inputs[n][m]
        for n in range(len(Inputs)):
            if (Min != Max):
                Inputs[n][m] = 2*Inputs[n][m]/(Max-Min) - Min*2/(Max-Min)-1
    return Inputs

def main():

    # Ausgangsverzeichnis: Verzeichnis, in dem Programm ausgeführt wird.
    RootPath = sys.path[2]

    # Auf Unterverzeichnisse, sortierte, unsortierte Dateien untersuchen.
    (CategoryNames, UnsortedFiles, SortedFiles) = ScanDirectory(RootPath)
    if (UnsortedFiles == []) or (SortedFiles == []):
        print unicode('Sortierte und unsortierte Textdateien nötig.', \
                      'iso-8859-1')
        sys.exit()
    
    # Häufigkeit der Zeichen in Textdateien analysieren.
    (CharStats,CharNums) = GetCharStats(SortedFiles+UnsortedFiles,RootPath)
    TopChars = GetTopChars(CharStats, 25)
    CharStats = CleanCharStats(CharStats, TopChars)

    # Ein- und Ausgaben für Training des neuronalen Netzes erstellen,
    # Eingaben für Anwendung des neuronalen Netzes.
    NNOutputsTrain = GetNNOutputs(SortedFiles, CategoryNames)
    NNInputsTrain  = GetNNInputs(SortedFiles, CharStats, CharNums)
    NNInputsReal   = GetNNInputs(UnsortedFiles, CharStats, CharNums)
    
    # Trainings- und Anwendungseingaben skalieren.
    NNInputsTrain  = ScaleInputs(NNInputsTrain)
    NNInputsReal   = ScaleInputs(NNInputsReal)

    # Neuronales Netz erstellen: 25 Eingabeneuronen, 10 Neuronen in der
    # Hidden Layer, so viele Ausgabeneuronen wie Sprach-Kategorien.
    LNet = Net([25,10,len(CategoryNames)],0.5,0.8,0.3)

    print '\nNeuronales Netz trainieren ...'
    print 'Epoche'.ljust(14), 'Fehler'.ljust(14)
    Error = 1.0; n = 0

    # Neuronales Netz trainieren bis Fehler =< 0.01,
    # alle 10 Epochen Fehler ausgeben, maximal 5000 Epochen.
    while (Error > 0.01) and (n < 500):
        n+=1
        Error = LNet.Train(NNInputsTrain, NNOutputsTrain, 10)
        print str(n*10).ljust(14), str(round(Error,8)).ljust(14)

    if (n == 500):
        print 'In 5000 Epochen keine Konvergenz erreicht.'
        sys.exit()
    else:
        print 'Neuronales Netz in %s Epochen erfolgreich trainiert.'%(n*10)

    print '\nKategorisierung der unsortierten Dateien:\n'

    # Neuronales Netz auf unsortierte Dateien anwenden. Dict-Objekt mit
    # eindeutig zugeordneten Kategorien erstellen.
    CatUnsortedFiles = {}
    for (NNInput, FileName) in zip(NNInputsReal, UnsortedFiles):
        NNOutput = LNet.Forward(NNInput)
        c = NNOutput.index(max(NNOutput))
        print '\n%s:' % FileName,
        print unicode('%s (%s)' % (CategoryNames[c], \
                                   round(NNOutput[c],4)),'iso-8859-1')
        if (NNOutput[c] > 0.7):
            CatUnsortedFiles[FileName] = CategoryNames[c]

    # Dateien ggf. verschieben / kopieren.
    SortJN = '-'
    while (SortJN not in 'jn'):
        SortJN = raw_input('\nText-Dateien in Ordner verschieben (j/n)? ')
        SortJN = SortJN.lower()
    if (SortJN == 'j'):
        for (FileName, Cat) in CatUnsortedFiles.items():
            distutils.file_util.move_file(os.path.join(RootPath,FileName),\
                                          os.path.join(RootPath,Cat))

main()
