from random import randint, gauss, choice, shuffle, random
from typing import List, Set

from lorem import word, get_word
from morelianoctua.model import OWLOntology, OWLAxiom
from morelianoctua.model.axioms.assertionaxiom import OWLClassAssertionAxiom, \
    OWLObjectPropertyAssertionAxiom, OWLDataPropertyAssertionAxiom
from morelianoctua.model.axioms.classaxiom import OWLSubClassOfAxiom
from morelianoctua.model.axioms.declarationaxiom import \
    OWLClassDeclarationAxiom, OWLObjectPropertyDeclarationAxiom
from morelianoctua.model.axioms.owldatapropertyaxiom import \
    OWLDataPropertyDomainAxiom, OWLDataPropertyRangeAxiom
from morelianoctua.model.axioms.owlobjectpropertyaxiom import \
    OWLObjectPropertyDomainAxiom, OWLObjectPropertyRangeAxiom
from morelianoctua.model.objects.classexpression import OWLClass, \
    OWLObjectSomeValuesFrom, OWLObjectIntersectionOf, OWLClassExpression
from morelianoctua.model.objects.datarange import OWLDatatype
from morelianoctua.model.objects.individual import OWLNamedIndividual
from morelianoctua.model.objects.property import OWLObjectProperty, \
    OWLDataProperty
from morelianoctua.reasoning.owllinkreasoner import OWLLinkReasoner
from rdflib import URIRef, XSD, Literal, OWL

from learningscenariogenerators import LearningScenarioGenerator, \
    LearningScenario, OntologyGenerator
from learningscenariogenerators.exception import \
    OntologyConstraintsViolatedException

class SimulatedAnnealingLearningScenario1Generator(LearningScenarioGenerator):
    """
    Generates a class expression learning scenario showing the potentials of
    using a simulated annealing approach.

    For class expression learning in description logics we focus on the top-down
    refinement approach which structures the search space based on iteratively
    refining current best candidate class expressions to more specific ones,
    e.g. by considering the class hierarchy of an ontology, or combining
    existing (complex) candidate class expressions or atomic classes
    conjunctively etc. (We may also allow upward refinements by introducing
    disjunctive combinations, though.) This allows a systematic exploration of
    the search space. However, since this allows a wide range of possible
    refinements of a single candidate class expression in a single refinement
    step the search space is huge.

    When balancing

    - exploitation (i.e. the greedy search following a -- mostly
      deterministic -- heuristic), and
    - exploration (i.e. providing a good coverage of the whole search space
      without strict guidance, e.g. by randomly generating class expressions)

    the simulated annealing approach starts in a highly explorative setting and
    'cools down' to a more exploitative setting after some time or whenever the
    score (e.g. accuracy, f1-score, ...) of a learned candidate class expression
    increases. In an adaptive simulated annealing setting the whole procedure
    may 'heat up' in case the learning process is stuck on a plateau or in a
    local optimum, i.e. whenever there was no increase in the score (no better
    candidate class expression could be found) for a certain amount of time.

    So a learning scenario which would highly benefit from a simulated
    annealing-based class expression learning approach would have the following
    characteristics

    C1) The target concept has multiple parts (e.g. disjunctively connected)
        which is not findable via straight greedy downward refinement
    C2) The learning scenario induces a plateau during the concept learning
        process which cannot be passed by a myopic greedy learning approach.
        This happens e.g. when all the positive examples are instances of a
        class expression

            hasComponent some GoodComponent

        and all the negative examples are instances of the class expression

            hasComponent some BadComponent

        During the systematic downward refinement process at some point we may
        create the candidate class expression

           hasComponent some Thing

        which is only one refinement step away from finding the perfect
        explanation for the positive examples (i.e. being instance of
        hasComponent some GoodComponent -- assuming GoodComponent and
        BadComponent are disjoint classes). However hasComponent some Thing
        would possibly be considered too weak since it only gives an accuracy
        score of 0.5 (assuming the number of positive and negative examples is
        balanced).

    At the moment, only C2 is implemented!!!
    """
    def __init__(
            self,
            num_pos_examples: int = 50,
            num_neg_examples: int = 50,
            num_classes: int = 30,
            num_object_properties: int = 10,
            num_data_properties: int = 5,
            num_overall_individuals: int = 500,
            existential_nesting_depth: int = 2):

        self.num_pos_examples = num_pos_examples
        self.num_neg_examples = num_neg_examples
        self.num_classes = num_classes
        self.num_object_properties = num_object_properties
        self.num_data_properties = num_data_properties
        self.num_overall_individuals = num_overall_individuals
        self.existential_nesting_depth = existential_nesting_depth

        self.num_non_example_individuals = \
            num_overall_individuals - num_pos_examples - num_neg_examples

        if self.num_non_example_individuals < existential_nesting_depth * \
                (num_pos_examples + num_neg_examples):
            raise OntologyConstraintsViolatedException(
                f'The number of overall examples must be greater than or equal '
                f'to (existential_nesting_depth(={existential_nesting_depth}) '
                f'* the number of positive and negative examples) + (the '
                f'number of positive and negative examples) as we do not want '
                f'to re-use non-example individuals in relation paths')

        if num_object_properties < existential_nesting_depth:
            raise OntologyConstraintsViolatedException(
                'The number of object properties should be greater than or '
                'equal to the nesting depth as we do not allow object '
                'properties appearing multiple times in the object property '
                'nesting sequence of the existential restriction'
            )

    def _generate_target_obj_prop_nesting(
            self, ont_generator: OntologyGenerator) -> List[OWLObjectProperty]:

        target_obj_prop_nesting_seq = []

        for i in range(self.existential_nesting_depth):
            while True:
                __obj_prop = ont_generator.pick_random_object_property()

                if __obj_prop not in target_obj_prop_nesting_seq:
                    break

            target_obj_prop_nesting_seq.append(__obj_prop)

        return target_obj_prop_nesting_seq

    def _get_final_filler(self, ont_generator: OntologyGenerator) -> OWLClass:
        """
        The final filler class of the nested existential restriction we want to
        generate here should have the following properties

        - there should be at least two subclasses, holding the positive and
          negative examples, respectively
        - the cardinality of the union of the class's parents, the class's
          siblings, and the siblings children should be at least as big as the
          nesting depth s.t. we can choose domain/range classes for object
          properties from the existential restriction sequence which are not
          inside the sub-tree of the filler class
        - That's it? -.-
        """

        # Loop until we find a class that has at least two subclasses and there
        # are more than self.existential_nesting_depth classes left in the
        # class hierarchy outside the subclass subtree of that class
        while True:
            filler_candidate = ont_generator.pick_random_class()

            if len(ont_generator.get_all_direct_sub_classes(
                    filler_candidate)) >= 2 and \
                    len(ont_generator.get_classes_from_complement_of_subtree(
                        filler_candidate)) > self.existential_nesting_depth:
                break

        return filler_candidate

    def _remove_examples_from_indivs(
            self,
            individuals: List[OWLNamedIndividual],
            examples: List[OWLNamedIndividual]):

        for example in examples:
            individuals.remove(example)

    def generate_scenario(self) -> LearningScenario:
        ontology_generator = OntologyGenerator()

        for i in range(self.num_classes):
            ontology_generator.add_new_class()

        for i in range(self.num_object_properties):
            ontology_generator.add_new_object_property()

        for i in range(self.num_data_properties):
            ontology_generator.add_new_data_property()

        ontology_generator.init_random_class_hierarchy()

        pos_examples = []
        for i in range(self.num_pos_examples):
            pos_example = ontology_generator.add_new_individual('pos_indiv')
            pos_examples.append(pos_example)

        neg_examples = []
        for i in range(self.num_neg_examples):
            neg_example = ontology_generator.add_new_individual('neg_indiv')
            neg_examples.append(neg_example)

        non_example_individuals = []
        for i in range(self.num_non_example_individuals):
            indiv = ontology_generator.add_new_individual()
            non_example_individuals.append(indiv)

        ext_restr_final_filler_class = \
            self._get_final_filler(ontology_generator)

        _all_direct_filler_subclasses = \
            ontology_generator.get_all_direct_sub_classes(
                ext_restr_final_filler_class)
        shuffle(_all_direct_filler_subclasses)

        pos_examples_ext_restr_final_filler_class = \
            _all_direct_filler_subclasses.pop(0)
        neg_examples_ext_restr_final_filler_class = \
            _all_direct_filler_subclasses.pop(0)

        target_obj_prop_nesting_seq = \
            self._generate_target_obj_prop_nesting(ontology_generator)

        target_obj_prop_nesting_seq_domains = []
        target_obj_prop_nesting_seq_ranges = []

        # Set domains and ranges for all the object properties involved in the
        # nested existential restriction. Class candidates are all those classes
        # - not equal to the filler of the final existential restriction in the
        #   existential restriction nesting
        # - not being a subclass of the filler of the final existential
        #   restriction in the existential restriction nesting
        cls_candidates = \
            ontology_generator.get_classes_from_complement_of_subtree(
                ext_restr_final_filler_class)
        shuffle(cls_candidates)

        last_range = None
        #                           all except last obj prop
        for i in range(0, len(target_obj_prop_nesting_seq)-1):
            obj_prop = target_obj_prop_nesting_seq[i]

            if last_range is None:
                domain = cls_candidates.pop(0)
            else:
                domain = last_range

            rnge = cls_candidates.pop(0)

            target_obj_prop_nesting_seq_domains.append(domain)
            target_obj_prop_nesting_seq_ranges.append(rnge)

            ontology_generator.set_object_property_domain_and_range(
                obj_prop, domain, rnge)

            last_range = rnge

        ontology_generator.set_object_property_domain_and_range(
            target_obj_prop_nesting_seq[-1],
            last_range,
            ext_restr_final_filler_class)
        target_obj_prop_nesting_seq_domains.append(last_range)
        target_obj_prop_nesting_seq_ranges.append(ext_restr_final_filler_class)

        assert len(target_obj_prop_nesting_seq) == \
            len(target_obj_prop_nesting_seq_domains)

        assert len(target_obj_prop_nesting_seq_domains) == \
            len(target_obj_prop_nesting_seq_ranges)

        # build target class expression
        target_cls_expression = pos_examples_ext_restr_final_filler_class

        for i in range(len(target_obj_prop_nesting_seq)-1, -1, -1):
            obj_prop = target_obj_prop_nesting_seq[i]
            target_cls_expression = \
                OWLObjectSomeValuesFrom(obj_prop, target_cls_expression)

        # build target negative class expression
        target_neg_cls_expression = neg_examples_ext_restr_final_filler_class

        for i in range(len(target_obj_prop_nesting_seq) - 1, -1, -1):
            obj_prop = target_obj_prop_nesting_seq[i]
            target_neg_cls_expression = \
                OWLObjectSomeValuesFrom(obj_prop, target_neg_cls_expression)

        # set domain and ranges for remaining object properties
        for obj_prop in ontology_generator.object_properties:
            if obj_prop in target_obj_prop_nesting_seq:
                continue  # We already set domain/range for those

            # FIXME: Might be this is too naive and we have to really only pick
            # classes not being subclasses of any of the domains/ranges assigned
            # to the obj properties from the existential restriction
            domain = ontology_generator.pick_random_class()
            rnge = ontology_generator.pick_random_class()

            ontology_generator.set_object_property_domain_and_range(
                obj_prop, domain, rnge)

        pos_ext_restr_final_filler_classes = \
            ontology_generator.get_all_sub_classes(
                pos_examples_ext_restr_final_filler_class)

        neg_examples_ext_restr_final_filler_classes = \
            ontology_generator.get_all_sub_classes(
                neg_examples_ext_restr_final_filler_class)

        # for data_prop in ontology_generator.data_properties:
        #     domain_cls = ontology_generator.pick_random_class()
        #     range_dtype = ontology_generator.pick_random_datatype()
        #
        #     ontology_generator.set_data_property_domain_and_range(
        #         data_prop, domain_cls, range_dtype)

        tmp_individuals = ontology_generator.individuals.copy()
        _len_before = len(tmp_individuals)
        self._remove_examples_from_indivs(tmp_individuals, pos_examples)
        self._remove_examples_from_indivs(tmp_individuals, neg_examples)
        _len_after = len(tmp_individuals)
        assert _len_before == _len_after + len(pos_examples) + len(neg_examples)

        shuffle(tmp_individuals)

        prop_add_probability = 0.5

        for pos_example in pos_examples:
            print(f'pos example: {pos_example}')
            tmp_dom_indiv = pos_example
            domain_super_cls = target_obj_prop_nesting_seq_domains[0]
            domain = choice(
                ontology_generator.get_all_sub_classes(domain_super_cls))
            ontology_generator.add_instance(tmp_dom_indiv, domain)

            for i in range(len(target_obj_prop_nesting_seq)-1):
                obj_prop = target_obj_prop_nesting_seq[i]

                range_super_cls = target_obj_prop_nesting_seq_ranges[i]
                rnge = choice(
                    ontology_generator.get_all_sub_classes(range_super_cls))

                tmp_range_indiv = tmp_individuals.pop(0)
                ontology_generator.add_instance(tmp_range_indiv, rnge)

                ontology_generator.add_axiom(
                    OWLObjectPropertyAssertionAxiom(
                        tmp_dom_indiv, obj_prop, tmp_range_indiv))
                tmp_dom_indiv = tmp_range_indiv

            # last filler individual should not be instance of the range cls
            # but of the more special positive filler class
            obj_prop = target_obj_prop_nesting_seq[-1]
            range_super_cls = pos_examples_ext_restr_final_filler_class
            rnge = choice(
                ontology_generator.get_all_sub_classes(range_super_cls))

            tmp_range_indiv = tmp_individuals.pop(0)
            ontology_generator.add_instance(tmp_range_indiv, rnge)

            ontology_generator.add_axiom(
                OWLObjectPropertyAssertionAxiom(
                    tmp_dom_indiv, obj_prop, tmp_range_indiv))

            properties_i_gave_up_with = []
            # add further random assertions (pos_example, propX, other_indiv)
            while random() > prop_add_probability:
                possible_obj_props = \
                    ontology_generator.get_object_properties_by_domain(domain)

                # should always work as at least target_obj_prop_nesting_seq[0]
                # has domain as domain
                obj_prop = choice(possible_obj_props)
                if obj_prop in properties_i_gave_up_with:
                    continue

                print(f'prop: {obj_prop}')

                max_number_of_attempts = 10

                attempt_cntr = 0
                while True:
                    if attempt_cntr == max_number_of_attempts:
                        break

                    rnge = ontology_generator.get_random_range_class(obj_prop)
                    print(f'range: {rnge}')

                    obj_indiv = ontology_generator.\
                        pick_random_individual_by_cls(rnge)

                    attempt_cntr += 1

                    if obj_indiv is None:
                        print('No instance found')
                        continue
                    break

                if attempt_cntr == max_number_of_attempts:
                    print(f'Giving up after {attempt_cntr} attempts')
                    properties_i_gave_up_with.append(obj_prop)
                    continue

                ontology_generator.add_axiom(
                    OWLObjectPropertyAssertionAxiom(
                        pos_example, obj_prop, obj_indiv))

            properties_i_gave_up_with = []
            # add further random assertions (other_indiv, propX, pos_example)
            while random() > prop_add_probability:
                possible_obj_props = \
                    ontology_generator.get_object_properties_by_range(domain)

                if len(possible_obj_props) == 0:
                    break

                obj_prop = choice(possible_obj_props)
                if obj_prop in properties_i_gave_up_with:
                    continue

                print(f'prop: {obj_prop}')

                max_number_of_attempts = 10

                attempt_cntr = 0
                while True:
                    if attempt_cntr == max_number_of_attempts:
                        break

                    print(f'domain: {domain}')
                    domain = ontology_generator.get_random_domain_class(
                        obj_prop)

                    subj_indiv = ontology_generator.\
                        pick_random_individual_by_cls(domain)

                    attempt_cntr += 1
                    if subj_indiv is None:
                        print('No instance found')
                        continue
                    break

                if attempt_cntr == max_number_of_attempts:
                    print(f'Giving up after {attempt_cntr} attempts')
                    properties_i_gave_up_with.append(obj_prop)
                    continue

                ontology_generator.add_axiom(
                    OWLObjectPropertyAssertionAxiom(
                        subj_indiv, obj_prop, pos_example))
        print('Done with pos examples')

        for neg_example in neg_examples:
            print(f'neg: {neg_example}')
            tmp_dom_indiv = neg_example
            domain_super_cls = target_obj_prop_nesting_seq_domains[0]
            domain = choice(
                ontology_generator.get_all_sub_classes(domain_super_cls))
            ontology_generator.add_instance(tmp_dom_indiv, domain)

            for i in range(len(target_obj_prop_nesting_seq)-1):
                obj_prop = target_obj_prop_nesting_seq[i]

                range_super_cls = target_obj_prop_nesting_seq_ranges[i]
                rnge = choice(
                    ontology_generator.get_all_sub_classes(range_super_cls))

                tmp_range_indiv = tmp_individuals.pop(0)
                ontology_generator.add_instance(tmp_range_indiv, rnge)

                ontology_generator.add_axiom(
                    OWLObjectPropertyAssertionAxiom(
                        tmp_dom_indiv, obj_prop, tmp_range_indiv))
                tmp_dom_indiv = tmp_range_indiv

            # last filler individual should not be instance of the range cls
            # but of the more special positive filler class
            obj_prop = target_obj_prop_nesting_seq[-1]
            range_super_cls = neg_examples_ext_restr_final_filler_class
            rnge = choice(
                ontology_generator.get_all_sub_classes(range_super_cls))

            tmp_range_indiv = tmp_individuals.pop(0)
            ontology_generator.add_instance(tmp_range_indiv, rnge)

            ontology_generator.add_axiom(
                OWLObjectPropertyAssertionAxiom(
                    tmp_dom_indiv, obj_prop, tmp_range_indiv))

            properties_i_gave_up_with = []
            # add further random assertions (neg_example, propX, other_indiv)
            while random() > prop_add_probability:
                possible_obj_props = \
                    ontology_generator.get_object_properties_by_domain(domain)

                # should always work as at least target_obj_prop_nesting_seq[0]
                # has domain as domain
                obj_prop = choice(possible_obj_props)
                if obj_prop in properties_i_gave_up_with:
                    continue

                print(f'prop: {obj_prop}')

                max_number_of_attempts = 10

                attempt_cntr = 0
                while True:
                    if attempt_cntr == max_number_of_attempts:
                        break

                    rnge = ontology_generator.get_random_range_class(obj_prop)
                    print(f'range: {rnge}')

                    # if len(tmp_individuals) > 0:
                    #     obj_indiv = tmp_individuals.pop(0)
                    #     ontology_generator.add_instance(obj_indiv, rnge)
                    #
                    # else:
                    obj_indiv = ontology_generator.\
                        pick_random_individual_by_cls(rnge)

                    attempt_cntr += 1
                    if obj_indiv is None:
                        print('No instance found')
                        continue
                    break

                if attempt_cntr == max_number_of_attempts:
                    print(f'Giving up after {attempt_cntr} attempts')
                    properties_i_gave_up_with.append(obj_prop)
                    continue

                ontology_generator.add_axiom(
                    OWLObjectPropertyAssertionAxiom(
                        neg_example, obj_prop, obj_indiv))

            properties_i_gave_up_with = []
            # add further random assertions (other_indiv, propX, neg_example)
            while random() > prop_add_probability:
                possible_obj_props = \
                    ontology_generator.get_object_properties_by_range(domain)

                if len(possible_obj_props) == 0:
                    break

                obj_prop = choice(possible_obj_props)
                if obj_prop in properties_i_gave_up_with:
                    continue

                print(f'prop: {obj_prop}')

                max_number_of_attempts = 10

                attempt_cntr = 0
                while True:
                    if attempt_cntr == max_number_of_attempts:
                        break

                    domain = ontology_generator.get_random_domain_class(
                        obj_prop)
                    print(f'domain: {domain}')

                    # if len(tmp_individuals) > 0:
                    #     subj_indiv = tmp_individuals.pop(0)
                    #     ontology_generator.add_instance(subj_indiv, domain)
                    #
                    # else:
                    subj_indiv = ontology_generator.\
                        pick_random_individual_by_cls(domain)

                    attempt_cntr += 1
                    if subj_indiv is None:
                        print('No instance found')
                        continue
                    break

                if attempt_cntr == max_number_of_attempts:
                    print(f'Giving up after {attempt_cntr} attempts')
                    properties_i_gave_up_with.append(obj_prop)
                    continue

                ontology_generator.add_axiom(
                    OWLObjectPropertyAssertionAxiom(
                        subj_indiv, obj_prop, neg_example))
        print('Done with neg examples')

        for indiv in tmp_individuals:

            while True:
                obj_prop = ontology_generator.pick_random_object_property()

                if obj_prop not in target_obj_prop_nesting_seq:
                    break

            cls = ontology_generator.get_random_domain_class(obj_prop)

            ontology_generator.add_instance(indiv, cls)

            # additional assertions (indiv, someObjProp, otherIndiv)
            while random() > prop_add_probability:
                rnge = ontology_generator.get_range_cls(obj_prop)

                obj_indiv = ontology_generator.\
                    pick_random_individual_by_cls(rnge)
                if obj_indiv is None:
                    continue

                ontology_generator.add_axiom(
                    OWLObjectPropertyAssertionAxiom(indiv, obj_prop, obj_indiv))

            # additional assertions (otherIndiv, someObjProp, indiv)
            while random() > prop_add_probability:
                possible_obj_props = \
                    ontology_generator.get_object_properties_by_range(cls)

                if len(possible_obj_props) == 0:
                    break

                obj_prop = choice(possible_obj_props)

                domain = ontology_generator.get_domain_cls(obj_prop)

                subj_indiv = ontology_generator.\
                    pick_random_individual_by_cls(domain)
                if subj_indiv is None:
                    continue

                ontology_generator.add_axiom(
                    OWLObjectPropertyAssertionAxiom(
                        subj_indiv, obj_prop, indiv))
        print('Done with remaining individuals')

        # pos_bridge_individuals = []
        # for pos_example in pos_examples:
        #     while True:
        #         bridge_individual = ontology_generator.pick_random_individual()
        #
        #         if not bridge_individual == pos_example and \
        #                 not ontology_generator.is_instance_of(
        #                     bridge_individual, target_obj_prop_2_range) and \
        #                 not ontology_generator.is_instance_of(
        #                     bridge_individual, ext_restr_final_filler_class) and \
        #                 bridge_individual not in pos_examples and \
        #                 bridge_individual not in neg_examples:
        #             break
        #
        #     pos_bridge_individuals.append(bridge_individual)
        #     bridge_indiv_cls = choice(
        #         ontology_generator.get_all_sub_classes(target_obj_prop_1_range))
        #     ontology_generator.add_instance(
        #         bridge_individual, bridge_indiv_cls)
        #
        #     ontology_generator.add_axiom(
        #         OWLObjectPropertyAssertionAxiom(
        #             pos_example, target_obj_prop_1, bridge_individual))
        #
        #     while True:
        #         filler_instance = ontology_generator.pick_random_individual()
        #
        #         if not filler_instance == pos_example and \
        #                 not filler_instance == bridge_individual and \
        #                 not ontology_generator.is_instance_of(
        #                     filler_instance, target_obj_prop_1_range) and \
        #                 not ontology_generator.is_instance_of(
        #                     filler_instance, ext_restr_final_filler_class):
        #             break
        #
        #     filler_instance_cls = choice(
        #         ontology_generator.get_all_sub_classes(
        #             target_obj_prop_2_pos_range))
        #     ontology_generator.add_instance(
        #         filler_instance, filler_instance_cls)
        #
        #     ontology_generator.add_axiom(
        #         OWLObjectPropertyAssertionAxiom(
        #             bridge_individual, target_obj_prop_2, filler_instance))
        #
        #     while random() > 0.5:
        #         obj_prop = ontology_generator.pick_random_object_property()
        #
        #         # if obj_prop == target_obj_prop_1 or \
        #         #         obj_prop == target_obj_prop_2:
        #         #     continue
        #
        #         while True:
        #             object_individual = \
        #                 ontology_generator.pick_random_individual()
        #
        #             if object_individual != pos_example:
        #                 break
        #
        #         while True:
        #             obj_indiv_cls = \
        #                 ontology_generator.get_random_range_class(obj_prop)
        #
        #             is_pos_target_2_range_sub_class = \
        #                 obj_indiv_cls in ontology_generator.get_all_sub_classes(
        #                     target_obj_prop_2_pos_range)
        #             is_neg_target_2_range_sub_class = \
        #                 obj_indiv_cls in ontology_generator.get_all_sub_classes(
        #                     target_obj_prop_2_neg_range)
        #
        #             if not is_pos_target_2_range_sub_class and \
        #                     not is_neg_target_2_range_sub_class:
        #                 break
        #
        #         ontology_generator.add_instance(
        #             object_individual, obj_indiv_cls)
        #         ontology_generator.add_axiom(
        #             OWLObjectPropertyAssertionAxiom(
        #                 pos_example, obj_prop, object_individual))
        #
        #     while random() > 0.5:
        #         obj_prop = ontology_generator.pick_random_object_property()
        #
        #         if obj_prop == target_obj_prop_1 or \
        #                 obj_prop == target_obj_prop_2:
        #             continue
        #
        #         while True:
        #             subject_individual = \
        #                 ontology_generator.pick_random_individual()
        #
        #             if subject_individual != pos_example:
        #                 break
        #
        #         subj_indiv_cls = \
        #             ontology_generator.get_random_range_class(obj_prop)
        #
        #         ontology_generator.add_instance(
        #             subject_individual, subj_indiv_cls)
        #         ontology_generator.add_axiom(
        #             OWLObjectPropertyAssertionAxiom(
        #                 subject_individual, obj_prop, pos_example))
        #
        #     while random() > 0.5:
        #         data_prop = ontology_generator.pick_random_data_property()
        #         datatype = ontology_generator.get_range_datatype(data_prop)
        #         literal = ontology_generator.generate_random_literal(datatype)
        #
        #         ontology_generator.add_axiom(
        #             OWLDataPropertyAssertionAxiom(
        #                 pos_example, data_prop, literal))
        #
        # neg_bridge_individuals = []
        # for neg_example in neg_examples:
        #     while True:
        #         bridge_individual = ontology_generator.pick_random_individual()
        #
        #         if not bridge_individual == neg_example and \
        #                 bridge_individual not in pos_bridge_individuals and \
        #                 not ontology_generator.is_instance_of(
        #                     bridge_individual, target_obj_prop_2_range) and \
        #                 not ontology_generator.is_instance_of(
        #                     bridge_individual, ext_restr_final_filler_class) and \
        #                 bridge_individual not in pos_examples and \
        #                 bridge_individual not in neg_examples:
        #             break
        #
        #     neg_bridge_individuals.append(bridge_individual)
        #
        #     bridge_indiv_cls = choice(
        #         ontology_generator.get_all_sub_classes(target_obj_prop_1_range))
        #     ontology_generator.add_instance(
        #         bridge_individual, bridge_indiv_cls)
        #
        #     ontology_generator.add_axiom(
        #         OWLObjectPropertyAssertionAxiom(
        #             neg_example, target_obj_prop_1, bridge_individual))
        #
        #     while True:
        #         filler_instance = ontology_generator.pick_random_individual()
        #
        #         if not filler_instance == neg_example and \
        #                 not filler_instance == bridge_individual and \
        #                 not ontology_generator.is_instance_of(
        #                     filler_instance, target_obj_prop_1_range) and \
        #                 not ontology_generator.is_instance_of(
        #                     filler_instance, ext_restr_final_filler_class) and \
        #                 not ontology_generator.is_instance_of(
        #                     filler_instance, target_obj_prop_2_pos_range):
        #             break
        #
        #     filler_instance_cls = choice(
        #         ontology_generator.get_all_sub_classes(
        #             target_obj_prop_2_neg_range))
        #     ontology_generator.add_instance(
        #         filler_instance, filler_instance_cls)
        #
        #     ontology_generator.add_axiom(
        #         OWLObjectPropertyAssertionAxiom(
        #             bridge_individual, target_obj_prop_2, filler_instance))
        #
        #     while random() > 0.5:
        #         obj_prop = ontology_generator.pick_random_object_property()
        #
        #         # if obj_prop == target_obj_prop_1 or \
        #         #         obj_prop == target_obj_prop_2:
        #         #     continue
        #
        #         while True:
        #             object_individual = \
        #                 ontology_generator.pick_random_individual()
        #
        #             if object_individual != neg_example:
        #                 break
        #
        #         while True:
        #             obj_indiv_cls = \
        #                 ontology_generator.get_random_range_class(obj_prop)
        #
        #             is_pos_target_2_range_sub_class = \
        #                 obj_indiv_cls in ontology_generator.get_all_sub_classes(
        #                     target_obj_prop_2_pos_range)
        #
        #             is_neg_target_2_range_sub_class = \
        #                 obj_indiv_cls in ontology_generator.get_all_sub_classes(
        #                     target_obj_prop_2_neg_range)
        #
        #             if not is_pos_target_2_range_sub_class and \
        #                     not is_neg_target_2_range_sub_class:
        #                 break
        #
        #         ontology_generator.add_instance(
        #             object_individual, obj_indiv_cls)
        #         ontology_generator.add_axiom(
        #             OWLObjectPropertyAssertionAxiom(
        #                 neg_example, obj_prop, object_individual))
        #
        #     while random() > 0.5:
        #         obj_prop = ontology_generator.pick_random_object_property()
        #
        #         if obj_prop == target_obj_prop_1 or \
        #                 obj_prop == target_obj_prop_2:
        #             continue
        #
        #         while True:
        #             subject_individual = \
        #                 ontology_generator.pick_random_individual()
        #
        #             if subject_individual != neg_example:
        #                 break
        #
        #         subj_indiv_cls = \
        #             ontology_generator.get_random_range_class(obj_prop)
        #
        #         ontology_generator.add_instance(
        #             subject_individual, subj_indiv_cls)
        #         ontology_generator.add_axiom(
        #             OWLObjectPropertyAssertionAxiom(
        #                 subject_individual, obj_prop, neg_example))
        #
        #     while random() > 0.5:
        #         data_prop = ontology_generator.pick_random_data_property()
        #         datatype = ontology_generator.get_range_datatype(data_prop)
        #         literal = ontology_generator.generate_random_literal(datatype)
        #
        #         ontology_generator.add_axiom(
        #             OWLDataPropertyAssertionAxiom(
        #                 neg_example, data_prop, literal))
        #
        # for indiv in non_example_individuals:
        #     if indiv in pos_bridge_individuals or \
        #             indiv in neg_bridge_individuals:
        #         continue
        #
        #     while random() > 0.5:
        #         obj_prop = ontology_generator.pick_random_object_property()
        #
        #         # if obj_prop == target_obj_prop_1 or \
        #         #         obj_prop == target_obj_prop_2:
        #         #     continue
        #
        #         while True:
        #             object_individual = \
        #                 ontology_generator.pick_random_individual()
        #
        #             if object_individual != indiv and \
        #                     object_individual not in pos_bridge_individuals and \
        #                     object_individual not in neg_bridge_individuals:
        #                 break
        #
        #         while True:
        #             obj_indiv_cls = \
        #                 ontology_generator.get_random_range_class(obj_prop)
        #
        #             # is_pos_target_2_range_sub_class = \
        #             #     obj_indiv_cls in ontology_generator.get_all_sub_classes(
        #             #         target_obj_prop_2_pos_range)
        #             #
        #             # is_neg_target_2_range_sub_class = \
        #             #     obj_indiv_cls in ontology_generator.get_all_sub_classes(
        #             #         target_obj_prop_2_neg_range)
        #
        #             # if not is_pos_target_2_range_sub_class and \
        #             #         not is_neg_target_2_range_sub_class:
        #             if True:
        #                 break
        #
        #         ontology_generator.add_instance(
        #             object_individual, obj_indiv_cls)
        #         ontology_generator.add_axiom(
        #             OWLObjectPropertyAssertionAxiom(
        #                 indiv, obj_prop, object_individual))
        #
        #     while random() > 0.5:
        #         obj_prop = ontology_generator.pick_random_object_property()
        #
        #         if obj_prop == target_obj_prop_1 or \
        #                 obj_prop == target_obj_prop_2:
        #             continue
        #
        #         while True:
        #             subject_individual = \
        #                 ontology_generator.pick_random_individual()
        #
        #             if subject_individual != indiv and \
        #                     subject_individual not in pos_bridge_individuals and \
        #                     subject_individual not in neg_bridge_individuals:
        #                 break
        #
        #         subj_indiv_cls = \
        #             ontology_generator.get_random_range_class(obj_prop)
        #
        #         ontology_generator.add_instance(
        #             subject_individual, subj_indiv_cls)
        #         ontology_generator.add_axiom(
        #             OWLObjectPropertyAssertionAxiom(
        #                 subject_individual, obj_prop, indiv))
        #
        #     while random() > 0.5:
        #         data_prop = ontology_generator.pick_random_data_property()
        #         datatype = ontology_generator.get_range_datatype(data_prop)
        #         literal = ontology_generator.generate_random_literal(datatype)
        #
        #         ontology_generator.add_axiom(
        #             OWLDataPropertyAssertionAxiom(
        #                 indiv, data_prop, literal))

        ont = ontology_generator.get_ontology()
        # g = ont.as_rdf_graph()
        # g.serialize(open('/tmp/dbg.ttl', 'wb'), format='turtle')

        print(f'pos: {target_cls_expression}')
        print(f'neg: {target_neg_cls_expression}')

        return LearningScenario(
            {p for p in pos_examples},
            {n for n in neg_examples},
            target_cls_expression,
            ont)


from django.views import View
from django.http import JsonResponse, request
from .models import Owner, Dog
import json
# Create your views here.

class SampleOwnerView(View):
    def post(self, request):
        try:
            data = json.loads(request.body)
            Owner.objects.create(name=data['name'],email=data['email'],age=data['age'])
            return JsonResponse({"message":"SUCCESS!"},status=201)
        except KeyError:
            return JsonResponse({"message":"INVALID_KEY"}, status=400)

    def get(self, request):
        '''owners = Owner.objects.all()
        owner_list=[]
        for owner in owners:
            owner_={}
            owner_['name']=owner.name
            owner_['email']=owner.email
            owner_['age']=owner.age
            owner_list.append(owner_)'''
        owners = Owner.objects.all()
        owner_list=[]
        for owner in owners:
            owner_={}
            dogs = Dog.objects.filter(owner_id=owner.id)
            dog_list = [{'name':dog.name,'age':dog.age} for dog in dogs]
            owner_['name']=owner.name
            owner_['email']=owner.email
            owner_['age']=owner.age
            owner_['dogs']=dog_list
            owner_list.append(owner_)
        return JsonResponse({'result':owner_list}, status=200)

class SampleDogView(View):
    def post(self, request):
        try:
            data = json.loads(request.body)
            dog_owner = Owner.objects.get(name=data['owner'])
            Dog.objects.create(name=data['name'],age=data['age'],owner=dog_owner)
            return JsonResponse({"message":"SUCCESS!"},status=201)
        except KeyError:
            return JsonResponse({"message":"INVALID_KEY"}, status=400)

    def get(self, request):
        dogs = Dog.objects.all()
        dogs_list=[]
        for dog in dogs:
            dog_={}
            owners = Owner.objects.filter(id=dog.owner_id)
            dog_owner = [owner.name for owner in owners]
            dog_['name']=dog.name
            dog_['age']=dog.age
            dog_['owner']=dog_owner
            dogs_list.append(dog_)
        return JsonResponse({'result':dogs_list}, status=200)


#!/usr/bin/env python

from dsx.hard.hard import *

def TSArch(cluster_x, cluster_y, nb_proc = 1, nb_tty = 8, wcoproc = False):

    nb_cluster = cluster_x * cluster_y

    hd = Hardware(cluster_x, cluster_y , addr_size = 40, nb_proc = nb_proc, ccoherence = True) #nb_proc : proc by cluster

    ######### peripherals ##########
    hd.add(Tty('PSEG_TTY', pbase = 0xB4000000, channel_size = 16, nb_channel = nb_tty))
    hd.add(Fbf('PSEG_FBF', pbase = 0xB2000000, channel_size = 352 * 288 * 2, nb_channel = 1))
    hd.add(Ioc('PSEG_IOC', pbase = 0xB3000000, channel_size = 32, nb_channel = 1))
    hd.add(SimH('PSEG_SIMH', pbase = 0xB7000000, channel_size = 32, nb_channel = 1)) # 6 mapped registers

    hd.add(Xicu('PSEG_XICU', pbase = 0xB0000000, channel_size = 32, nb_channel = nb_proc, replicated = True)) # name suffixed with "_<num_cluster>"
    hd.add(Dma('PSEG_DMA', pbase = 0xB1000000, channel_size = 32, nb_channel = nb_proc, replicated = True))

    ############## MEMORY ###########
    for cl in range(nb_cluster):
        hd.add(RAM('PSEG_RAM_%d'%cl, pbase = 0x00000000 + (cl * hd.cluster_span), size = 0x00C00000))

    ############## IRQ ############
    hd.add(Irq(cluster_id = 0, proc_id = 0, icu_irq_id = 31, peri = Ioc, channel_id = 0))
    for j in range(16, 31):
        hd.add(Irq(cluster_id = 0, proc_id = 0, icu_irq_id = j, peri = Tty, channel_id = j - 16))

    for cl in range(nb_cluster):
        for p in xrange(nb_proc):
            hd.add(Irq(cluster_id = cl, proc_id = p, icu_irq_id = p + 8, peri = Dma,  channel_id = p))
            hd.add(Irq(cluster_id = cl, proc_id = p, icu_irq_id = p,     peri = Xicu, channel_id = p))

    ############# ROM ############
    hd.add(ROM("PSEG_ROM", pbase = 0xbfc00000, size = 0x00100000))

    return hd


#Declare original "L" array as a tuple and new tuple "new_L" as a list since it needs to be mutated
L = [(2,4,6), (1,3,5), (10,20,30)]
new_L = []

#Iterate through every row and then iterate through every row item in order to iterate through every 2D tuple item
for i in range(0, len(L)):
    #Declare row empty temporary to be added to new tuple
    tempRow = []
    for j in range(0, len(L[i])):
        #Append product of row number and current attribute/cell to temporary row
        tempRow.append(L[i][j] * i)

    #Append temporary row to new tuple
    new_L.append(tempRow)

new_L = tuple(new_L)
print("L: ", L, "\nNew L: ", new_L)


"""Clear Statement --> For each user we need to get all possible combinations of 3 sequences
Then from those possible 3 sequences we need to return the 3 sequence which has been visited by maximum
number of users.
1) Create a dictionary
{
    Joe:[home,about,career],
    Mary:[home,about,career],
    James:[home,cart,maps,home]
}
2) For each value in the above dictionary, create all possible combinations and append each combination
into a list and then apply counter on that list which will give count of each element in that list
i.e count of each 3 sequence. Return the 3 sequence having maximum count by using the most_common method
from Counter class
"""

from collections import defaultdict,Counter
from itertools import combinations
class Solution(object):
    def mostVisitedPattern(self, username, timestamp, website):
        """
        :type username: List[str]
        :type timestamp: List[int]
        :type website: List[str]
        :rtype: List[str]
        """
        data_dict = defaultdict(list)
        for i in range(len(username)):
            data_dict[username[i]].append(website[i])
        val_lst = list(data_dict.values())
        temp_lst = []
        for i in range(len(val_lst)):
            comb = list(combinations(val_lst[i],3))
            for val in comb:
                temp_lst.append(val)
        c = Counter(temp_lst)
        return c.most_common(1)[0][0]

username = ["joe","joe","joe","james","james","james","james","mary","mary","mary"]
timestamp = [1,2,3,4,5,6,7,8,9,10]
website = ["home","about","career","home","cart","maps","home","home","about","career"]

s = Solution()
print(s.mostVisitedPattern(username,timestamp,website))


################################################################################################################
"""

"""

# Built-in/Generic Imports
import math
import random
import sys

# Libs
import matplotlib.pyplot as plt

# Own modules

__version__ = '1.1.1'
__author__ = 'Victor Guillet'
__date__ = '31/01/2020'

################################################################################################################
import numpy as np

def update_area(grid, simulation_pos, max_value=10, min_value=0, radius=3, plot=1):
    # --> Creating meshgrid
    x = np.linspace(0, radius * 2, radius * 2)
    y = np.linspace(0, radius * 2, radius * 2)

    xx, yy = np.meshgrid(x, y)

    # --> Apply cone function
    update_grid = -np.sqrt((xx - radius) ** 2 +
                           (yy - radius) ** 2)

    update_grid += np.amax(np.absolute(update_grid))

    # --> Apply max/min limits
    update_grid = update_grid/np.amax(update_grid) * (max_value-min_value) + min_value

    # --> Display update grid
    if plot == 1:
        # --> Make a 3D plot
        fig = plt.figure()
        ax = fig.gca(projection='3d')
        ax.plot_surface(xx, yy, grid, cmap='viridis', linewidth=0)
        ax.set_xlabel('X axis')
        ax.set_ylabel('Y axis')
        ax.set_zlabel('Z axis')
        plt.show()

    # --> Apply to grid
    for i in range(radius * 2):
        for j in range(radius * 2):
            try:
                grid[simulation_pos[0] - radius + i, simulation_pos[1] - radius + j] = update_grid[i, j]
            except:
                pass

if __name__ == "__main__":
    update_area(grid=np.array([5, 5]),
                simulation_pos=[0, 0],
                max_value=10,
                min_value=5,
                radius=10)


# url_data
# https://docs.python.org/3/library/urllib.parse.html

import requests
from datetime import datetime
from urllib.parse import urlparse

class urlObject:
    def __init__(self, url,AR=False):
        super().__init__()
        self.url = url

        '''
        Parse url, decomposing in elements, preparating for test
        '''
        results = urlparse(self.url)

        self.scheme = results.scheme
        self.netloc = results.netloc
        self.path = results.path
        self.params = results.params
        self.fragment = results.fragment

        '''
        Test URL, check if url is alive (200) or not, or redirect (301). Decomposing the url's information in
        fields returned
        '''
        if self.scheme == '':
            url2Test = 'http://'+self.path
        else:
            url2Test = self.scheme+'://'+self.netloc

        d = datetime.now()
        self.datetime = str(d)
        self.urlTested = ''
        self.headers = ''
        try:
            r = requests.get(url2Test, allow_redirects=AR,timeout=1)
            #self.isAlive = True
            #self.is_permanent_redirect = r.is_permanent_redirect
            self.status_code = r.status_code
            self.urlTested = r.url
            self.headers = r.headers
        except TimeoutError:
            #self.isAlive = False
            print('Error Timeout:  '+url2Test)
            self.status_code = 0
            self.urlTested = url2Test
        except ConnectionError:
            #self.isAlive = False
            print('Error ConnectionError:  '+url2Test)
            self.status_code = -1
            self.urlTested = url2Test
        #except TooManyRedirects:
            #self.isAlive = False
        #    print('Error TooManyRedirects:  '+url2Test)
        #    self.status_code = -2
        except:
            print('Error:  '+url2Test)
            self.status_code = -2
            self.urlTested = url2Test

        print (self.url+' status code --'+str(self.status_code))


# -*- coding: utf-8 -*-

"""
Skeleton for first part of the blob-detection coursework as part of INF250
at NMBU (Autumn 2017).
"""

__author__ = "Yngve Mardal Moe"
__email__ = "yngve.m.moe@gmail.com"

import numpy as np
import matplotlib.pyplot as plt

def threshold(image, th=None):
    """Returns a binarised version of given image, thresholded at given value.

    Binarises the image using a global threshold `th`. Uses Otsu's method
    to find optimal thrshold value if the threshold variable is None. The
    returned image will be in the form of an 8-bit unsigned integer array
    with 255 as white and 0 as black.

    Parameters:
    -----------
    image : np.ndarray
        Image to binarise. If this image is a colour image then the last
        dimension will be the colour value (as RGB values).
    th : numeric
        Threshold value. Uses Otsu's method if this variable is None.

    Returns:
    --------
    binarised : np.ndarray(dtype=np.uint8)
        Image where all pixel values are either 0 or 255.
    """
    # Setup
    shape = np.shape(image)
    binarised = np.zeros([shape[0], shape[1]], dtype=np.uint8)

    if len(shape) == 3:
        image = image.mean(axis=2)
    elif len(shape) > 3:
        raise ValueError('Must be at 2D image')

    if th is None:
        th = otsu(image)
    # Start thresholding

    #for row in range(len(image)):
    #    for element in range(len(image[row])):
    #        binarised[row][element] = 255*(image[row][element]>=th)

    binarised = [[255*(image[row][element]>=th) for element in range(len(image[row]))]for row in range(len(image))]

    return binarised

def histogram(image):
    """Returns the image histogram with 256 bins.
    """
    # Setup
    shape = np.shape(image)
    histogram = np.zeros(256)

    if len(shape) == 3:
        image = image.mean(axis=2)
    elif len(shape) > 3:
        raise ValueError('Must be at 2D image')

    # Start to make the histogram

    for row in range(len(image)):
        for element in range(len(image[row])):
            wot = int(image[row][element])
            histogram[wot] += 1

    return histogram

def otsu(image):
    """Finds the optimal thresholdvalue of given image using Otsu's method.
    """
    hist = histogram(image)
    th = 0
    cum_hist = [0 for i in range(len(hist))]
    cum_hist[0] = hist[0]

    for i in range(1,len(hist)):
        cum_hist[i] = hist[i] + cum_hist[i-1]

    ## WRITE YOUR CODE HERE
    levels = range(256)

    sum_values = cum_hist[-1]

    t_vals = dict()

    for t in range(len(levels)):

        lower_sum = cum_hist[t]
        higher_sum = cum_hist[-1] - lower_sum

        if lower_sum == 0 or higher_sum == 0: # sanity check
            continue

        Wb=lower_sum/sum_values
        Wf=higher_sum/sum_values

        mean_b=sum([lev*hist[lev]  for lev in levels[:t+1]])/lower_sum
        mean_f=sum([lev*hist[lev]  for lev in levels[t+1:]])/higher_sum

        t_vals[t] = Wb*Wf*(mean_f-mean_b)**2

    th = max(t_vals, key=t_vals.get)
    return th

if __name__ == "__main__":
    img = plt.imread("bie_threshold.jpeg",format="jpeg")
    new_img = threshold(img)
    plt.imsave("biw_theshold_bin.jpeg",new_img,cmap="gray")
    plt.imshow(new_img,cmap="gray")
    plt.show()


import motor.motor_asyncio
import datetime
from TG_ROBOT import MONGO_DB_URI

class Database:
    def init(self, uri, database_name):
        self._client = motor.motor_asyncio.AsyncIOMotorClient(uri)
        self.db = self._client[database_name]
        self.col = self.db.users

    async def add_chat_list(self, chat_id, ch_id=None):
        get_chat = await self.is_chat_exist(chat_id)
        if get_chat:
            chat_list = list(get_chat.get("chats"))
            if ch_id != None and int(ch_id) in chat_list:
                return True, f"{ch_id} already in white list."
            elif ch_id is not None:
                chat_list.append(int(ch_id))
                await self.col.update_one(
                    {"id": chat_id}, {"$set": {"chats": chat_list}}
                )
                return True, f"{ch_id}, added into white list"
        a_chat = {"id": int(chat_id), "chats": [ch_id]}
        await self.col.insert_one(a_chat)
        return False, " "

    def new_user(self, id):
        return dict(
            id=id,
            join_date=datetime.date.today().isoformat(),
            notif=True,
        )

    async def is_user_exist(self, id):
        user = await self.col.find_one({"id": int(id)})
        return True if user else False

    async def total_users_count(self):
        count = await self.col.count_documents({})
        return count

    async def get_all_users(self):
        all_users = self.col.find({})
        return all_users

    async def set_notif(self, id, notif):
        await self.col.update_one({"id": id}, {"$set": {"notif": notif}})

    async def get_notif(self, id):
        user = await self.col.find_one({"id": int(id)})
        return user.get("notif", False)

    async def get_all_notif_user(self):
        notif_users = self.col.find({"notif": True})
        return notif_users

    async def total_notif_users_count(self):
        count = await self.col.count_documents({"notif": True})
        return count

    async def delete_user(self, user_id):
        await self.col.delete_many({"id": int(user_id)})

    async def is_chat_exist(self, id):
        user = await self.col.find_one({"id": int(id)})
        return user if user else False

    async def get_chat_list(self, chat_id):
        get_chat = await self.is_chat_exist(chat_id)
        if get_chat:
            return get_chat.get("chats", [])
        else:
            return False

    async def del_chat_list(self, chat_id, ch_id=None):
        get_chat = await self.is_chat_exist(chat_id)
        if get_chat:
            chat_list = list(get_chat.get("chats"))
            if ch_id != None and ch_id in chat_list:
                chat_list.remove(int(ch_id))
                await self.col.update_one(
                    {"id": chat_id}, {"$set": {"chats": chat_list}}
                )
                return True, f"{ch_id}, removed from white list"
            elif int(ch_id) not in chat_list:
                return True, f"{ch_id}, not found in white list."

remdb = Database(MONGO_DB_URI, "whitelist_chats")


import draw_graphs
import eeg_calculate
from in_out import DataFragment, ProcessingConstants, Filenames, InputFileLen
import numpy as np

electrode = 0   # the number of processed electrode; the input file contains data from 64 electrodes;
shift = 100  # for precise results shift should be about 5, but the calculations would be longer
np.set_printoptions(precision=None, threshold=np.inf, suppress=True, linewidth=np.inf)

def EEG_stationarity_check():
    """ checks the original EEG series for stationarity before calculations: draws graph to show
    that the EEG series and its autocorrelation function is typical for non-stationary time series"""
    datafrag = DataFragment(2000)
    datafrag.read_file()
    sequence = datafrag.get_fragment()[:, 1]
    draw_graphs.draw_EEG_sample(seq=sequence, electrode_num=0)
    seq_title = "electrode {} data".format(electrode)
    draw_graphs.test_autocorr_stationarity_shift(sequence, max_lag=400, seq_title=seq_title)

EEG_stationarity_check()

# find optimal sample length for further calculations
constants = ProcessingConstants(sliding_step=shift, electrode=electrode)
#calculates how many pieces do we divide eeg into
number_samples = InputFileLen.get_sample_number(sample_len=constants.statistic_gap)

# main calculations: divides EEG into samples, moves sliding window along every sample with step = shift
# and finds the stationary distance of every sample using Kolmogorov-Smirnov test
c1norm = eeg_calculate.C1norm_sequence(number_samples, constants)
sequence = c1norm.get_C1norm_sequence()
sequence = sequence.reshape(1, sequence.size)

# write calculated sequence of stationary points to file
with open(Filenames.filenames['stationary_point_seq'], 'w') as file:
    np.savetxt(file, sequence, fmt='%12.6f')
    print("sequence of stationary points is written to {}".format("Filenames.filenames['stationary_point_seq']"))

sequence = np.loadtxt(Filenames.filenames['stationary_point_seq'])

# draw calculated stationary sequence - it's main result of EEG processing
draw_graphs.draw_stationary_point_seq(filename=Filenames.filenames['stationary_point_seq'],\
            electrode=electrode, shift=shift)
# as we haven't enough data to perform autocorrelation test for stationarity, we perform L1 test
# to demonstarate that calculated sequence is stationary; for stationary process result should be < 0.4
print("stationarity check using L1 norm distance:", eeg_calculate.test_L1norm_stationarity(sequence))
# and also perform test using dispersion: for stationary series it doesn't vary a lot with time
draw_graphs.test_variance_stationarity(sequence=sequence, seq_title='stationary point sequence of EEG series')


import sys
sys.path.append('/Data_Structures/')
from stacks.stack import Stack
from queue import QueueUnderflowError, QueueOverflowError

class StackQueue:
    """
    Implementation of Queue using two Stacks.
    """

    def __init__(self, limit=10):
        self.limit = limit
        self.stack_one = Stack(limit)
        self.stack_two = Stack(limit)

    def enqueue(self, item):
        if self.isFull():
            raise QueueOverflowError("Cannot insert item in a full queue.")
        self.stack_one.push(item)

    def _move_item_to_second_stack(self):
        if self.stack_two.is_empty():
            while not self.stack_one.is_empty():
                self.stack_two.push(self.stack_one.pop())

    def dequeue(self):
        if self.isEmpty():
            raise QueueUnderflowError(
                "Cannot Remove an item from empty Queue.")
        self._move_item_to_second_stack()
        return self.stack_two.pop()

    def peek(self):
        self._move_item_to_second_stack()
        return self.stack_two.peek()

    def isEmpty(self):
        return self.stack_one.is_empty() and self.stack_two.is_empty()

    def isFull(self):
        return (self.stack_one.size() + self.stack_two.size()) >= self.limit

if __name__ == "__main__":
    queue = StackQueue(3)
    queue.enqueue(10)
    queue.enqueue(20)
    print(queue.dequeue())
    print(queue.peek())
    queue.enqueue(30)
    print(queue.dequeue())
    print(queue.peek())


import struct
import matplotlib.pyplot as plt
import numpy as np

def read(file):
    with open(file, "rb") as f:
        buff = f.read()
        print(len(buff))
        data = struct.unpack("%dB" % (108000 * 10), buff)
        data = np.array(data).reshape(10, 600, 180)
        return data

if __name__ == "__main__":
    file = "c_0.35_0.02_180_600_108000_1_100.bin"
    data = read(file)
    for frame in data:
        plt.imshow(frame.T, origin="lower")
        plt.show()
        plt.close()


import pygame

from Util.window import Window

def text_ob(text, font, color):
    textforScreen = font.render(text, True, color)
    return textforScreen, textforScreen.get_rect()

def game_over():
    """"Create Game Over Screen"""
    WIN = Window.WIN
    WHITE = (255, 255, 255)
    BLACK = (0, 0, 0)

    end = True

    while end:
        for event in pygame.event.get():
            print(event)

            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

        WIN.fill(BLACK)
        text = pygame.font.SysFont('Times New Roman', 100)
        Text, TextRect = text_ob("Game Over", text, WHITE)
        TextRect.center = ((Window.WIDTH / 2), (Window.HEIGHT / 2))
        WIN.blit(Text, TextRect)
        pygame.display.update()


_base_ = [
    '../_base_/models/fcos_van_fpn.py',
    '../_base_/datasets/finetune_coco_detection.py',
    '../_base_/default_runtime.py',
    '../_base_/schedules/schedule_1x_adam.py',
]

data_root = '/home/nils/VAN-Detection/datasets/coco/'
CLASSES = ('Audi_A7', 'Audi_RS_6_Avant')

dims = [32, 64, 160, 256]
# pretrained = dict(type='Pretrained', checkpoint='models/van_b0_bt_300.pth', prefix='backbone.')
# pretrained = dict(type='Pretrained', checkpoint='models/van_b0_bt_small_neck_1000.pth', prefix='backbone.')
pretrained = dict(type='Pretrained', checkpoint='models/van_b0_bt_thick_neck_1000.pth', prefix='backbone.')
# pretrained = None
norm_cfg = dict(type='SyncBN', requires_grad=not bool(pretrained))  # train BN only when not using pretrained model
image_size = (1024, 1024)
# image_size = (1333, 800)
file_client_args = dict(backend='disk')
img_norm_cfg = dict(mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)

model = dict(
    type='FCOS',
    backbone=dict(
        type='VAN',
        _delete_=True,
        arch='b0',
        out_indices=[0,1,2,3],
        init_cfg=pretrained,
      ),
    neck=dict(
        type='FPN',
        in_channels=dims,
        out_channels=256,
        start_level=1,
        add_extra_convs='on_output',  # use P5
        num_outs=5,
        relu_before_extra_convs=True),
    bbox_head=dict(
        type='FCOSHead',
        num_classes=len(CLASSES),
        in_channels=256,
        stacked_convs=4,
        feat_channels=256,
        strides=[8, 16, 32, 64, 128],
        loss_cls=dict(
            type='FocalLoss',
            use_sigmoid=True,
            gamma=2.0,
            alpha=0.25,
            loss_weight=1.0),
        loss_bbox=dict(type='IoULoss', loss_weight=1.0),
        loss_centerness=dict(
            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)),
    # training and testing settings
    train_cfg=dict(
        assigner=dict(
            type='MaxIoUAssigner',
            pos_iou_thr=0.5,
            neg_iou_thr=0.4,
            min_pos_iou=0,
            ignore_iof_thr=-1),
        allowed_border=-1,
        pos_weight=-1,
        debug=False),
    test_cfg=dict(
        nms_pre=1000,
        min_bbox_size=0,
        score_thr=0.05,
        nms=dict(type='nms', iou_threshold=0.5),
        max_per_img=100)
        )

hooks=[
    dict(type='TextLoggerHook', by_epoch=True),
    dict(type='MMDetWandbHook',
      by_epoch=True,
      init_kwargs=dict(
        entity="nkoch-aitastic",
        project='van-detection',
        tags=[
          'backbone:VAN-B0',
          'neck:FPN',
          'head:FCOS',
          'pretrained',
          'schedule:1x',
          'finetune-copy-paste',
          'barlow-twins:stanford-cars',
          'less-classes',
          ]
      ),
      interval=10,
      log_checkpoint=True,
      log_checkpoint_metadata=True,
      num_eval_images=100,
    ), # Check https://docs.wandb.ai/ref/python/init for more init arguments.
    ]

albu_train_transforms = [
    dict(
        type='OneOf',
        transforms=[
            dict(
                type='Resize',
                width=image_size[1],
                height=image_size[0],
            ),
            dict(
                type='RandomResizedCrop',
                width=image_size[1],
                height=image_size[0],
            ),
        ],
        p=1.0
    ),
    dict(
        type='ShiftScaleRotate',
        scale_limit=[-0.9,0.0],
        rotate_limit=0,
        border_mode=0,
        rotate_method='ellipse',
        interpolation=1,
        p=0.5),
    dict(
        type='RandomBrightnessContrast',
        brightness_limit=[0.1, 0.3],
        contrast_limit=[0.1, 0.3],
        p=0.5),
    dict(
        type='OneOf',
        transforms=[
            dict(
                type='RGBShift',
                p=1.0),
            dict(
                type='HueSaturationValue',
                hue_shift_limit=128,
                sat_shift_limit=128,
                val_shift_limit=128,
                p=1.0),
        ],
        p=0.9),
    dict(type='ImageCompression', quality_lower=45, quality_upper=95, p=0.3),
    dict(type='ChannelShuffle', p=0.4),
    dict(type='GaussNoise', p=0.8),
    dict(
        type='OneOf',
        transforms=[
            dict(type='AdvancedBlur', p=1.0),
            dict(type='MedianBlur', blur_limit=3, p=1.0),
        ],
        p=0.4),
    dict(
        type='ColorJitter',
        ),
    dict(
        type='RandomCrop',
        width=400,
        height=400,
        p=0.2,
        ),
    dict(
        type='Flip',
        ),
    dict(
        type='Resize',
        width=image_size[1],
        height=image_size[0],
        ),
    dict(
        type='PadIfNeeded',
        min_width=image_size[1],
        min_height=image_size[0],
        border_mode=0,
        p=1.0,
        ),
]

test_pipeline = [
    dict(
        type='FineTuneCopyPaste',
        max_num_pasted=100,
        copy_paste_chance=0.8,
        supl_dataset_cfg=dict(
            ann_file='/home/nils/datasets/cars/coco/train.json',
            data_root='/home/nils/datasets/cars/',
            img_prefix='raw',
            pipeline=[
                dict(type='LoadImageFromFile', file_client_args=file_client_args),
                dict(type='LoadAnnotations', with_bbox=True, with_mask=True),
                dict(
                    type='Albu',
                    transforms=albu_train_transforms,
                    bbox_params=dict(
                        type='BboxParams',
                        format='pascal_voc',
                        label_fields=['gt_labels'],
                        min_visibility=0.0,
                        filter_lost_elements=True,
                    ),
                    update_pad_shape=False,
                    skip_img_without_anno=False,
                ),
            ],
            classes=CLASSES,
        ),
    ),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='DefaultFormatBundle'),
    dict(type='Collect', keys=['img']),
]

train_pipeline = [
    dict(
        type='FineTuneCopyPaste',
        max_num_pasted=100,
        copy_paste_chance=0.8,
        supl_dataset_cfg=dict(
            ann_file='/home/nils/datasets/cars/coco/train.json',
            data_root='/home/nils/datasets/cars/',
            img_prefix='raw',
            pipeline=[
                dict(type='LoadImageFromFile', file_client_args=file_client_args),
                dict(type='LoadAnnotations', with_bbox=True, with_mask=True),
                dict(
                    type='Albu',
                    transforms=albu_train_transforms,
                    bbox_params=dict(
                        type='BboxParams',
                        format='pascal_voc',
                        label_fields=['gt_labels'],
                        min_visibility=0.0,
                        filter_lost_elements=True,
                    ),
                    update_pad_shape=False,
                    skip_img_without_anno=False,
                ),
            ],
            classes=CLASSES,
        ),
    ),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='DefaultFormatBundle'),
    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),
]

load_pipeline = [
    dict(type='LoadImageFromFile', file_client_args=file_client_args),
    dict(type='LoadAnnotations', with_bbox=True, with_mask=True),
    dict(
        type='Resize',
        img_scale=image_size,
        # ratio_range=(0.8, 1.25),
        # multiscale_mode='range',
        keep_ratio=False,
        ),
    dict(
        type='RandomCrop',
        crop_type='absolute_range',
        crop_size=image_size,
        recompute_bbox=True,
        allow_negative_crop=True),
    dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)),
    dict(type='RandomFlip', flip_ratio=0.5),
    # dict(type='Pad', size_divisor=32),
    dict(type='Pad', size=image_size),
]

data = dict(
    samples_per_gpu=4,
    train=dict(
        dataset=dict(
            classes=CLASSES,
            filter_empty_gt=False,
        ),
        pipeline=train_pipeline,
    ),
    val=dict(
        classes=CLASSES,
    ),
    test=dict(
        classes=CLASSES,
    ),
)

    # hooks = [
    # dict(type='TextLoggerHook', by_epoch=True),
# ]
log_config = dict(
    hooks=hooks,
)

# # FIXME This crashes right now due to incorrect mapping of classes during evaluation
# # We skip this hook until we're done training, so we at least get something done...
# evaluation = dict(interval=12)
# checkpoint_config = dict(interval=12)

workflow = [('train', 1)]
# data = dict(samples_per_gpu=4)


import pandas as pd
import seaborn as sns
import numpy as np
from functools import partial
from multiprocessing import Pool
from matplotlib import pyplot as plt
import pybedtools as pbt

atac = snakemake.input['atac_peaks']
h3k4me1 = snakemake.input['h3k4me1_peaks']
h3k4me3 = snakemake.input['h3k4me3_peaks']
h3k27me3 = snakemake.input['h3k27me3_peaks']
h3k27ac = snakemake.input['h3k27ac_peaks']
chrom_sizes = snakemake.input['chrom_sizes']
ncpus = snakemake.resources['cpus']

out_png = snakemake.output['png']
out_csv = snakemake.output['csv']

def calculate_metrics(atac_peaks, RP_count, RN_count, scores):
    ratio_count_df = pd.DataFrame(columns=['cutoff', 'ratio', 'value'])
    for cutoff in scores:
        filtered = atac_peaks[atac_peaks['score'] > cutoff]
        active_overlap = active.intersect(pbt.bedtool.BedTool.from_dataframe(filtered), u = True).count()
        inactive_overlap = inactive.intersect(pbt.bedtool.BedTool.from_dataframe(filtered), u = True).count()
        ratio_count_df = pd.concat([ratio_count_df, pd.DataFrame(data = {'cutoff': [cutoff], 'ratio': 'active', 'value': [active_overlap]})])
        ratio_count_df = pd.concat([ratio_count_df, pd.DataFrame(data = {'cutoff': [cutoff], 'ratio': 'inactive', 'value': [inactive_overlap]})])
    ratio_count_df['cutoff'] = ratio_count_df['cutoff'].astype(int)
    ratio_count_df['value'] = ratio_count_df['value'].astype(int)
    ratio_count_df_pivot = ratio_count_df.pivot_table(index = 'cutoff', columns = 'ratio', values = 'value')
    ratio_count_df_pivot.columns = ['TP', 'FP']
    ratio_count_df_pivot['precision'] = ratio_count_df_pivot['TP']/(ratio_count_df_pivot['TP'] + ratio_count_df_pivot['FP'])
    ratio_count_df_pivot['TPR'] = ratio_count_df_pivot['TP']/RP_count
    ratio_count_df_pivot['FPR'] = ratio_count_df_pivot['FP']/RN_count
    return ratio_count_df_pivot

atac_peaks = pd.read_csv(atac, sep = '\t', names = ['chr', 'start', 'end', 'strand', 'score'])

h3k4me1_peaks = pbt.BedTool(h3k4me1).slop(b = 200, g = chrom_sizes)
h3k4me3_peaks = pbt.BedTool(h3k4me3).slop(b = 200, g = chrom_sizes)
h3k27ac_peaks = pbt.BedTool(h3k27ac).slop(b = 200, g = chrom_sizes)
h3k27me3_peaks = pbt.BedTool(h3k27me3).slop(b = 200, g = chrom_sizes)

active = (h3k4me1_peaks.merge(i=h3k4me3_peaks) + h3k27ac_peaks - h3k27me3_peaks).sort().merge()
inactive = h3k27me3_peaks.sort().merge()
RP_count = active.count()
RN_count = inactive.count()

f = partial(calculate_metrics, atac_peaks, RP_count, RN_count)

with Pool(ncpus) as pool:
    res = pool.map(f, range(0,int(atac_peaks['score'].quantile(.99)), 1), chunksize = 1e2)

ratio_count_df_pivot = pd.concat(list(res))
ratio_count_df_pivot.to_csv(out_csv)

fig, ax = plt.subplots(figsize = (6, 4))
sns.lineplot(data = ratio_count_df_pivot, x = 'FPR', y = 'TPR', ax = ax)
lims = [
    np.min([ax.get_xlim(), ax.get_ylim()]),
    np.max([ax.get_xlim(), ax.get_ylim()]),
]

ax.plot(lims, lims, 'k-', alpha=0.75, zorder=0)
ax.set_aspect('equal')
ax.set_xlim(lims)
ax.set_ylim(lims)
ax.set_xlabel('FPR', fontsize = 20)
ax.set_ylabel('TPR', fontsize = 20)
ax.set_title('ROC Curve', fontsize = 30)
fig.savefig(out_png, dpi = 300)