#!/usr/bin/env python3

"""Aggregator.py: Aggregation classifier"""
__author__      = "Jan Polisenky, Radek Hranicky"


from classifiers.Classifier import Classifier

import json
import re
import datetime



class Aggregator(Classifier):

    def __init__(self):
        """
        ! Constructor of the Lexical classifier class
        """
        super().__init__()
        self.name = "aggregator"
        self.external_requires = [ ]
        self.external_wants = []
        self.internal_data_parsed = []
        self.verbose = True

        self.classifier_wants = ['data', 'svm']
        self.classifier_requires = ['lexical']
        self.final = True # This classifier provides the final result!

    def __validate_and_parse(self, internal_data):

        svm = {"badness": 0.0, "accuracy": 0.0, "explanation": [], 'is_available': False}
        data = {"badness": 0.0, "accuracy": 0.0, "explanation": [], 'is_available': False}
        lexical = {"badness": 0.0, "accuracy": 0.0, "explanation": [], 'is_available': False}





        svm_data, data_data, lexical_data = None, None, None

        try:
            svm_data = internal_data['svm']

        except Exception as e:
            if self.verbose:
                print("[Aggregator warning]: No SVM data")

        try:
            lexical_data = internal_data['lexical']


        except Exception as e:
            if self.verbose:
                print("[Aggregator fatal error]: No lexical data")
            return self.err_handler("Missing data from dependency classifiers")

        try:
            data_data = internal_data['data']

        except Exception as e:
            if self.verbose:
                print("[Aggregator warning]: No Data model output")



        preprocessed_data = [svm_data, data_data, lexical_data]


        for data_block in preprocessed_data:

            if data_block is None:
                continue

            if data_block["classifier_name"] == "lexical":
                if data_block['success'] is False:
                    print("[Aggregator fatal error]: No lexical data, giving up...")
                    return self.err_handler("Agregator: Lexical classifier returned error")

                lexical['badness'] = data_block['badness']
                lexical["accuracy"] = data_block["accuracy"]
                lexical["explanation"] = data_block["explanation"]
                lexical['is_available'] = True

            # SVM classifier
            try:
                if data_block["classifier_name"] == "svm":
                    if data_block['success'] is False:
                        svm['is_available'] = False
                        continue

                    svm['badness'] = data_block['badness']
                    svm['accuracy'] = data_block['accuracy']
                    svm['explanation'] = data_block['explanation']
                    svm['is_available'] = True
            except:
                svm['is_available'] = False


            # Data classifier
            try:
                if data_block["classifier_name"] == "data":
                    if data_block['success'] is False:
                        svm['is_available'] = False
                        continue

                    data["accuracy"] = data_block["accuracy"]
                    data["badness"] = data_block["badness"]
                    data["explanation"] = data_block["explanation"]
                    data['is_available'] = True
            except:
                data['is_available'] = False

        self.internal_data_parsed = {'svm': svm, 'data': data, 'lexical': lexical}


        return True
    


    def __aggregate(self, internal_data_parsed):
        """
        ! Aggregate the data from the classifiers
        @param lex_data Data from the lexical classifier
        @param svm_data Data from the svm classifier
        @param data_data Data from the data classifier
        @return Returns the classification output

        """


        # If there is no data from the SVM classifier or data classifier, use only the lexical classifier

        if internal_data_parsed['svm']['is_available'] is False and internal_data_parsed['data']['is_available'] is False:
            return {"prediction": internal_data_parsed['lexical']['badness'], "accuracy": 1.0, "explanation": ["Used only lexical model"], "lexical": internal_data_parsed['lexical']['badness']}





        # Data and SVM classifier are only optional #
        svm = False
        data = False
        lexical = internal_data_parsed['lexical']['badness']
        #############################################


        ### Static data weights ###
        grey_zone_width = 0.1

        # SVM + DATA = 1.0
        lexical_weight = 0.4
        data_weight = 0.6
        self.accuracy = 1.0

        # BIAS of SVM
        svm_weight = 0.3
        ###########################







        if internal_data_parsed['svm']['is_available'] is True:
            svm = internal_data_parsed['svm']['badness']

        if internal_data_parsed['data']['is_available'] is True:
            data = internal_data_parsed['data']['badness']

        if float(lexical) < 0.5:
            svm = 0.0
                 
            


        # Weighting the data if there is missing some classifiers #
        if svm is False:
            data_weight = 1.0
            svm_weight = 0.0
            self.accuracy = 0.8

        if data is False:
            data_weight = 0.0
            svm_weight = 1.0
            self.accuracy = 0.4

        ###########################################################

        prediction = float((data_weight*data + lexical_weight*lexical) / (data_weight+lexical_weight))


        if svm > (1 - grey_zone_width):
            if prediction > 0.5:
                self.accuracy = (self.accuracy+1)/2
                prediction = prediction*(3/4)+1/4
            else:
                prediction+=svm_weight
                self.accuracy = (self.accuracy+0.5)/2

        elif svm < (0.5 - grey_zone_width):
            if prediction < 0.5:
                self.accuracy = (self.accuracy+1)/2
                prediction = prediction*(3/4)
            else:
                prediction-=svm_weight
                self.accuracy = (self.accuracy+0.5)/2

                # Correction of bad results of prediction #
        if prediction > 1:
            prediction = 1.00
        elif prediction < 0:
            prediction = 0.00


        explanation = []
        if internal_data_parsed['svm']['is_available'] is False:
            explanation.append("no SVM-Classifier result")

        if internal_data_parsed['data']['is_available'] is False:
            explanation.append("no Data-Classifier results")


        return {"prediction": prediction, "accuracy": self.accuracy, "explanation": explanation, 'lexical': lexical}


    def classify(self, domain_name, internal_data, external_data):
        """
        ! Perform the classifion of the given domain_name
        @param domain_name Domain name to classify, e.g. 'fit.vut.cz'
        @param internal_data Dictionary of dependency classifiers' outputs
        @param external_data Dictionary of external inputs, e.g.
        @return Returns the classification output
        """

        self.__validate_and_parse(internal_data)

        result = self.__aggregate(self.internal_data_parsed)

        badness = result['prediction']
        accuracy = result['accuracy']
        explanation = result['explanation']
        

        if self.verbose:
            print("[Aggregator info]: Done, returning: ", badness)

        return {
                "classifier_name": self.getName(),
                "success": True,
                "error_description": '',
                "badness": float(badness),
                "accuracy": accuracy,
                "explanation": list(explanation),
                "final": self.isFinal(),
                "created": datetime.datetime.now()
                }
