from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.utils import np_utils
import numpy as np
import database

class classification():
    def __init__(self):
        self.model = Sequential()

    def train_with(self, training_data_list, answers):
        #put data in right format
        answers = self.hot_encode_classification(answers)
        training_data = np.array(training_data_list)

        #simple network
        #16 hidden units for the GPU
        training_feature_shape = len(training_data_list[0])
        self.model.add(Dense(16, input_shape = (training_feature_shape, )) )
        self.model.add(Activation('sigmoid'))
        #for the 2 classification outputs
        self.model.add(Dense(2))
        self.model.add(Activation('softmax'))
        self.model.compile(loss='categorical_crossentropy', optimizer='adam')

        #fit model
        self.model.fit(training_data, answers, verbose = 0, batch_size = 1)

    def predict(self, testing_data):

        if testing_data is not False and self.model:
            testing = np.array(testing_data)
            return self.model.predict(testing).tolist()

        else:
            #for debugging
            return False

    '''
    list of values, where in input, 1_d array with binary encoding ( 0 is not-occupied and 1 is occupied)

    output: hot encoding where the place 0 indicates occupancy, and the space of 1 indicates non-occupancy.  Example: occupied day = [1, 0], not occupied day = [0,1]  There should be no values of [0,0] or [1, 1]
    '''
    def hot_encode_classification(self, classification_list):
        final = np.zeros((len(classification_list), 2))

        for x, classification in enumerate(classification_list):
            if classification == 0: #not occupied
                final[x, 1] = 1
            elif classification == 1: #occupied
                final[x, 0] = 1
        return final
'''
TESTING
expecting binary inputs from prediction and classification
0 is false
1 is true
'''

'''
the following methods takes a dictionary result of test_prediction to calculate measuring
values

both prediction and classification will be lists.  However, predictions are hot-encoded, so they are lists of lists
'''
class results():
    def __init__(self, prediction, classification):
        self.true_true = 0
        self.true_false = 0
        self.false_true = 0
        self.false_false = 0

        self.prediction = prediction
        self.classification = classification

        self.make_results()
        self.make_precision()
        self.make_recall()
        self.make_fOne()
        self.make_correctovertotal()


    def make_results(self):

        if len(self.prediction) != len(self.classification):
            print "prediction and classification inputs are not the same size"
            self.results = None
        for i in range (0, len(self.prediction)):
            #analyze prediction hot encoding, where 0 is occupied, and 1 is not occupied
            if self.prediction[i][0] > self.prediction[i][1]:
                #this was mostly an occupied prediction
                this_prediction = 1
            elif self.prediction[i][1] > self.prediction[i][0]:
                #this was most a non-occupied prediction
                this_prediction = 0
            else: #they might as well be equal
                this_predition = 0 #defer to not-occupied

            if this_prediction == self.classification[i]:
                if this_prediction == 1:
                    self.true_true += 1
                else:
                    self.true_false += 1
            else:
                if this_prediction == 1:
                    self.false_true += 1
                else:
                    self.false_false += 1

    #how many of the selected items are relevant (how good was it at guessing occupancy)
    #true_truths/ (total number of truths guessed = "true truths)
    #Of those that I guess to be X, how many were actually X

    def make_precision(self):
        try:
            self.occupancy_precision = float(self.true_true)/(self.true_true + self.false_true)
        except Exception as e:
            #most likely float by zero error
            self.occupancy_precision = None
        try:
            self.empty_precision = float(self.true_false)/(self.true_false + self.false_false)
        except Exception as e:
            self.empty_precision = None


    def make_recall(self):
    #how many relevant items were selected
    #true_truths/ total_truths
    #Of all the X's in the sample, how many did I find?
        try:
            self.occupancy_recall = float(self.true_true)/(self.true_true + self.false_false)
        except Exception as e:
            self.occupancy_recall = None
        try:
            self.empty_recall = float(self.true_false)/(self.true_false + self.false_true)
        except Exception as e:
            self.empty_recall = None

    def make_fOne(self):
        try:
            self.occupancy_fOne = 2 * float(self.occupancy_precision * self.occupancy_recall)/(self.occupancy_precision + self.occupancy_recall)
        except Exception as e:
            self.occupancy_fOne = None
        try:
            self.empty_fOne = 2 * float(self.empty_precision * self.empty_recall)/(self.empty_precision + self.empty_recall)
        except Exception as e:
            self.empty_fOne = None

    def make_correctovertotal(self):
        correct = self.true_true + self.true_false
        total = correct + self.false_true + self.false_false

        try:
            self.overall = float(correct)/total
        except Exception as e:
            print "no data was given to test with, it looks like..."
            self.overall = None

    def get_results(self):

        return {"true_true": self.true_true, "true_false": self.true_false, "false_false": self.false_false, "false_true": self.false_true, "occupancy_precision": self.occupancy_precision, "empty_precision": self.empty_precision, "correct_overall": self.overall, "occupancy_recall": self.occupancy_recall, "empty_recall": self.empty_recall, "occupancy_fOne": self.occupancy_fOne, "empty_fOne": self.empty_fOne}


def save_to_database(table_name, experiment_name, city_name, full_dict):
    thesis_data = database.database("Thesis")

    #delete similar entries
    query = "DELETE FROM `" + table_name + "` WHERE `city` = '" + city_name + "' AND `experiment` = '" + experiment_name + "';"
    #print query
    thesis_data.execute(query)

    print "saving to database " + table_name + " experiment results: " + experiment_name

    #put entries in, then the keys are lists and what I want to store are the true_true,
    if full_dict and isinstance(full_dict.keys()[0], long):
        insert_query = "INSERT INTO " + table_name + "  VALUES('%s','%s',%s,'%s',%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
        for listing_id, full_records in full_dict.iteritems():
            #experiment ,  city , listing_id, method, true_true, true_false, false_true, false_false, occupancy_precision, occupancy_recall, empty_precision, empty_recall, occupancy_fOne, empty_fOne, correct_overall
            for method, method_results in full_records.iteritems():
                to_insert = [experiment_name, city_name, listing_id, method]
                for this_thing in ["true_true", "true_false", "false_true", "false_false", "occupancy_precision", "occupancy_recall", "empty_precision", "empty_recall", "occupancy_fOne", "empty_fOne", "correct_overall"]:
                    if method_results[this_thing]:
                        to_insert.append(method_results[this_thing])
                    else:
                        to_insert.append("null")
                #print (insert_query % to_insert)
                thesis_data.execute(insert_query % tuple(to_insert))
    elif full_dict:
        insert_query = "INSERT INTO " + table_name + " VALUES('%s','%s','%s',%s,%s,%s,%s,%s, %s)"
        #experiment ,  city ,  method, occupancy_precision, occupancy_recall, empty_precision, empty_recall, occupancy_fOne, empty_fOne,
        for method, method_results in full_dict.iteritems():
            to_insert = [experiment_name, city_name, method]
            for this_thing in ["occupancy_precision", "occupancy_recall", "empty_precision", "empty_recall", "occupancy_fOne", "empty_fOne"]:
                if method_results[this_thing]:
                    to_insert.append(method_results[this_thing])
                else:
                    to_insert.append("null")

            thesis_data.execute(insert_query % tuple(to_insert))

    thesis_data.destroy_connection()





