import logging
import math
from pvpower.estimator import Estimator, SVMEstimator, FullVectorizer, CoreVectorizer, SunshineVectorizer, PlusVisibilityVectorizer, SushinePlusCloudCoverVectorizer, PlusSunshineVectorizer, PlusCloudCoverVectorizer, PlusVisibilitySunshineVectorizer, PlusVisibilityCloudCoverVectorizer, PlusVisibilityFogCloudCoverVectorizer
from pvpower.traindata import TrainData


class TrainRun:

    def __init__(self, estimator: Estimator, train_data: TrainData):
        example_data, validation_data = train_data.split()
        self.validation_samples  = [record for record in validation_data.samples]
        self.estimator = estimator
        self.estimator.retrain(example_data)
        self.predictions = [estimator.predict(validation_record) for validation_record in self.validation_samples]
        self.estimator.retrain(train_data)  # retrain with all data

    def __score(self, real, predicted):
        rounded_real = round(real/10)*10
        rounded_predicted = round(predicted/10)*10
        score = abs(rounded_real - rounded_predicted)
        return score

    @property
    def score(self) -> float:
        scores = []
        for i in range(len(self.validation_samples)):
            scores.append(self.__score(self.validation_samples[i].power_watt, self.predictions[i]))

        scores = sorted(scores)
        ignore_size = int(len(scores) * 0.1)
        if ignore_size <= 0:
            ignore_size = 1
        scores = scores[ignore_size:-ignore_size]
        if len(scores) == 0:
            return 999999
        else:
            return round(sum(scores) / len(scores), 2)

    def __lt__(self, other):
        return self.score < other.score

    def __eq__(self, other):
        return self.score == other.score

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        max_lines = 1000
        txt = str(self.estimator) + "\n"
        txt += "score:  " + str(self.score) + " (smaller is better)\n"
        txt += '{:25s}   {:10s} {:10s}    {:10s}          {:14s} {:14s} {:14s} {:14s} {:14s}         \n'.format("time", "real", "predicted", "score", "irradiance", "sunshine", "cloud_cover", "visibility", "proba.fog")
        for i in range(0, len(self.validation_samples)):
            if i > max_lines:
                txt += '....'
                break
            txt += '{:<25s}   {:<10d} {:<10d}    {:<10s}          {:<14d} {:<14d} {:<14s} {:<14d}  {:<14d}        \n'.format(self.validation_samples[i].time.strftime("%d.%b %H:%M") + " (" + self.validation_samples[i].time_utc.strftime("%H:%M") + " utc)",
                                                                                                                               self.validation_samples[i].power_watt,
                                                                                                                               self.predictions[i],
                                                                                                                               "[" + str(self.__score(self.validation_samples[i].power_watt, self.predictions[i])) + "]",
                                                                                                                               self.validation_samples[i].irradiance,
                                                                                                                               self.validation_samples[i].sunshine,
                                                                                                                               str(self.validation_samples[i].cloud_cover_effective) + "%",
                                                                                                                               self.validation_samples[i].visibility,
                                                                                                                               self.validation_samples[i].probability_for_fog)
        return txt



class TrainingCenter:

    def new_estimator(self, trainData: TrainData) -> Estimator:
        estimators = [SVMEstimator(CoreVectorizer()),
                      SVMEstimator(FullVectorizer()),
                      SVMEstimator(SunshineVectorizer()),
                      SVMEstimator(SushinePlusCloudCoverVectorizer()),
                      SVMEstimator(PlusVisibilityVectorizer()),
                      SVMEstimator(PlusSunshineVectorizer()),
                      SVMEstimator(PlusCloudCoverVectorizer()),
                      SVMEstimator(PlusVisibilitySunshineVectorizer()),
                      SVMEstimator(PlusVisibilityCloudCoverVectorizer()),
                      SVMEstimator(PlusVisibilityFogCloudCoverVectorizer())]

        logging.info("train estimators with " + str(len(trainData.samples)) + " samples")
        rounds = 6
        train_runs = []
        for estimator in estimators:
            runs = sorted([TrainRun(estimator, trainData.rotated(round(i * 100 / rounds))) for i in range(0, rounds)])
            cleaned_runs = [run for run in runs if run.score < 10000]
            if len(cleaned_runs) > 0:
                runs = cleaned_runs
            train_runs.append(runs[int(len(runs)*0.5)])

        train_runs = sorted(train_runs, reverse=True)
        for run in train_runs:
            logging.debug(run)
        best_train = train_runs[-1]
        logging.info("new estimator trained \n" + str(best_train))
        return best_train.estimator
