'''
The Jira class in this module is the primary abstraction around the Jira API.
'''
import collections.abc
import dataclasses
import decimal
import json
import logging
import os
from typing import cast, Dict, Hashable, List, Optional, Set

import numpy
import pandas as pd
from peak.util.proxies import LazyProxy
import pytz

from jira_offline.config import get_cache_filepath, load_config
from jira_offline.config.user_config import apply_user_config_to_project
from jira_offline.exceptions import JiraApiError, MultipleTimezoneError, ProjectDoesntExist
from jira_offline.models import (AppConfig, CustomFields, Issue, IssueType, IssueUpdate, ProjectMeta,
                                 Sprint)
from jira_offline.sql_filter import IssueFilter
from jira_offline.utils import iter_issue_fields_by_type
from jira_offline.utils.api import get as api_get, post as api_post, put as api_put
from jira_offline.utils.convert import jiraapi_object_to_issue
from jira_offline.utils.decorators import auth_retry
from jira_offline.utils.serializer import istype

logger = logging.getLogger('jira')

# Create single Jira object instance for this invocation of the app
# Made available to all modules via simple python import: `from jira_offline.jira import jira`
jira = LazyProxy(lambda: Jira())  # pylint: disable=unnecessary-lambda

class Jira(collections.abc.MutableMapping):
    _df: pd.DataFrame

    filter: IssueFilter

    def __init__(self):
        self.store = dict()

        # Create the underlying storage for persisting Issues
        self._df = pd.DataFrame()

        # Load application config without prompting
        self.config: AppConfig = load_config()

        # Initialise an empty filter
        self.filter = IssueFilter()

    def _get_full_key(self, key: str) -> str:
        # If key is an abbreviated UUID, load full key from the DataFrame
        full_key = self._df.index[(self._df.key.str.len() == 36) & self._df.index.str.startswith(key)].any()

        if full_key:
            return cast(str, full_key)
        return key

    def __getitem__(self, key: str) -> Issue:
        key = self._get_full_key(key)

        series = self._df.loc[key]
        return Issue.from_series(
            series,
            project=self.config.projects[series['project_id']]
        )

    def __setitem__(self, key: str, issue: Issue):
        key = self._get_full_key(key)

        series = issue.to_series()
        self._df.loc[key] = series

    def __delitem__(self, key: str):
        key = self._get_full_key(key)

        self._df.drop(key, inplace=True)

    def __iter__(self):
        return (k for k, row in self._df.iterrows())

    def __len__(self):
        return len(self._df)

    def __contains__(self, key):
        # Check if key is an abbreviated UUID
        if self._df.index[(self._df.key.str.len() == 36) & self._df.index.str.startswith(key)].any():
            return True

        return key in self._df.index

    @property
    def df(self) -> pd.DataFrame:
        return self.filter.apply()

    def update(self, issues: List[Issue]):  # type: ignore[override] # pylint: disable=arguments-differ
        '''
        Merge another DataFrame of Issues to the store. New issues are appended to the underlying
        DataFrame and existing modified issues are updated in-place.

        This method is called during sync with Jira server.

        Notably this method _does not_ use the logic in Issue.to_series() for performance reasons;
        it's much faster to build the DataFrame and operate on that, than to process each Issue in a
        tight loop and then create a DataFrame.
        '''
        # Validate all timezones are the same for this DataFrame, as the following tz_convert() call
        # fails when they differ.
        # It's not possible for a single Jira to return issues with multiple different timezones.
        if not all(i.created.tzinfo == issues[0].created.tzinfo for i in issues):  # type: ignore[union-attr]
            raise MultipleTimezoneError
        if not all(i.updated.tzinfo == issues[0].updated.tzinfo for i in issues):  # type: ignore[union-attr]
            raise MultipleTimezoneError

        # Convert list of Issues into a dict
        data = {}
        for issue in issues:
            data[issue.key] = issue.__dict__
            if data[issue.key]['sprint']:
                data[issue.key]['sprint'] = [s.serialize() for s in data[issue.key]['sprint']]

        # Construct a DataFrame from the dict, fill any NaNs with blank
        df = pd.DataFrame.from_dict(data, orient='index').fillna('')

        # Convert all datetimes to UTC
        for col in ('created', 'updated'):
            df[col] = df[col].dt.tz_convert('UTC')  # pylint: disable=unsubscriptable-object,unsupported-assignment-operation

        # Extract ProjectMeta.key into a new string column named `project_key`
        df.loc[:, 'project_key'] = [p.key if p else None for p in df['project']]  # pylint: disable=unsubscriptable-object

        # Drop columns for fields marked repr=False
        df.drop(
            columns=[f.name for f in dataclasses.fields(Issue) if f.repr is False],
            inplace=True,
        )

        # Render modified as a string for storage in the DataFrame
        df['modified'] = df['modified'].apply(lambda x: json.dumps(x) if x else numpy.nan)  # pylint: disable=unsubscriptable-object,unsupported-assignment-operation

        # Add an empty column to for Issue.original
        df['original'] = ''  # pylint: disable=unsupported-assignment-operation

        # Append any new issues
        self._df = pd.concat([ self._df, df[~df.key.isin(self._df.index)] ])  # pylint: disable=unsubscriptable-object

        # In-place update for modified issues
        self._df.update(df)

        # Specify Pandas datatypes
        type_mappings = {}
        for f in iter_issue_fields_by_type(str, int):
            # Cast for mypy as istype uses @functools.lru_cache
            typ_ = cast(Hashable, f.type)

            if istype(typ_, (decimal.Decimal, str)):
                type_mappings[f.name] = 'string'
            elif istype(typ_, int):
                type_mappings[f.name] = 'Int64'
            else:
                type_mappings[f.name] = 'object'
        self._df = self._df.astype(type_mappings)

        # Customfields are stored as a dict in the extended column of the DataFrame
        self._df = self._expand_customfields(self._df)

        # Persist new data to disk
        self.write_issues()

    class KeysView(collections.abc.KeysView):
        '''Override KeysView to enable filtering via __iter__'''
        def __init__(self, jira_, filter_):
            self.filter = filter_
            super().__init__(jira_)

        def __iter__(self):
            for key in self.filter.apply().index:
                yield key

    class ItemsView(collections.abc.ItemsView):
        '''Override ItemsView to enable filtering via __iter__'''
        def __init__(self, jira_, filter_):
            self.filter = filter_
            super().__init__(jira_)

        def __iter__(self):
            for key in self.filter.apply().index:
                yield (key, self._mapping[key])

    class ValuesView(collections.abc.ValuesView):
        '''Override ValuesView to enable filtering via __iter__'''
        def __init__(self, jira_, filter_):
            self.filter = filter_
            super().__init__(jira_)

        def __iter__(self):
            for key in self.filter.apply().index:
                yield self._mapping[key]

    def keys(self):
        return Jira.KeysView(self, self.filter)

    def items(self):
        return Jira.ItemsView(self, self.filter)

    def values(self):
        return Jira.ValuesView(self, self.filter)

    def is_new(self):
        return self.df.id == 0

    def is_modified(self):
        return ~self.df.modified.isna()

    def _expand_customfields(self, df: pd.DataFrame) -> pd.DataFrame:  # pylint: disable=no-self-use
        '''
        Customfields are stored as a dict in the extended column of the DataFrame.

        Expand the key-value pairs from the dict into columns.
        '''
        # Drop all extended columns, previously created via this method
        df1 = df.drop(columns=df.columns[df.columns.str.startswith('extended.')])

        # Expand extended dict into columns in a new DataFrame
        # Prefix each field with "extended."
        df2 = df['extended'].apply(pd.Series).add_prefix('extended.').fillna('')

        # Merge the customfields columns onto the core DataFrame
        return pd.merge(df1, df2, left_index=True, right_index=True)

    def _contract_customfields(self, df: pd.DataFrame) -> pd.DataFrame:  # pylint: disable=no-self-use
        '''
        Customfields are stored as a dict in the extended column of the DataFrame.

        Drop DataFrame columns created in `_expand_customfields`, and cleanup the data where
        an extended customfield is None for every issue.
        '''
        # Drop the extended columns, which were created via `self._expand_customfields`
        df = df.drop(columns=df.columns[df.columns.str.startswith('extended.')])

        # Expand the "extended" column into a new Series
        df_extended = df['extended'].apply(pd.Series)

        # Nothing more to do, if there are no extended fields with values
        if df_extended.empty:
            return df

        # Drop columns which are None for every row
        df_extended.drop(columns=df_extended.loc[:, df_extended.isnull().all(axis=0)].columns, inplace=True)

        # Combine back into a dict and update the "extended" column
        if df_extended.empty:
            df['extended'] = ''
        else:
            df['extended'] = df_extended.to_dict('records')

        return df

    def load_issues(self) -> None:
        '''
        Load issues from feather cache file, and store in underlying pandas DataFrame.
        '''
        if not self._df.empty:
            return

        cache_filepath = get_cache_filepath()
        if os.path.exists(cache_filepath) and os.stat(cache_filepath).st_size > 0:
            self._df = pd.read_feather(
                cache_filepath
            ).set_index('key', drop=False).rename_axis(None)

            # Add an empty column to for Issue.original
            self._df['original'] = ''

            # Customfields are stored as a dict in the extended column of the DataFrame
            self._df = self._expand_customfields(self._df)

        else:
            # Handle the case where an empty project has been cloned, which results in no Feather
            # file on disk.
            self._df = pd.DataFrame(columns=[
                'project_id', 'issuetype', 'summary', 'key', 'assignee', 'created',
                'creator', 'description', 'fix_versions', 'components', 'id', 'labels',
                'priority', 'reporter', 'status', 'updated', 'epic_link', 'epic_name',
                'sprint', 'story_points', 'extended', 'modified', 'project_key',
                'parent_link', 'original', 'transitions',
            ])

    def write_issues(self):
        '''
        Dump issues to feather cache file.
        '''
        # Make a copy of the DataFrame, for munging before write
        # Don't write out the original field as it can be recreated from Issue.modified
        df = self._df.drop(columns=['original'])

        # Cleanup extended customfields columns
        df = self._contract_customfields(df)

        # PyArrow does not like sets
        for col in ('components', 'fix_versions', 'labels', 'sprint'):
            df[col] = df[col].apply(list)

        # PyArrow does not like decimals
        df['story_points'] = df['story_points'].astype('string')

        cache_filepath = get_cache_filepath()
        df.reset_index(drop=True).to_feather(cache_filepath)

    @auth_retry()
    def get_project_meta(self, project: ProjectMeta):  # pylint: disable=no-self-use
        '''
        Load additional Jira project meta data from Jira's createmeta API

        Params:
            project:  Jira project object into which we should load additional metadata
        '''
        # Some user-defined config applies to specific projects; ensure that this project is
        # configured correctly
        apply_user_config_to_project(self.config, project)

        try:
            params = {'projectKeys': project.key, 'expand': 'projects.issuetypes.fields'}
            data = api_get(project, '/rest/api/2/issue/createmeta', params=params)
            if not data.get('projects'):
                raise ProjectDoesntExist(project.key)

            # Project friendly name
            project.name = data['projects'][0]['name']

            # Jira's internal ID for this project
            project.jira_id = data['projects'][0]['id']

            issuetypes_: Dict[str, IssueType] = dict()
            priorities_: Set[str] = set()

            # Extract set of issuetypes, and their priority values returned from the createmeta API
            for x in data['projects'][0]['issuetypes']:
                issuetypes_[x['name']] = IssueType(name=x['name'])

                # Priority is a project-level setting, which can be extracted from any issue with the
                # "priority" field
                if not priorities_ and x['fields'].get('priority'):
                    priorities_ = {y['name'] for y in x['fields']['priority']['allowedValues']}

            # Update project issuetypes & priorities to latest defined on Jira
            project.issuetypes = issuetypes_
            project.priorities = priorities_

            # Map all customfields for this project
            self._load_customfields(project, data['projects'][0]['issuetypes'])

            # Pull project statuses for issue types
            self._get_project_issue_statuses(project)

            # Pull project components
            self._get_project_components(project)

            # Pull user's configured timezone from their profile and store on the ProjectMeta
            tz = self._get_user_timezone(project)
            if tz is not None:
                project.timezone = pytz.timezone(tz)

            # Retrieve the list of available sprints on this project
            self._get_sprints(project)

        except (IndexError, KeyError) as e:
            raise JiraApiError((
                f'Missing or bad project meta returned for {project.key} with error '
                f'"{e.__class__.__name__}({e})"'
            ))

    def _load_customfields(self, project: ProjectMeta, issuetypes_data: dict):
        '''
        Load the customfields for this project from the Jira API.

        User-specified customfields are added to jira-offline.ini and can be accessed via
        `jira.config.customfields`. Projects can have 0-N configured customfields, this method
        does the mapping between the two.

        Params:
            project:          Jira project to query
            issuetypes_data:  Data from Jira API describing issuetypes on this project
        '''
        project_customfields = {}

        # Extract custom fields from the API response for each issuetype on this project
        for issuetype in issuetypes_data:
            for name, field_props in issuetype['fields'].items():
                if name.startswith('customfield_'):
                    project_customfields[name] = field_props

        logger.debug('Customfields for project %s are %s', project.key, project_customfields)

        customfield_epic_name = customfield_epic_link = customfield_sprint = customfield_parent_link = ''

        # Epic Name, Epic Link & Sprint are "locked" custom fields, and so should always exist
        for name, field_props in project_customfields.items():
            if field_props['name'] == 'Epic Name':
                customfield_epic_name = field_props.get('fieldId', field_props.get('key'))
            elif field_props['name'] == 'Epic Link':
                customfield_epic_link = field_props.get('fieldId', field_props.get('key'))
            elif field_props['name'] == 'Sprint':
                customfield_sprint = field_props.get('fieldId', field_props.get('key'))
            elif field_props['name'] == 'Parent Link':
                customfield_parent_link = field_props.get('fieldId', field_props.get('key'))

        # Initialise project's customfields
        project.customfields = CustomFields(
            epic_name=customfield_epic_name,
            epic_link=customfield_epic_link,
            sprint=customfield_sprint,
            parent_link=customfield_parent_link,
        )

        def apply_customfield_config(name, value):
            '''
            Apply user-defined customfield configuration to this Jira project, if mapped in the Jira
            project metadata.

            Customfields must be mapped on the Jira server to this project _and_ be set in the
            user's configuration.
            '''
            # Check to ensure the user-defined customfield is in use on this project
            if value in project_customfields:
                # Replace field name dashes with underscores
                name = name.replace('-', '_')

                if hasattr(project.customfields, name):
                    setattr(project.customfields, name, value)
                else:
                    project.customfields.extended[name] = value

        # First, apply global customfield mappings to this project (if set)
        if '*' in self.config.user_config.customfields:
            customfield_mapping = self.config.user_config.customfields['*']
            for name, value in customfield_mapping.items():
                apply_customfield_config(name, value)

        # Second, apply per-Jira host and per-project customfield mappings to this project, in order
        for match in ('hostname', 'key'):
            for target, customfield_mapping in self.config.user_config.customfields.items():
                if target == getattr(project, match):
                    for name, value in customfield_mapping.items():
                        apply_customfield_config(name, value)

    def _get_sprints(self, project: ProjectMeta):  # pylint: disable=no-self-use
        '''
        Retrieve and parse the sprints mapped to issues on this project

        Params:
            project:  The project to query for timezone info
        '''
        if not project.board_id:
            logger.info('To use the sprint field, set your board in config.issue.board_id')
            return

        sprints = {}

        try:
            sprint_data = api_get(project, f'/rest/agile/1.0/board/{project.board_id}/sprint')
            for s in sprint_data['values']:
                sprints[int(s['id'])] = Sprint(id=s['id'], name=s['name'], active=bool(s['state'] == 'active'))

        except JiraApiError as e:
            if 'The board does not support sprints' not in str(e):
                logger.error('Configured board_id does not support sprints!')
                return
            else:
                raise

        project.sprints = sprints

    def _get_user_timezone(self, project: ProjectMeta) -> Optional[str]:  # pylint: disable=no-self-use
        '''
        Retrieve user-specific timezone setting

        Params:
            project:  Jira project to query. Timezone is a server setting, but in this application
                      it's stored per-project.
        '''
        data = api_get(project, '/rest/api/2/myself')
        return data.get('timeZone')

    def _get_project_issue_statuses(self, project: ProjectMeta):  # pylint: disable=no-self-use
        '''
        Pull valid statuses for each issuetype in this project

        Params:
            project:  Jira project to query
        '''
        data = api_get(project, f'/rest/api/2/project/{project.key}/statuses')

        for obj in data:
            try:
                issuetype = project.issuetypes[obj['name']]
                issuetype.statuses = {x['name'] for x in obj['statuses']}
            except KeyError:
                logger.debug('Unknown issuetype "%s" returned from /project/{project.key}/statuses', obj['name'])

    def _get_project_components(self, project: ProjectMeta):  # pylint: disable=no-self-use
        '''
        Pull set of components for this project

        Params:
            project:  Jira project to query
        '''
        data = api_get(project, f'/rest/api/2/project/{project.key}/components')
        project.components = {x['name'] for x in data}

    def new_issue(self, project: ProjectMeta, fields: dict, offline_temp_key: str) -> Issue:
        '''
        Create a new issue on a Jira project via the API. A POST request is immediately followed by a
        GET for the new issue, to retrive the new key and other default fields provided by the Jira
        server.

        Params:
            project:           Jira project on which to create new Issue
            fields:            K/V pairs for the issue; output from `utils.convert.issue_to_jiraapi_update`
            offline_temp_key:  Temporary key created for this issue until it's sync'd to Jira
        Returns:
            The new Issue, including the Jira-generated key field
        '''
        # Transitions are not valid for new issues
        if 'transitions' in fields:
            del fields['transitions']

        # Create new issue in Jira
        data = api_post(project, '/rest/api/2/issue', data={'fields': fields})

        # Retrieve the freshly minted Jira issue
        new_issue: Issue = self.fetch_issue(project, data['key'])

        if new_issue.key is None:
            # This code path is not reachable, as the `key` field is mandatory on the Jira API. This
            # is included to keep the type-checker happy
            raise Exception

        # Add to self under the new key
        self[new_issue.key] = new_issue

        for link_name in ('epic_link', 'parent_link'):
            # Re-link all issues to the new Jira-generated key
            for key in self._df.loc[self._df[link_name] == offline_temp_key, 'key']:
                linked_issue = jira[key]
                setattr(linked_issue, link_name, new_issue.key)
                linked_issue.commit()

        # Remove the placeholder Issue
        del self[offline_temp_key]

        # Write changes to disk
        self.write_issues()

        return new_issue

    def update_issue(self, project: ProjectMeta, update_obj: IssueUpdate):
        '''
        Update an issue on a Jira project via the API. A PUT request is immediately followed by a
        GET for the updated issue - which is rather heavyweight, but ensures that issue timestamps
        are correct as they can only be set by the Jira server.

        Params:
            project:     Jira project which owns the issue to update
            update_obj:  IssueUpdate DTO representing issue changes
        '''
        api_put(
            project, f'/rest/api/2/issue/{update_obj.merged_issue.key}',
            data={'fields': update_obj.fields}
        )

        if 'status' in update_obj.modified:
            api_post(
                project, f'/rest/api/2/issue/{update_obj.merged_issue.key}/transitions',
                data={'transition': update_obj.transition}
            )

        # Jira is now updated to match local; synchronise offline issue to match the server version
        self[update_obj.merged_issue.key] = self.fetch_issue(project, update_obj.merged_issue.key)

        # Write changes to disk
        self.write_issues()

    def fetch_issue(self, project: ProjectMeta, key: str) -> Issue:  # pylint: disable=no-self-use
        '''
        Return a single Issue object from the Jira API by key

        Params:
            project:  Jira project on which to lookup the issue
            key:      Issue key to lookup on Jira API
        Returns:
            Issue dataclass instance
        '''
        issue_data = api_get(project, f'/rest/api/2/issue/{key}', params={'expand': 'transitions'})
        return jiraapi_object_to_issue(project, issue_data)


# from django.shortcuts import render
from .models import Snack
from django.urls import reverse_lazy
from django.views.generic import DeleteView, UpdateView, CreateView, DetailView, ListView

# Create your views here.
class SnackListView(ListView):
    template_name = 'snackList.html'
    model = Snack
    success_url = reverse_lazy('listView')

class SnackDetailView(DetailView):
    template_name = 'snackDetail.html'
    model = Snack
    success_url = reverse_lazy('listView')

class SnackCreateView(CreateView):
    template_name = 'snackCreate.html'
    model = Snack
    fields = ['title', 'description', 'purchaser' ]
    success_url = reverse_lazy('listView')

class SnackDeleteView(DeleteView):
    template_name = 'snackDelete.html'
    model = Snack
    success_url = reverse_lazy('listView')

class SnackUpdateView(UpdateView):
    template_name = 'snackUpdate.html'
    model = Snack
    fields = ['title', 'description', 'purchaser' ]
    success_url = reverse_lazy('listView')


import logging

from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _

from horizon import forms
from horizon import messages
from horizon import exceptions
from horizon.utils import validators

from openstack_dashboard import api

LOG = logging.getLogger(__name__)

def limit_port_range(port):
    if port not in range(10000, 65000):
        raise ValidationError(_("Enter an integer value "
                                "between 10000 and 65000"))

class FirewallForm(forms.SelfHandlingForm):
    #required_css_class = 'required form-float'
    required_css_class = 'form-horizontal'

    gateway_ip = forms.ChoiceField(label=_("Select Gateway"),
            widget=forms.SelectWidget(attrs={'style': 'width: 500px'}))
    gateway_port = forms.IntegerField(label=_("Gateway Port (10000-65000"),
            validators=[limit_port_range],
            widget=forms.TextInput(attrs={'style': 'width: 510px'}))
    service_port = forms.IntegerField(label=_("Service Port"), min_value=1,
            validators=[validators.validate_port_range],
            widget=forms.TextInput(attrs={'style': 'width: 510px'}))

    def __init__(self, request, *args, **kwargs):
        super(FirewallForm, self).__init__(request, *args, **kwargs)
        choices = []
        tmp_choices = []

        instance = kwargs['initial']['instance']
        gateways = kwargs['initial']['gateways']
        for gateway in gateways:
            item = (gateway.hostname, gateway.vext_ip)
            if gateway.int_dev != gateway.ext_dev or \
                    gateway.vext_ip != gateway.ext_ip:
                choices.append(item)
            else:
                if gateway.hostname == instance.host_server:
                    tmp_choices.append(item)

        if not choices:
            choices = tmp_choices

        if choices:
            choices.insert(0, ("", _("Select Gateway")))
        else:
            choices.insert(0, ("", _("No Gateway available")))
        self.fields['gateway_ip'].choices = choices

    def clean(self):
        data = super(forms.Form, self).clean()
        hostname = data.get("gateway_ip")
        gateway_port = data.get("gateway_port")
        service_port = data.get("service_port")
        if hostname and gateway_port and service_port:
            instance = self.initial['instance']
            try:
                api.daolicloud.firewall_exist(self.request,
                                              instance.id,
                                              hostname=hostname,
                                              gateway_port=gateway_port)
            except Exception as e:
                #msg = _("This gateway port already be used.")
                #raise forms.ValidationError(msg)
                raise forms.ValidationError(e.message)

        return data

    def handle(self, request, data):
        hostname = data['gateway_ip']
        gport = data['gateway_port']
        sport = data['service_port']
        instance = self.initial['instance']
        try:
            firewall = api.daolicloud.firewall_create(
                    request, instance.id, hostname, gport, sport)
            message = _('Create gateway %(gport)s, service %(sport)s to '
                         ' instance %(inst)s.') % {"gport": gport,
                                                   "sport": sport,
                                                   "inst": instance.name}
            messages.success(request, message)
            return firewall
        except Exception:
            redirect = reverse("horizon:project:edges:index")
            exceptions.handle(request,
                              _('Unable to add firewall.'),
                              redirect=redirect)


import io
import pickle
import pickletools
from typing import Tuple, Optional

import torch

def pack_weight(weight, bias):
    return (0, weight, bias)

def unpack_weight(packed):
    version, weight, bias = packed
    assert version == 0
    return weight, bias

# Bundled input aware.
class MyObj1(object):
  packed: Tuple[int, int, int]
  bundled: str

  def __init__(self, weight, bias, bundled):
    self.packed = pack_weight(weight, bias)
    self.bundled = bundled

  def __getstate__(self):
    print("__getstate__")
    weight, bias = unpack_weight(self.packed)
    return (
        weight,
        bias,
        self.bundled,
    )

  def __setstate__(self, state):
    print("__setstate__")
    self.packed = pack_weight(state[0], state[1])
    self.bundled = state[2]

ser = pickle.dumps(MyObj1(5, 6, "hi"))
pickletools.dis(io.BytesIO(ser))
loaded = pickle.loads(ser)
print()
print(loaded.bundled)


#!/usr/bin/python3

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

class SelectSpikes:
    '''SELECTSPIKES - Manually select spikes based on graphical threshold
    ss = SELECTSPIKES(tms, hei) creates a GUI to manually select spikes
    using a draggable threshold.
    idx = ss.run() runs the GUI and reports the result
    This is a re-implementation of the Matlab/Octave version I wrote a
    decade ago.'''
    def __init__(self, tms, hei):
        self.t = tms
        self.y = hei
        self.t0 = tms[0]
        self.t1 = tms[-1]
        tblue = np.array([np.mean(tms)])
        tgreen = np.array([np.mean(tms)])
        self.tdots = [tblue, tgreen]
        yblue = np.array([np.mean(np.abs(hei))])
        ygreen = np.array([2*np.mean(np.abs(hei))])
        self.ydots = [yblue, ygreen]
        self.colors = ['g', 'b']
        self.clickthr = 10

        self.fig, self.ax = plt.subplots(figsize=[15, 5])
        self.hdata = self.ax.plot(self.t, self.y, 'r.')
        self.hlines = []
        self.hdots = []
        for k in range(2):
            self.hlines.append(self.ax.plot(np.hstack((self.t0,
                                                       self.tdots[k],
                                                       self.t1)),
                                            np.hstack((self.ydots[k][0],
                                                       self.ydots[k],
                                                       self.ydots[k][-1])),
                                            self.colors[k]))
            self.hdots.append(self.ax.plot(self.tdots[k], self.ydots[k],
                                           self.colors[k] + '.',
                                           markersize=20))

        self.draggingdot = None # Or tuple (k, idx)
        self.cidp = self.fig.canvas.mpl_connect('button_press_event',
                                               self.onpress)
        self.cidr = self.fig.canvas.mpl_connect('button_release_event',
                                               self.onrelease)
        self.cidm = None

    def selection(self):
        '''SELECTION - Indices of included spikes
        idx = SELECTION(ss) returns the indices of the spikes between the
        lines.'''
        if np.mean(self.ydots[1]) < np.mean(self.ydots[0]):
            kk = [1, 0]
        else:
            kk = [0, 1]
        sgms_low = self.segments(kk[0])
        sgms_high = self.segments(kk[1])
        use = np.zeros(self.t.shape, dtype=bool)
        for n in range(len(self.t)):
            t = self.t[n]
            y = self.y[n]
            q, ylow = self.findsegment(t, sgms_low)
            q, yhigh = self.findsegment(t, sgms_high)
            if ylow is not None and yhigh is not None:
                use[n] = y>=ylow and y<=yhigh
        return np.nonzero(use)[0]

    def run(self, leaveopen=False):
        '''RUN - Show the figure and wait until clicked outside
        sel = RUN(ss) makes sure the figure is visible, lets the user interact
        with it until s/he clicks outside the axes area, then returns the
        selection. See also SELECTION.'''
        b = plt.isinteractive()
        plt.ion()
        plt.show()
        self.quit = False
        print('''Drag lines to enclose desired spikes:
Click on a line segments to add a new handle.
Drag a handle past another handle or edge of graph to make it go away.
Click in margin of figure to end interaction.
The lines are equivalent: it's OK for green to be above blue.
However, the lines should not cross.''')

        while not self.quit:
            plt.pause(1)
        if not leaveopen:
            plt.close(ss.fig)
        if not b:
            plt.ioff()
        return self.selection()

    def pixdist(self, dt, dy):
        '''PIXDIST - Pixel distance given data distance
        r = PIXDIST(ss, dt, dy) returns the Euclidean pixel distance
        between two graphical element given data spacing of (dt, dy)
        between them. DT and DY may be vectors, in which case R will
        be a vector as well.'''
        pixbox = self.ax.get_window_extent().bounds # l,t,w,h
        databox = self.ax.dataLim.bounds # l,t,w,h
        dt = pixbox[2]*dt/databox[2]
        dy = pixbox[3]*dy/databox[3]
        return np.sqrt(dt*dt + dy*dy)

    def nearestdot(self, t, y):
        '''NEARESTDOT - Identity of and distance to nearest dot
        k, idx, r = NEARESTBLUEDOT(ss, x, y) returns the indentity of
        and the distance to the dot nearest to (t, y).'''
        idx = []
        dd = []
        for k in range(2):
            dd1 = self.pixdist(t - self.tdots[k], y - self.ydots[k])
            idx1 = np.argmin(dd1)
            idx.append(idx1)
            dd.append(dd1[idx1])
        k = np.argmin(dd)
        return (k, idx[k], dd[k])

    def segments(self, k):
        '''SEGMENTS - List of segments in the k-th line
        sgm = SEGMENTS(ss, k) returns a list of segments in the k-th line.
        SGM is a list of tuples (t0, t1, y0, dy); the segment is defined
        on the interval [t0, t1] by y(t) = y0 + dy * (t-t0).'''
        sgm = []
        sgm.append((self.t0, self.tdots[k][0], self.ydots[k][0], 0))
        for i in range(len(self.tdots[k]) - 1):
            t0 = self.tdots[k][i]
            t1 = self.tdots[k][i+1]
            y0 = self.ydots[k][i]
            y1 = self.ydots[k][i+1]
            sgm.append((t0, t1, y0, (y1-y0)/(t1-t0)))
        sgm.append((self.tdots[k][-1], self.t1, self.ydots[k][-1], 0))
        return sgm

    def findsegment(self, t, sgms):
        for q in range(len(sgms)):
            t0, t1, y0, dy = sgms[q]
            if t>=t0 and t<=t1:
                return q, y0 + dy*(t-t0)
        return None, None

    def nearestsegment(self, t, y):
        dd = []
        idx = []
        for k in range(2):
            sgms = self.segments(k)
            q, yat = self.findsegment(t, sgms)
            if q is None:
                idx1 = None
                dd1 = np.inf
            else:
                idx1 = q
                dd1 = self.pixdist(0, y-yat)
            dd.append(dd1)
            idx.append(idx1)
        k = np.argmin(dd)
        return (k, idx[k], dd[k])

    def updatedotsandlines(self, k):
        self.hdots[k][0].set_data(self.tdots[k], self.ydots[k])
        self.hlines[k][0].set_data(np.hstack((self.t0,
                                              self.tdots[k],
                                              self.t1)),
                                   np.hstack((self.ydots[k][0],
                                              self.ydots[k],
                                              self.ydots[k][-1])))
        self.fig.canvas.draw()

    def onpress(self, event):
        t = event.xdata
        y = event.ydata
        if t is None or y is None:
            # click outside
            self.quit = True
            return

        k, idx, dd = self.nearestdot(t, y)
        if dd < self.clickthr:
            self.draggingdot = [k, idx]
        else:
            k, idx, dd = self.nearestsegment(t, y)
            if dd < self.clickthr:
                # Add a new dot
                self.tdots[k] = np.insert(self.tdots[k], idx, t)
                self.ydots[k] = np.insert(self.ydots[k], idx, y)
                self.updatedotsandlines(k)
                self.draggingdot = [k, idx]
        if self.draggingdot is not None:
            self.cidm = self.fig.canvas.mpl_connect('motion_notify_event',
                                                    self.onmove)

    def onrelease(self, event):
        if self.draggingdot is None:
           return

        k, idx = self.draggingdot
        self.draggingdot = None
        self.fig.canvas.mpl_disconnect(self.cidm)
        self.cidm = None

        if len(self.tdots[k]) <= 1:
            return

        drop = False
        if self.tdots[k][idx] <= self.t0:
            drop = True
        elif self.tdots[k][idx] >= self.t1:
            drop = True
        elif idx>0 and self.tdots[k][idx] <= self.tdots[k][idx-1]:
            drop = True
        elif idx+1 < len(self.tdots[k]) \
             and self.tdots[k][idx] >= self.tdots[k][idx+1]:
            drop = True
        if drop:
            self.tdots[k] = np.delete(self.tdots[k], idx)
            self.ydots[k] = np.delete(self.ydots[k], idx)
            self.updatedotsandlines(k)

    def onmove(self, event):
       if self.draggingdot is not None:
            k, idx = self.draggingdot
            t = event.xdata
            y = event.ydata
            self.tdots[k][idx] = t
            self.ydots[k][idx] = y
            self.updatedotsandlines(k)

def selectspikes(tms, hei, return_idx=False):
    '''SELECTSPIKES - Run the SelectSpikes GUI
    tms, hei = SELECTSPIKES(tms, hei) runs the GUI and returns times
    and amplitudes of selected spikes.
    idx = SELECTSPIKES(tms, hei, True) runs the GUI and returns the indices
    of selected spikes instead.'''
    ss = SelectSpikes(tms, hei)
    idx = ss.run()
    if return_idx:
        return idx
    else:
        return tms, hei

if __name__ == '__main__':
    import scipy.signal
    from ephysx import spikex
    import pyqplot as qp

    b, a = scipy.signal.butter(3, .1)
    N = 1000
    tt = np.arange(N) / 1e3
    rnd = np.random.randn(N)
    flt = scipy.signal.filtfilt(b, a, rnd)
    qp.figure('/tmp/s1')
    qp.pen('b', 1)
    qp.plot(tt, flt)
    idx = spikex.detectspikes(flt, .1, tkill=0)
    qp.pen('r')
    qp.marker('o', 2)
    qp.mark(tt[idx], flt[idx])
    id2 = spikex.cleancontext(idx, flt)
    qp.marker('o', 4, fill='open')
    qp.pen('g', 1)
    qp.mark(tt[id2], flt[id2])
    qp.pen('k', .5)
    qp.xaxis()
    qp.yaxis()
    qp.shrink()

    ss = SelectSpikes(tt[idx], flt[idx])
    print(ss.run())


# type: ignore

from typing import Union, Dict, List, Any, Optional
from toolbox import Database, Collection
import os
import logging; log = logging.getLogger(__name__)

class MongoCollection(Collection):
    def __init__(self, bot, database: Database, name: str) -> None:
        super().__init__(
            database=database,
            name=name
        )
        self.bot = bot
        self.collection_name = name
        self.cached = name in bot.config.cache_options

    def get(self, _id: Union[str, int], key: str) -> Optional[Union[str, int, Dict[Union[str, int], Any], List[Any]]]:
        if self.cached:
            return (getattr(self.bot.cache, self.collection_name)).get(_id, key)
        else:
            return super().get(_id, key)

    def get_doc(self, _id: Union[str, int]) -> Optional[dict]:
        if self.cached:
            return (getattr(self.bot.cache, self.collection_name)).get_all(_id)
        else:
            return super().get_doc(_id)

    def get_from_db(self, _id: Union[str, int], key: str) -> Optional[Union[str, int, Dict[Union[str, int], Any], List[Any]]]:
        return super().get(_id, key)

    def insert(self, schema: Dict[str, Any]) -> None:
        super().insert_one(schema)
        if self.cached: (getattr(self.bot.cache, self.collection_name)).insert(schema["id"], schema)

    def update(self, _id: Union[str, int], key: str, value: Union[str, int, Dict[Union[str, int], Any], List[Any]]) -> None:
        super().update(_id, key, value)
        if self.cached: (getattr(self.bot.cache, self.collection_name)).update(_id, key, value)

    def multi_update(self, _id: Union[str, int], updates: Dict[str, Any]) -> None:
        for k, v in updates.items():
            self.update(_id, k, v)

    def delete(self, _id: Union[str, int]) -> None:
        super().delete(_id)
        if self.cached: (getattr(self.bot.cache, self.collection_name)).delete(_id)

    def multi_delete(self,  _filter: Dict[Any, Any]) -> None:
        super().delete_many(_filter)
        if self.cached:
            data = (getattr(self.bot.cache, self.collection_name)).data
            for k in (getattr(self.bot.cache, self.collection_name)).data.copy():
                if data[k][list(_filter.keys())[0]] == list(_filter.values())[0]:
                    (getattr(self.bot.cache, self.collection_name)).delete(k)

class MongoDB(Database):
    def __init__(self, bot) -> None:
        super().__init__(
            name=bot.config.db_name,
            host=bot.config.mongo_url
        )
        # Only doing this to have type hints
        self.configs = MongoCollection(bot, self, "configs")
        self.tags = MongoCollection(bot, self, "tags")
        self.cases = MongoCollection(bot, self, "cases")
        self.warns = MongoCollection(bot, self, "warns")
        self.mutes = MongoCollection(bot, self, "mutes")
        self.level = MongoCollection(bot, self, "level")
        self.slowmodes = MongoCollection(bot, self, "slowmodes")
        self.tbans = MongoCollection(bot, self, "tbans")
        self.responders = MongoCollection(bot, self, "responders")
        self.highlights = MongoCollection(bot, self, "highlights")
        self.stats = MongoCollection(bot, self, "stats")

        log.info("[Database] Connected to mongo", extra={"loc": f"PID {os.getpid()}"})


# Построить сегментацию изображения на основании вычисления площади поверхности сегмента.

from PIL import Image, ImageDraw
import numpy as np
from matplotlib import pyplot as plt

CELL_SIZE = 20
delta = [1, 2]

def get_A(cell, ds):
    length, width = cell.size[0], cell.size[1]
    u = np.zeros((len(ds) + 1, length + 1, width + 1))
    b = np.zeros((len(ds) + 1, length + 1, width + 1))
    v = np.zeros((len(ds) + 1))
    A = np.zeros((len(ds) + 1))

    for i in range(0, length):
        for j in range(0, width):
            u[0][i][j] = cell.getpixel((i, j))
            b[0][i][j] = cell.getpixel((i, j))

            for d in ds:
                u[d][i][j] = max(u[d - 1][i][j] + 1, max(u[d - 1][i + 1][j + 1], u[d - 1][i - 1][j + 1], u[d - 1][i + 1][j - 1], u[d - 1][i - 1][j - 1]))
                b[d][i][j] = min(b[d - 1][i][j] - 1, min(b[d - 1][i + 1][j + 1], b[d - 1][i - 1][j + 1], b[d - 1][i + 1][j - 1], b[d - 1][i - 1][j - 1]))

    for d in ds:
        v[d] = 0
        for i in range(0, length):
            for j in range(0, width):
                v[d] += u[d][i][j] - b[d][i][j]

    for d in ds:
        A[d] = (v[d] - v[d-1]) / 2

    return A[delta[-1]]

def build_segmentation(image, save_file_name):
    length, width = image.size[0], image.size[1]
    cell_length, cell_width = length//CELL_SIZE, width//CELL_SIZE
    A = np.zeros((cell_length, cell_width))
    for i in range(0, cell_length):
        for j in range(0, cell_width):
            area = (i*CELL_SIZE, j*CELL_SIZE, (i+1)*CELL_SIZE, (j+1)*CELL_SIZE)
            cell = image.crop(area)
            A[i][j] = get_A(cell, delta)

    mean = np.mean(A)

    new_image = Image.new("L", (cell_length*CELL_SIZE, cell_width*CELL_SIZE))
    img1 = ImageDraw.Draw(new_image)

    for i in range(0, cell_length):
        for j in range(0, cell_width):
            if A[i][j] < mean:
                img1.rectangle([(i*CELL_SIZE, j*CELL_SIZE), ((i+1)*CELL_SIZE, (j+1)*CELL_SIZE)], fill = "white", outline="white")
            else:
                img1.rectangle([(i*CELL_SIZE, j*CELL_SIZE), ((i+1)*CELL_SIZE, (j+1)*CELL_SIZE)], fill = "black", outline="black")
    new_image.save(save_file_name)

def getBlackAndWhiteImage(image):
    black_and_white = image.convert('1')
    return black_and_white

if __name__ == '__main__':
        name = 'image_text.png'
        try:
            im = Image.open(name)

            im = getBlackAndWhiteImage(im)
            segmentated_name = "segmentated_" + name
            build_segmentation(im, segmentated_name)
            im_result = getBlackAndWhiteImage(Image.open('segmentated_image.jpg'))

            plt.figure(figsize=(5, 5))
            plt.imshow(im_result)
            plt.show()

        except Exception as e:
            print(e)
            print("Could not find the file. Please try again.")

Методы фрактального анализа цифровых изображений/labs/lab7.py


from django.db import models
from core.models.conference import Conference

class ScheduleForEarlyTrack(models.Model):
    # For use cases undefined
    created_at = models.DateTimeField(auto_now_add=True, blank=True)

    # Associated conference
    conference = models.OneToOneField(
        Conference,
        on_delete=models.CASCADE,
        related_name="early_track_schedule",
    )

    # Org details
    last_date = models.DateField()
    author_notification = models.DateField()
    registration_deadline = models.DateField()
    start_date = models.DateField(blank=True, null=True)
    end_date = models.DateField(blank=True, null=True)

    def __str__(self) -> str:
        return str(self.conference)

    class Meta:
        ordering = ["created_at"]


from fastapi import APIRouter, Depends, UploadFile

from ....controllers import PropertiesController
from ....schemas.query_params import PropertyQuery
from ....schemas.request import (
    RentalCreate,
    PropertyCreate,
    PropertyUpdate,
    ReviewCreate,
    ReviewUpdate,
)
from ....utils.jwt import ADMIN_AUTH, AUTH

router = APIRouter()
controller = PropertiesController()

@router.get("")
async def get_properties(filters: PropertyQuery = Depends()):
    return await controller.get_properties(filters=filters)

@router.get("/{property_id}")
async def get_property(property_id: int):
    return await controller.get_property(property_id=property_id)

@router.post("", dependencies=[Depends(ADMIN_AUTH)])
async def create_property(data: PropertyCreate):
    return await controller.create_property(data_in=data)

@router.post("/{property_id}/images", dependencies=[Depends(ADMIN_AUTH)])
async def upload_image(property_id: int, image: UploadFile):
    return await controller.upload_image(property_id=property_id, image=image)

@router.delete("/{property_id}/images/{image_id}", dependencies=[Depends(ADMIN_AUTH)])
async def delete_image(property_id: int, image_id: int):
    return await controller.remove_image(property_id=property_id, image_id=image_id)

@router.put("/{property_id}", dependencies=[Depends(ADMIN_AUTH)])
async def update_property(property_id: int, data: PropertyUpdate):
    return await controller.update_property(property_id=property_id, data=data)

@router.delete("/{property_id}", dependencies=[Depends(ADMIN_AUTH)])
async def delete_property(property_id: int):
    return await controller.delete_property(property_id=property_id)

@router.get("/{property_id}/reviews")
async def get_reviews(property_id: int):
    return await controller.get_reviews(property_id=property_id)

@router.post("/{property_id}/reviews")
async def create_review(property_id: int, data: ReviewCreate, user=Depends(AUTH)):
    return await controller.add_review(
        property_id=property_id,
        user_id=user.id,
        data=data,
    )

@router.put("/{property_id}/reviews/{review_id}")
async def update_review(
    property_id: int,
    review_id: int,
    data: ReviewUpdate,
    user=Depends(AUTH),
):
    return await controller.update_review(
        property_id=property_id,
        review_id=review_id,
        user_id=user.id,
        data=data,
    )

@router.get("/{property_id}/rentals")
async def get_rentals(property_id: int):
    return await controller.get_rentals(property_id=property_id)

@router.post("/{property_id}/rentals")
async def create_rental(property_id: int, data: RentalCreate, user=Depends(AUTH)):
    return await controller.book_property(
        property_id=property_id,
        user_id=user.id,
        data=data,
    )

@router.post("/rentals/{rental_id}/accept", dependencies=[Depends(ADMIN_AUTH)])
async def accept_rental(rental_id: int):
    return await controller.accept_rental(
        rental_id=rental_id,
    )

@router.post("/rentals/{rental_id}/decline", dependencies=[Depends(ADMIN_AUTH)])
async def decline_rental(rental_id: int):
    return await controller.decline_rental(
        rental_id=rental_id,
    )

@router.get("/{property_id}/tenants")
async def get_tenants(property_id: int):
    return await controller.get_tenants(property_id=property_id)

@router.post("/{property_id}/tenants", dependencies=[Depends(ADMIN_AUTH)])
async def add_tenant(property_id: int, user_id: int):
    return await controller.add_tenant(property_id=property_id, user_id=user_id)

@router.delete("/{property_id}/tenants/{tenant_id}", dependencies=[Depends(ADMIN_AUTH)])
async def remove_tenant(property_id: int, tenant_id: int):
    return await controller.remove_tenant(property_id=property_id, tenant_id=tenant_id)


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import lightgbm as lgb
from sklearn.model_selection import KFold
import warnings
import time
import sys
import datetime
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import mean_squared_error
warnings.simplefilter(action='ignore', category=FutureWarning)
pd.set_option('display.max_columns', 500)
from sklearn.model_selection import train_test_split
import itertools
##########

def binarize(df):
    for col in ['authorized_flag', 'category_1']:
        df[col] = df[col].map({'Y':1, 'N':0})
    return df

def encoding_purchase_date(dat):
    dat['month_diff'] = ((datetime.datetime.today() - dat['purchase_date']).dt.days)//30
    dat['month_diff'] += dat['month_lag']
    dat['purchase_month'] = dat['purchase_date'].dt.month
    dat['purchase_date_last'] = dat.groupby('card_id')['purchase_date'].shift(1)
    dat['purchase_amount_last'] = dat.groupby('card_id')['purchase_amount'].shift(1)
    dat['purchase_date_gap'] = dat.purchase_date - dat.purchase_date_last
    dat['purchase_amount_gap'] = dat.purchase_amount - dat.purchase_amount_last
    dat['purchase_date_gap'].fillna(0,inplace=True)
    dat['purchase_amount_gap'].fillna(0,inplace=True)
    return dat

def delta_purchase_date():
    temp1 = historical_transactions.groupby('card_id')['purchase_date'].max().to_frame('hist_latest_purchase_date')
    temp2 = new_transactions.groupby('card_id')['purchase_date'].min().to_frame('new_earliest_purchase_date')
    temp = temp1.merge(temp2,how='left',left_index=True,right_index=True)
    temp["delta_purchase_days"] = temp.new_earliest_purchase_date - temp.hist_latest_purchase_date
    temp["delta_purchase_days"] = temp["delta_purchase_days"].dt.days
    temp["delta_purchase_days"].fillna(9999,inplace=True)
    return temp

def category_dummies():
    historical_transactions = pd.get_dummies(historical_transactions, columns=['category_2','category_3','category_1_2','category_1_3','category_1_2_3','category_1_auth'])
    new_transactions = pd.get_dummies(new_transactions, columns=['category_2', 'category_3','category_1_2','category_1_3','category_1_2_3','category_1_auth'])
    return historical_transactions,new_transactions

def authorized_mean():
    agg_fun = {'authorized_flag': ['mean']}
    auth_mean = historical_transactions.groupby(['card_id']).agg(agg_fun)
    auth_mean.columns = ['_'.join(col).strip() for col in auth_mean.columns.values]
    auth_mean.reset_index(inplace=True)
    return auth_mean

def aggregate_per_month(history):
    grouped = history.groupby(['card_id', 'month_lag'])

    agg_func = {
            'purchase_amount': ['count', 'sum', 'mean', 'min', 'max', 'std'],
            'installments': ['count', 'sum', 'mean', 'min', 'max', 'std'],
            }

    intermediate_group = grouped.agg(agg_func)
    intermediate_group.columns = ['_'.join(col).strip() for col in intermediate_group.columns.values]
    intermediate_group.reset_index(inplace=True)

    final_group = intermediate_group.groupby('card_id').agg(['mean', 'std'])
    final_group.columns = ['_'.join(col).strip() for col in final_group.columns.values]
    final_group.reset_index(inplace=True)

    return final_group

def main():
    ### Read Data
    train = read_data('D:/ML/Elo/train.csv')
    test = read_data('D:/ML/Elo/test.csv')

    target = train['target']
    del train['target']
    historical_transactions = pd.read_csv('D:/ML/Elo/historical_transactions.csv',parse_dates=['purchase_date'])
    new_transactions = pd.read_csv('D:/ML/Elo/new_merchant_transactions.csv',parse_dates=['purchase_date'])

    ###
    historical_transactions = binarize(historical_transactions)
    new_transactions = binarize(new_transactions)

    historical_transactions = encoding_purchase_date(historical_transactions)
    new_transactions = encoding_purchase_date(new_transactions)

    DATA_delta_purchase_date = delta_purchase_date()
    DATA_authorized_mean = authorized_mean()

if __name__ == 'main':
    main()


#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2023 GPLv3 License
import logging
import array
import time
import os
from binascii import hexlify
from struct import pack, unpack

from mtkclient.Library.exploit_handler import Exploitation
from mtkclient.Library.utils import LogBase, print_progress, revdword, logsetup
from mtkclient.Library.Connection.usblib import usb
from mtkclient.config.payloads import pathconfig

class Amonet(Exploitation, metaclass=LogBase):

    def __init__(self, mtk, loglevel=logging.INFO):
        super().__init__(mtk, loglevel)

    def exploit(self, payload, payloadaddr):
        pass

    def da_read_write(self, address, length, data=None, check_result=True):
        if self.chipconfig.blacklist:
            self.hwcrypto.disable_range_blacklist("gcpu", self.mtk)
        if data is None:
            old = 0
            data=bytearray()
            for addr in range(address, address+length, 16):
                prog = int(addr / length * 100)
                if round(prog, 1) > old:
                    print_progress(prog, 100, prefix='Progress:', suffix='Complete, addr %08X' % addr,
                                   bar_length=50)
                    old = round(prog, 1)
                data.extend(self.hwcrypto.gcpu.aes_read_cbc(addr))
            return data
        else:
            for addr in range(address, address+length, 4):
                prog = int(addr / length * 100)
                if round(prog, 1) > old:
                    print_progress(prog, 100, prefix='Progress:', suffix='Complete, addr %08X' % addr,
                                   bar_length=50)
                    old = round(prog, 1)
                    self.write32(data[addr-address:(addr-address)+4])

    def bruteforce(self, args, startaddr=0x9900):
        pass

    def newbrute(self, dump_ptr, dump=False):
        pass

    def payload(self, payload, daaddr):
        ptype = "gcpu"
        self.hwcrypto.disable_range_blacklist(ptype, self.mtk.preloader.run_ext_cmd)
        try:
            while len(payload) % 4 != 0:
                payload += b"\x00"

            words = []
            for x in range(len(payload) // 4):
                word = payload[x * 4:(x + 1) * 4]
                word = unpack("<I", word)[0]
                words.append(word)

            self.info("Sending payload")
            self.write32(self, words)

            self.info("Running payload ...")
            self.write32(self.mtk.config.chipconfig.blacklist[0][0] + 0x40, daaddr)
            return True
        except Exception as e:
            self.error("Failed to load payload file. Error: " + str(e))
        return False

    def runpayload(self, payload, ack=0xA1A2A3A4, addr=None, dontack=False):
        self.info("Amonet Run")
        if addr is None:
            addr = self.chipconfig.da_payload_addr
        if self.payload(payload, addr):
            if dontack:
                return ack
            result = self.usbread(4)
            if result == pack(">I", ack):
                return ack
            else:
                self.info("Error, payload answered instead: " + hexlify(result).decode('utf-8'))
        return None

    def dump_preloader(self, filename=None):
        btype = "gcpu"
        if filename is None:
            return None
        if self.chipconfig.gcpu_base is None:
            self.error("Chipconfig has no gcpu_base field for this cpu")
            return False
        if self.chipconfig.blacklist:
            self.hwcrypto.disable_range_blacklist(btype, self.mtk)
        self.info("Dump preloader")
        if filename is None:
            data = bytearray()
            for addr in range(0x200000, 0x240000, 16):
                if not self.chipconfig.blacklist:
                    data.extend(self.hwcrypto.gcpu.aes_read_cbc(addr))
                else:
                    data.extend(self.hwcrypto.gcpu.aes_read_cbc(addr))
            return data, filename
        else:
            print_progress(0, 100, prefix='Progress:', suffix='Complete', bar_length=50)
            old = 0
            with open(filename, 'wb') as wf:
                for addr in range(0x200000, 0x240000, 16):
                    prog = int(addr / 0x20000 * 100)
                    if round(prog, 1) > old:
                        print_progress(prog, 100, prefix='Progress:', suffix='Complete, addr %08X' % addr,
                                       bar_length=50)
                        old = round(prog, 1)
                    wf.write(self.hwcrypto.gcpu.aes_read_cbc(addr))
            print_progress(100, 100, prefix='Progress:', suffix='Complete', bar_length=50)
            return True

    def dump_brom(self, filename, dump_ptr=None, length=0x20000):
        btype = "gcpu"
        if self.chipconfig.gcpu_base is None:
            self.error("Chipconfig has no gcpu_base field for this cpu")
            return False
        if self.chipconfig.blacklist:
            self.hwcrypto.disable_range_blacklist(btype, self.mtk)
        self.info("Dump bootrom")
        print_progress(0, 100, prefix='Progress:', suffix='Complete', bar_length=50)
        old = 0
        with open(filename, 'wb') as wf:
            for addr in range(0x0, length, 16):
                prog = int(addr / length * 100)
                if round(prog, 1) > old:
                    print_progress(prog, 100, prefix='Progress:', suffix='Complete, addr %08X' % addr,
                                   bar_length=50)
                    old = round(prog, 1)
                wf.write(self.hwcrypto.gcpu.aes_read_cbc(addr))
        print_progress(100, 100, prefix='Progress:', suffix='Complete', bar_length=50)
        return True


import enum
from typing import Union
import collections
import numpy as np
from myosuite.utils.quat_math import euler2quat
import glob
import pickle

# Time precision to use. Avoids rounding/resolution errors during comparisons
_TIME_PRECISION = 4

# Reference structure
ReferenceStruct = collections.namedtuple('ReferenceStruct',
        ['time',        # float(N)
         'robot',       # shape(N, n_robot_jnt) ==> robot trajectory
         'robot_vel',   # shape(N, n_robot_jnt) ==> robot velocity
         'object',      # shape(M, n_objects_jnt) ==> object trajectory
         'robot_init',  # shape(n_objects_jnt) ==> initial robot pose (can be different from robot(0,n_robot_jnt))
         'object_init'  # shape(n_objects_jnt) ==> initial object (can be different from object(0,n_object_jnt))
         ])

# Reference type
class ReferenceType(enum.Enum):
    """Reference types"""
    FIXED = 0
    RANDOM = 1
    TRACK = 2

# Reference motion
class ReferenceMotion():
    def __init__(self, reference_data:Union[str, dict],
                 motion_extrapolation:bool=False,    # zero order hold if the motion is over
                 random_generator=None
                 ):
        """
        Reference Type
            Fixed  :: N==1, M==1  :: input is <Fixed target dict>
            Random :: N==2, M==2  :: input is <Randomization range dict> :: ind0:low_limit, ind1:high_limit
            Track  :: N>2 or M>2  :: input as <Motion file to be tracked>
        """

        self.motion_extrapolation = motion_extrapolation
        self.np_random = random_generator if random_generator!=None else np.random

        # load reference
        self.reference = self.load(reference_data)

        # check reference for format
        self.check_format(self.reference)
        self.reference['time'] = np.around(self.reference['time'], _TIME_PRECISION) # round to help with comparisons
        robot_shape = self.reference['robot'].shape if self.reference['robot'] is not None else (0,0)
        object_shape = self.reference['object'].shape if self.reference['object'] is not None else (0,0)
        self.robot_dim = robot_shape[1]
        self.object_dim = object_shape[1]

        # identify type
        if robot_shape[0] > 2 or object_shape[0] > 2:
            self.type = ReferenceType.TRACK
        elif robot_shape[0] == 2 or object_shape[0] == 2:
            self.type = ReferenceType.RANDOM
        elif robot_shape[0] == 1 or object_shape[0] == 1:
            self.type = ReferenceType.FIXED
        else:
            raise ValueError("Reference values not per specs")

        # identify length
        self.robot_horizon = robot_shape[0]
        self.object_horizon = object_shape[0]
        self.horizon = max(robot_shape[0], object_shape[0])

        # Add missing values
        if self.type == ReferenceType.RANDOM:
            # use mean postures from the randomization range
            if 'robot_init' not in self.reference.keys():
                self.reference['robot_init'] = np.mean(self.reference['robot'])
            if 'object_init' not in self.reference.keys():
                self.reference['object_init'] = np.mean(self.reference['object'][0])
        else:
            # use first timeset
            if 'robot_init' not in self.reference.keys():
                self.reference['robot_init'] = self.reference['robot'][0]
            if 'object_init' not in self.reference.keys():
                self.reference['object_init'] = self.reference['object'][0]

        # cache to help with finding index
        self.index_cache = 0

    def check_format(self, reference):
        """
        Check reference format
        """
        if reference['robot'] is not None:
            assert reference['robot'].ndim == 2, "Check robot reference, must be shape(N, n_robot_jnt)"
        if reference['object'] is not None:
            assert reference['object'].ndim == 2, "Check object reference, must be shape(N, n_object_jnt)"
        if reference['robot_init'] is not None:
            assert reference['robot_init'].ndim == 1, "Check robot_init reference, must be shape(n_robot_jnt)"
        if reference['robot'] is not None and reference['robot_init'] is not None:
            assert reference['robot_init'].shape[0] == reference['robot'].shape[1], "n_robot_jnt different between motion and init "
        if reference['object_init'] is not None:
            assert reference['object_init'].ndim == 1, "Check object_init reference, must be shape(n_object_jnt)"
        if reference['object_init'] is not None and reference['object'] is not None:
            assert reference['object_init'].shape[0] == reference['object'].shape[1], "n_object_jnt different between motion and init "

    def load(self, reference_data):
        """
        Load reference motion
        """

        # Load data
        if isinstance(reference_data, str):
            if reference_data.split('.')[-1]=="npz":
                reference = {k:v for k, v in np.load(reference_data).items()}
            elif reference_data.split('.')[-1]=="pkl" or reference_data.split('.')[-1]=="pickle":
                with open(reference_data, 'rb') as data:
                    reference = pickle.load(data)
            # if 'robot_vel' not in self.reference.keys():
                # self.reference['robot_vel'] = np.zeros(self.reference['robot'].shape)
        elif isinstance(reference_data, dict):
            reference = reference_data.copy()
        else:
            raise TypeError("Unknown reference type")

        # resolve values
        assert 'time' in reference.keys(), "Missing key (time) in reference"
        if 'robot_init' not in reference.keys():
            reference['robot_init'] = reference['robot'][0] if 'robot' in reference.keys() else None
        if 'object_init' not in reference.keys():
            reference['object_init'] = reference['object'][0] if 'object' in reference.keys() else None

        # build reference
        ref = ReferenceStruct(
            time = reference['time'], # Must have
            robot = reference['robot'] if 'robot' in reference.keys() else None,
            robot_vel = reference['robot_vel'] if 'robot_vel' in reference.keys() else None,
            object = reference['object'] if 'object' in reference.keys() else None,
            robot_init = reference['robot_init'],
            object_init = reference['object_init'],
        )
        return ref._asdict()

    def find_timeslot_in_reference(self, time):
        """
        Find the timeslot interval for the provided time in the reference motion
        INPUT:  Time: Specified time
        OUTPUT: Timeslot index tuple(ind_prev, ind_next)
                - where reference['time'][ind_prev] <= time <= reference['time'][ind_next]
                - ind_prev == ind_next if exact time is found in reference['time']
        """

        time = np.around(time, _TIME_PRECISION) # round to help with comparisons
        if self.type == ReferenceType.FIXED:
            return (0,0)
        if self.motion_extrapolation and time>=self.reference['time'][-1]:
            return (self.horizon-1,self.horizon-1)
        else:
            assert time<=self.reference['time'][-1], f"Trying to access time (={time}) beyond max reference duration (={self.reference['time'][-1]}) "

        # search locally for index
        if time == self.reference['time'][self.index_cache]:
            # print(f"curr match: {time}")
            return (self.index_cache, self.index_cache)

        elif self.index_cache<(self.horizon-1):
            if time == self.reference['time'][self.index_cache+1]:
                # print(f"next match: {time}")
                self.index_cache += 1
                return (self.index_cache, self.index_cache)

            elif time > self.reference['time'][self.index_cache] and time < self.reference['time'][self.index_cache+1]:
                # print(f"interval match: {time}")
                return (self.index_cache, self.index_cache+1)
            else:
                print(f"No result using hueristic search. Attempting sort match: {time}")
                self.index_cache = np.searchsorted(self.reference['time'], time, side="right") - 1
                if time == self.reference['time'][self.index_cache]:
                    return (self.index_cache, self.index_cache)
                elif time > self.reference['time'][self.index_cache] and time < self.reference['time'][self.index_cache+1]:
                    return (self.index_cache, self.index_cache+1)
                else:
                    raise ValueError("We shouldn't be in this condition")
        else:
            raise ValueError("We shouldn't be in this condition")

    def reset(self):
        """
        Reset the cache to point back to the beginning
        """
        self.index_cache = 0

    def get_init(self):
        """
        return the initial posture of the robot and the object
        """
        return self.reference['robot_init'], self.reference['object_init']

    def get_reference(self, time):
        """
        Return the reference at the given time. Linearly interpolate if there is no exact match
        """
        if self.type == ReferenceType.FIXED:
            robot_ref = self.reference['robot'][0]
            robot_vel_ref = self.reference['robot_vel'][0]
            object_ref = self.reference['object'][0]
        elif self.type == ReferenceType.RANDOM:
            robot_ref     = self.np_random.uniform(low=self.reference['robot'][0,:], high=self.reference['robot'][1,:])
            robot_vel_ref = self.np_random.uniform(low=self.reference['robot_vel'][0,:], high=self.reference['robot_vel'][1,:])
            object_ref    = self.np_random.uniform(low=self.reference['object'][0,:], high=self.reference['object'][1,:])
        elif self.type == ReferenceType.TRACK:
            ind, ind_next = self.find_timeslot_in_reference(time=time)
            if ind == ind_next:
                # Exact frame[time] found for reference
                robot_ref     = self.reference['robot'][ind] if self.robot_horizon>1 else self.reference['robot'][0]
                if self.reference['robot_vel'] is None:
                    robot_vel_ref = None
                else:
                    robot_vel_ref = self.reference['robot_vel'][ind] if self.robot_horizon>1 else self.reference['robot_vel'][0]
                if self.reference['object'] is None:
                    object_ref = None
                else:
                    object_ref = self.reference['object'][ind] if self.object_horizon>1 else self.reference['object'][0]
            else:
                # Linearly interpolate between frames to get references
                # ref[time] = blend(ref[ind] + (1-blend)*ref[ind_next])
                print(f"Direct frame reference not found at {time} sec. Attempting linear blend between two frames [{ind},{ind_next}]")
                blend = time - self.reference['time'][ind]/(self.reference['time'][ind_next]-self.reference['time'][ind])

                # robot motion
                if self.robot_horizon>1:
                    robot_ref = (1.0-blend)**self.reference['robot'][ind]+blend*self.reference['robot'][ind_next]
                    if self.reference['robot_vel'] is None:
                        robot_vel_ref = None
                    else:
                        robot_vel_ref = (1.0-blend)**self.reference['robot_vel'][ind]+blend*self.reference['robot_vel'][ind_next]
                else:
                    robot_ref = self.reference['robot'][0]
                    robot_vel_ref = None if self.reference['robot_vel'] is None else self.reference['robot_vel'][0]

                # object motion
                if self.reference['object'] is None:
                    object_ref = None
                elif self.object_horizon>1:
                    object_ref = (1.0-blend)*self.reference['object'][ind]+blend*self.reference['object'][ind_next]
                else:
                    object_ref = self.reference['object'][0]

        return ReferenceStruct(time = time,
            robot = robot_ref,
            robot_vel = robot_vel_ref,
            object = object_ref,
            robot_init = self.reference['robot_init'],
            object_init = self.reference['object_init'],
            )

    def __repr__(self) -> str:
        return self.reference.__repr__()


import cv2

from extraction.FormatModel import UtilFunctionsExtraction, UtilDebug
from api import engine

class RawValue:
    def __init__(self, value, countItems=1, parser=None, nameParser='None', singleParser=None,
                 nameSingleParser='None'):

        if countItems >=1:
            self.position = value
            self.position[0]=(self.position[0][0],self.position[0][1])
            self.position[1] = (self.position[1][0], self.position[1][1])
            self.countItems = countItems
            self.parser = parser
            self.singleParser = singleParser
            self.nameParser = nameParser
            self.nameSingleParser = nameSingleParser
            self.predictedValue = None
            self.arrayOfImages = None

        else:
            if nameSingleParser is not None and nameSingleParser == 'digitPredictor':
                self.predictedValue =[]
                EngineDigit = engine.UniqueEngineDigit()
                for v in value:
                    if v is not None:
                        self.predictedValue.append(chr(EngineDigit.pred[v] + ord('0')))
                    else:
                        self.predictedValue.append(' ')
            elif nameSingleParser is not None and nameSingleParser == 'letterPredictor':
                self.predictedValue = []
                EngineLetter = engine.UniqueEngineLetter()
                for v in value:

                    if v is not None:
                        self.predictedValue.append(chr(EngineLetter.pred[v] + ord('A')))
                    else:
                        self.predictedValue.append(' ')
            else:
                self.predictedValue = value

            self.nameParser = nameParser
            self.nameSingleParser = nameSingleParser
            self.countItems = -1

    def convert2ParsedValues(self):
        if self.nameParser == 'parserImage2ArrayChar':
            if self.nameSingleParser == 'letterPredictor':
                return ArrayPredictedChar(self.predictedValue)
            elif self.nameSingleParser == 'digitPredictor':
                return ArrayPredictedNumber(self.predictedValue)
        elif self.nameParser == 'parserImage2Categoric':
            return PredictedCategoric(self.predictedValue)
        print(self.nameParser)
        if self.nameSingleParser is not None:
            print(self.nameSingleParser)
        else:
            print('nameSingleParser is None')
        raise Exception('bad arguments or not implemented parser')

    def jsonDefault(object):
        return object.__dict__

    def __str__(self):
        return str(self.position)

    def getPosition(self):
        return self.position

    def parse(self, arg):
        if self.parser is None:
            raise Exception('no parser given')

        return self.parser(arg)

    def drawPosition(self, img):
        TL = self.position[0]
        BR = self.position[1]
        # print('TL ', TL)
        # TL = (TL[0],TL[1])
        # BR = (BR[0], BR[1])
        # print('BR ', BR)
        cv2.rectangle(img, TL, BR, (0, 255, 0), 2)

    def parserImage2ArrayChar(self, arg):
        if self.singleParser == self.letterPredictor:
            charArray_timer = UtilDebug.ArrayLetterTimer()
        else:
            charArray_timer = UtilDebug.ArrayDigitTimer()

        charArray_timer.startTimer(1) #self.count
        img = arg

        TL = self.position[0]
        BR = self.position[1]
        count = self.countItems
        arrayOfImages = UtilFunctionsExtraction.extractCharacters(img, TL, BR, count)
        arrayResult = []
        for singleImage in arrayOfImages:
            if singleImage is None:
                predicted = None
            else:
                predicted = self.singleParser(singleImage)

            arrayResult.append(predicted)
        #UtilFunctionsExtraction.plotImagesWithPrediction(arrayResult,arrayOfImages)
        self.predictedValue = arrayResult
        self.arrayOfImages = arrayOfImages
        charArray_timer.endTimer()
        return self.predictedValue

    def calcCuadroArrayChar(self, arg):
        if self.singleParser == self.letterPredictor:
            charArray_timer = UtilDebug.ArrayLetterTimer()
        else:
            charArray_timer = UtilDebug.ArrayDigitTimer()

        charArray_timer.startTimer(1) #self.count
        img = arg

        TL = self.position[0]
        BR = self.position[1]
        count = self.countItems
        UtilFunctionsExtraction.predictCuadros(img, TL, BR, count)
        charArray_timer.endTimer()

    def getFinalValue(self, arg):
        return self.predictedValue

    def parserImage2Categoric(self,arg):
        if self.singleParser is not None:
            categoric_timer = UtilDebug.CategoryTimer()
            categoric_timer.startTimer(1)
            ret = self.singleParser(arg)
            categoric_timer.endTimer()
        else:
            self.arrayOfImages = None
            self.predictedValue = ['unknow']
            return self.predictedValue

    def parserCategoricLabelsInside(self, arg):
        img = arg
        TL = self.position[0]
        BR = self.position[1]
        labels = self.position[2]
        self.countItems = len(labels)
        self.arrayOfImages = UtilFunctionsExtraction.extractCategory_extractColumnLabelsInside(img, TL, BR, len(labels))
        results = UtilFunctionsExtraction.predictValuesCategory_labelsInside(self.arrayOfImages, labels)
        self.predictedValue = []
        for r in results:
            self.predictedValue.append(r)
        return self.predictedValue
    def parserCategoricLabelsLeft(self, arg):
        img = arg
        TL = self.position[0]
        BR = self.position[1]
        labels = self.position[2]
        self.countItems = len(labels)
        self.arrayOfImages = UtilFunctionsExtraction.extractCategory_extractColumnLabelsLeft(img, TL, BR, len(labels), len(labels[0]))
        results = UtilFunctionsExtraction.predictValuesCategory_labelsLeft(self.arrayOfImages, labels)
        self.predictedValue = []
        for r in results:
            self.predictedValue.append(r)
        return self.predictedValue
    def parserCategoricLabelsSex(self, arg):
        img = arg
        TL = self.position[0]
        BR = self.position[1]
        labels = self.position[2]
        self.countItems = len(labels)
        self.arrayOfImages = UtilFunctionsExtraction.extractCategory_extractColumnLabelsSex(img, TL, BR, len(labels))
        results = UtilFunctionsExtraction.predictValuesCategory_labelsSex(self.arrayOfImages, labels)
        self.predictedValue = []
        for r in results:
            self.predictedValue.append(r)
        return self.predictedValue

    def parserCategoricLabelsDocumento(self, arg):
        img = arg
        TL = self.position[0]
        BR = self.position[1]
        labels = self.position[2]
        self.countItems = len(labels)
        self.arrayOfImages = UtilFunctionsExtraction.extractCategory_extractColumnLabelsDocumento(img, TL, BR, len(labels))
        results = UtilFunctionsExtraction.predictValuesCategory_labelsDocumento(self.arrayOfImages, labels)
        self.predictedValue = []
        for r in results:
            self.predictedValue.append(r)
        return self.predictedValue
    def parserCategoricLabelsTipoSuministro(self, arg):
        img = arg
        TL = self.position[0]
        BR = self.position[1]
        labels = self.position[2]
        self.countItems = len(labels)
        self.arrayOfImages = UtilFunctionsExtraction.extractCategory_extractColumnLabelsTipoSuministro(img, TL, BR, len(labels))
        results = UtilFunctionsExtraction.predictValuesCategory_labelsSingleButtons(self.arrayOfImages, labels)
        self.predictedValue = []
        for r in results:
            self.predictedValue.append(r)
        return self.predictedValue

    def parserCategoricLabelsTipoVia(self, arg):
        img = arg
        TL = self.position[0]
        BR = self.position[1]
        labels = self.position[2]
        self.countItems = len(labels)
        self.arrayOfImages = UtilFunctionsExtraction.extractCategory_extractColumnLabelsTipoVia(img, TL, BR, len(labels))
        results = UtilFunctionsExtraction.predictValuesCategory_labelsSingleButtons(self.arrayOfImages, labels)
        self.predictedValue = []
        for r in results:
            self.predictedValue.append(r)
        return self.predictedValue

    def parserCategoricLabelsSiNo(self, arg):
        img = arg
        TL = self.position[0]
        BR = self.position[1]
        labels = self.position[2]
        self.countItems = len(labels)
        self.arrayOfImages = UtilFunctionsExtraction.extractCategory_extractColumnLabelsTipoSiNo(img, TL, BR, len(labels))
        results = UtilFunctionsExtraction.predictValuesCategory_labelsSingleButtons(self.arrayOfImages, labels)
        self.predictedValue = []
        for r in results:
            self.predictedValue.append(r)
        return self.predictedValue

    def letterPredictor(self, img):
        indx = engine.predictImage(img)
        return indx#chr(pred_label + ord('A'))
        #return 'A'

    def digitPredictor(self, img):
        indx = engine.predictImageDigit(img)
        return indx#chr(pred_label + ord('0'))
        #return '0'

class ArrayImageNumber(RawValue):
    def __init__(self, position, count):
        super().__init__(position, count, self.parserImage2ArrayChar, 'parserImage2ArrayChar',
                         self.digitPredictor, 'digitPredictor')
    def __str__(self):
        return 'ArrayImageNumber: ' + str(self.countItems)

class ArrayImageChar(RawValue):
    def __init__(self, position, count):
        super().__init__(position, count, self.parserImage2ArrayChar, 'parserImage2ArrayChar',
                         self.letterPredictor, 'letterPredictor')
    def __str__(self):
        return 'ArrayImageChar: ' + str(self.countItems)

class ImageCategoric(RawValue):
    def __init__(self, position, count):
        if count == 1:
            super().__init__(position, 1, self.parserImage2Categoric, 'parserImage2Categoric', None, None)
        else:
            raise Exception('Categoric values always should have count = 1')
    def __str__(self):
        return 'ImageCategoric  : ' + str(self.countItems)

class ImageCategoricLabelsInside(RawValue):
    def __init__(self, position, count):
        if count == 1:
            super().__init__(position, 1, self.parserImage2Categoric, 'parserImage2Categoric',
                             self.parserCategoricLabelsInside, 'parserCategoricLabelsInside')
        else:
            raise Exception('Categoric values always should have count = 1')
    def __str__(self):
        return 'ImageCategoric  : ' + str(self.countItems)
class ImageCategoricLabelsLeft(RawValue):
    def __init__(self, position, count):
        if count == 1:
            super().__init__(position, 1, self.parserImage2Categoric, 'parserImage2Categoric',
                             self.parserCategoricLabelsLeft, 'parserCategoricLabelsLeft')
        else:
            raise Exception('Categoric values always should have count = 1')

    def __str__(self):
        return 'ImageCategoric  : ' + str(self.countItems)

class ImageCategoricLabelsSex(RawValue):
    def __init__(self, position, count):
        if count == 1:
            super().__init__(position, 1, self.parserImage2Categoric, 'parserImage2Categoric',
                             self.parserCategoricLabelsSex, 'parserCategoricLabelsSex')
        else:
            raise Exception('Categoric values always should have count = 1')

    def __str__(self):
        return 'ImageCategoric  : ' + str(self.countItems)

class ImageCategoricLabelsSiNo(RawValue):
    def __init__(self, position, count):
        if count == 1:
            super().__init__(position, 1, self.parserImage2Categoric, 'parserImage2Categoric',
                             self.parserCategoricLabelsSiNo, 'parserCategoricLabelsSiNo')
        else:
            raise Exception('Categoric values always should have count = 1')

    def __str__(self):
        return 'ImageCategoric  : ' + str(self.countItems)

class ImageCategoricLabelsTipoVia(RawValue):
    def __init__(self, position, count):
        if count == 1:
            super().__init__(position, 1, self.parserImage2Categoric, 'parserImage2Categoric',
                             self.parserCategoricLabelsTipoVia, 'parserCategoricLabelsTipoVia')
        else:
            raise Exception('Categoric values always should have count = 1')

    def __str__(self):
        return 'ImageCategoric  : ' + str(self.countItems)

class ImageCategoricLabelsDocumento(RawValue):
    def __init__(self, position, count):
        if count == 1:
            super().__init__(position, 1, self.parserImage2Categoric, 'parserImage2Categoric',
                             self.parserCategoricLabelsDocumento, 'parserCategoricLabelsDocumento')
        else:
            raise Exception('Categoric values always should have count = 1')

    def __str__(self):
        return 'ImageCategoric  : ' + str(self.countItems)
class ImageCategoricLabelsTipoSuministro(RawValue):
    def __init__(self, position, count):
        if count == 1:
            super().__init__(position, 1, self.parserImage2Categoric, 'parserImage2Categoric',
                             self.parserCategoricLabelsTipoSuministro, 'parserCategoricLabelsTipoSuministro')
        else:
            raise Exception('Categoric values always should have count = 1')

    def __str__(self):
        return 'ImageCategoric  : ' + str(self.countItems)
class ArrayPredictedNumber(RawValue):
    def __init__(self, value):
        super().__init__(value, -1, None, 'parserImage2ArrayChar',None, 'digitPredictor')

    def __str__(self):
        return 'ArrayPredictedNumber: ' + str(self.countItems)

class ArrayPredictedChar(RawValue):
    def __init__(self, value):
        super().__init__(value, -1, None, 'parserImage2ArrayChar', None, 'letterPredictor')
    def __str__(self):
        return 'ArrayPredictedChar: ' + str(self.countItems)

class PredictedCategoric(RawValue):
    def __init__(self, value):
        super().__init__(value, -1, None, 'parserImage2Categoric', None, None)

    def __str__(self):
        return 'PredictedCategoric  : ' + str(self.countItems)


import matplotlib.pyplot as plt
import math
class HarmonicOscilator:
    def __init__(self):
        self.t = []
        self.x = []
        self.v = []
        self.a = []
    def init(self, x0, v0, masa = 1, konstanta = 20, faza0 = 0, dt = 0.01):
        self.t.append(0)
        self.x0 = x0
        self.x.append(self.x0)
        self.v.append(v0)
        self.m = masa
        self.k = konstanta
        self.fi = faza0
        self.omega = -self.k/self.m
        self.a.append((self.omega*self.x0))
        self.dt = dt
    def reset(self):
        self.t.clear()
        self.x.clear()
        self.v.clear()
        self.a.clear()
    def __move(self, i):
        self.t.append(self.t[i-1] + self.dt)
        self.a.append(self.omega*self.x[i-1])
        self.v.append(self.v[i-1] + self.a[i]*self.dt)
        self.x.append(self.x[i-1] + self.v[i]*self.dt)
    def titranje(self, t_):
        i_ = t_/self.dt
        i_round = int(i_)
        for i in range(1, i_round):
            self.__move(i)
        return self.x, self.t, self.v, self.a
    def analiticki_period(self):
        return 2*math.pi*math.sqrt(self.m/self.k)
    def period(self):
        i = 0
        T = 0
        while self.x[i] >= self.x0:
            self.__move(i)
            i += 1
            T += self.dt
        return 2*T
    def plot_xt(self):
        plt.plot(self.t, self.x)
        plt.xlabel("t [s]")
        plt.ylabel("x [m]")
        plt.title("Ovisnost pomaka o vremenskom intervalu")
        plt.show()
    def plot_vt(self):
        plt.plot(self.t, self.v)
        plt.xlabel("t [s]")
        plt.ylabel("v [m/s]")
        plt.title("Ovisnost brzine o vremenskom intervalu")
        plt.show()
    def plot_at(self):
        plt.plot(self.t, self.a)
        plt.xlabel("t [s]")
        plt.ylabel("a [m/s^2]")
        plt.title("Ovisnost akceleracije o vremenskom intervalu")
        plt.show()


import multiprocessing as mp

import pandas

import src.read.dearchive
import src.read.retrieve

class Interface:

    def __init__(self, var):
        """
        The constructor
        :param var: The dot map of parameters
        """
        self.var = var

        # Instances of dearchive & retrieve
        self.dearchive = src.read.dearchive.Dearchive(self.var)
        self.retrieve = src.read.retrieve.Retrieve(self.var)

    @staticmethod
    def iterables(metadata: pandas.DataFrame) -> (list, list):
        """

        :param metadata: A dataframe that includes a field of url strings
                         and file name strings; each must include a file extension.
        :return:
        """

        urlstrings = metadata.urlstring.to_list()
        filestrings = metadata.filestring.to_list()

        return urlstrings, filestrings

    def exc(self, metadata: pandas.DataFrame) -> None:
        """
        Slow:
            '.zip': self.pool.starmap(self.dearchive.unzip, [{i} for i in urlstrings])
            self.pool.starmap(self.retrieve.exc, zip(urlstrings, filestrings))

        :param metadata: A dataframe that includes a field of url strings
                         and file name strings; each must include a file extension.
        :return:
        """

        urlstrings, filestrings = self.iterables(metadata=metadata)

        print('Download starting ...')

        # Parallel processing anchor
        pool = mp.Pool(mp.cpu_count())

        # This set-up makes it easy to consider other types of archived files over time
        if self.var.source.archived:
            {
                '.zip': pool.map_async(self.dearchive.unzip, urlstrings).get()
            }.get(self.var.source.ext, LookupError('Unknown extension'))

        else:
            pool.starmap_async(self.retrieve.exc, zip(urlstrings, filestrings)).get()

        # Close
        pool.close()

        print('Download finished')


from pydantic import BaseModel
from ..enums.enums import MESSAGE_TYPE, MessageTypeEncoder
import json
from typing import Optional

class Message(BaseModel):
    message_type: MESSAGE_TYPE
    source_node: int
    # message_status: MESSAGE_STATUS
    message_id: int
    proposal_number: int
    value: Optional[str]

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

class MessageEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Message):
            # Convert the Message object to a dictionary
            return {
                "message_type": json.dumps(obj.message_type, cls=MessageTypeEncoder),
                "source_node": obj.source_node,
                "message_id": obj.message_id,
                "proposal_number": obj.proposal_number,
                "value": obj.value,
            }
        return super().default(obj)