import json
import time

import numpy as np

from main import network
from parsing import bag_of_words, data_loader, plot


class Classifier:
    # Klasy urzadzen
    classes = []

    def __init__(self, training_data_file, hidden_neurons_in_layers):
        """
         Znajduje i zapamietuje wystepujace w nim klasy.
         Zaklasyfikowane linie parsuje, zapamietuje i wypisuje do pliku, pozostale linie usuwa
         Tworzy słownik i wektory binarne na podstawie danych treningowych.
         :param training_data_file: nazwa treningowego pliku

        """

        self.training_data, self.test_data, self.classes, self.bag = data_loader.load_training_data(training_data_file)

        self.INPUT_L_SIZE = len(self.bag.word_set)
        self.OUTPUT_L_SIZE = len(self.classes)

        print("Classes: ", len(self.classes))

        print("Trained on {0} samples".format(len(self.training_data)))
        print("Tested on {0} samples".format(len(self.test_data)))

        sizes = [self.INPUT_L_SIZE] + hidden_neurons_in_layers + [self.OUTPUT_L_SIZE]
        self.nnetwork = network.Network(sizes)

    def train(self, epochs, mini_batch_size, alpha, test=False, s_plot=True):
        """
            Trenuje siec neuronowa na podstawie danych treningowych
            :param epochs liczba epok trenowania
            :param mini_batch_size rozmiar podzbioru danych treningowych do metody stochastycznego
                    najszybszego spadku
            :param alpha wspolczynnik uczenia - learning rate
            :param test jesli False test tylko na koncu, jesli True test wykonywany co epoke
            :param s_plot jesli True generowany jest wykres skutecznosci od epoki
        """

        start_time = time.time()
        print("Training with mini_batch_size: %s , alpha: %s," % (
            str(mini_batch_size), str(alpha)))
        if test:
            result = self.nnetwork.stochastic_gradient_descent(self.training_data, epochs, mini_batch_size, alpha,
                                                               self.test_data)
        else:
            result = self.nnetwork.stochastic_gradient_descent(self.training_data, epochs, mini_batch_size, alpha)
        print("Training time: ", time.time() - start_time)

        if s_plot:
            epochs, results = zip(*result)
            plot.show_plot(epochs, results, "Training rate: " + str(alpha))

    def classify(self, filename):
        """
            Klasyfikuje dane z pliku zrodlowego
            :param file_name: nazwa pliku zrodlowego
        """
        classify_data, documents = data_loader.load_data(self.bag, filename)

        results = self.nnetwork.classify(classify_data)

        with open('../text_files/' + filename + "-classified", 'w') as file:
            for x, y in zip(documents, results):
                print(x["sentence"])
                print(self.classes[y[0]] + "  [" + str(y[1]) + "]")
                file.write(x["sentence"] + "\n")
                file.write(self.classes[y[0]] + "  [" + str(y[1]) + "]\n")

    def test(self):
        """
            Wypisuje procent prawidlowych wynikow na podstawie danych testowych
        """
        print("Result {0:.2%}".format(self.nnetwork.evaluate(self.test_data) / len(self.test_data)))

    def save(self, filename):
        """
            Zapisuje parametry sieci do pliku
            :param filename nazwa pliku
        """
        data = {"sizes": self.nnetwork.sizes,
                "weights": [w.tolist() for w in self.nnetwork.weights],
                "biases": [b.tolist() for b in self.nnetwork.biases],
                'dictionary': list(self.bag.word_set),
                'classes': list(self.classes)}

        f = open('../text_files/' + filename, 'w')
        json.dump(data, f, indent=4, sort_keys=True)
        f.close()

    def load(self, filename):
        """
             Laduje z pliku .json parametry sieci
             :param filename: nazwa pliku zrodlowego typu .json
        """
        with open('../text_files/' + filename, 'r') as file:
            data = json.load(file)
            weights = [np.array(w) for w in data["weights"]]
            biases = [np.array(b) for b in data["biases"]]
            self.classes = np.asarray(data['classes'])
            print("\nClasses: ", self.classes)
            dictionary = np.asarray(data['dictionary'])
            self.bag = bag_of_words.BagOfWords(dictionary)
            sizes = data["sizes"]

            self.nnetwork = network.Network(sizes)
            self.nnetwork.weights = weights
            self.nnetwork.biases = biases


"""
    Tworzony jest klasyfikator, pierwszy parametr to nazwa pliku z danymi treningowymi, drugi parametr to liczba 
    neuronow w poszczegolnych warstwach ukrytnych (np. [15, 20] - w pierwszej warstwie ukryten 15 neuronow, 20 w
    drugiej
"""
classifier = Classifier("dict2017-5-annotated.txt", [15])
"""
    Trenowanie 
"""
classifier.train(300, 300, 5.0, True)

"""
    Klasyfikacja podanego pliku
"""
classifier.classify("dict2015-5.txt")

"""
    Zapisywanie i wczytywanie parametrow sieci
"""
# classifier.save("result.json")

# classifier.load("result.json")

