from __future__ import unicode_literals

from datetime import date
import json
from os.path import join
import re

from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ObjectDoesNotExist
from django.core.files.storage import FileSystemStorage
from django.core.urlresolvers import reverse
from django.db import models
from django.template import loader, Context
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _l
from django.utils.six.moves.urllib_parse import urljoin, quote_plus

from usersettings.shortcuts import get_current_usersettings

from dateutil import parser
from slugify import slugify
from django_date_extensions.fields import ApproximateDate

from elections.models import Election, AreaType
from popolo.models import (
    ContactDetail, Person, Organization, Post, Membership, Area, Identifier
)
from images.models import Image, HasImageMixin

from compat import python_2_unicode_compatible
from .fields import (
    ExtraField, PersonExtraFieldValue, SimplePopoloField, ComplexPopoloField,
    get_complex_popolo_fields,
)
from ..diffs import get_version_diffs
from ..twitter_api import update_twitter_user_id, TwitterAPITokenMissing
from .sitesettings import get_site_setting
from .versions import get_person_as_version_data

"""Extensions to the base django-popolo classes for YourNextRepresentative

These are done via explicit one-to-one fields to avoid the performance
problems with multi-table inheritance; it's preferable to state when you
want a join or not.

  http://stackoverflow.com/q/23466577/223092

"""

def update_person_from_form(person, person_extra, form):
    form_data = form.cleaned_data.copy()
    # The date is returned as a datetime.date, so if that's set, turn
    # it into a string:
    birth_date_date = form_data['birth_date']
    if birth_date_date:
        form_data['birth_date'] = repr(birth_date_date).replace("-00-00", "")
    else:
        form_data['birth_date'] = ''
    for field in SimplePopoloField.objects.all():
        setattr(person, field.name, form_data[field.name])
    for field in ComplexPopoloField.objects.all():
        person_extra.update_complex_field(field, form_data[field.name])
    for extra_field in ExtraField.objects.all():
        if extra_field.key in form_data:
            PersonExtraFieldValue.objects.update_or_create(
                person=person, field=extra_field,
                defaults={'value': form_data[extra_field.key]}
            )
    person.save()
    person_extra.save()
    try:
        update_twitter_user_id(person)
    except TwitterAPITokenMissing:
        pass
    for election_data in form.elections_with_fields:
        post_id = form_data.get('constituency_' + election_data.slug)
        standing = form_data.pop('standing_' + election_data.slug, 'standing')
        if post_id:
            party_set = PartySet.objects.get(postextra__slug=post_id)
            party_key = 'party_' + party_set.slug + '_' + election_data.slug
            position_key = \
                'party_list_position_' + party_set.slug + '_' + election_data.slug
            party = Organization.objects.get(pk=form_data[party_key])
            party_list_position = form_data.get(position_key) or None
            post = Post.objects.get(extra__slug=post_id)
        else:
            party = None
            party_list_position = None
        # Get a queryset of the existing candidacies:
        candidacy_qs = Membership.objects.filter(
            extra__election=election_data,
            role=election_data.candidate_membership_role,
            person__extra=person_extra
        )
        # Preserve the 'elected' property of MembershipExtra, which
        # isn't in the form:
        elected_saved = dict(
            candidacy_qs.values_list('extra__election__slug', 'extra__elected')
        )
        # Remove any existing memberships; we'll recreate them if the
        # person's actually standing. (Since MembershipExtra depends
        # on Membership, this will delete any corresponding Membership.)
        candidacy_qs.delete()
        # Remove any indication that they're not standing in that
        # election. Similarly we'll recreate it if necessary:
        person_extra.not_standing.remove(election_data)
        if standing == 'standing':
            # Create the new membership:
            membership = Membership.objects.create(
                post=post,
                on_behalf_of=party,
                person=person,
                role=election_data.candidate_membership_role,
            )
            MembershipExtra.objects.create(
                base=membership,
                party_list_position=party_list_position,
                election=election_data,
                elected=elected_saved.get(election_data.slug)
            )
        elif standing == 'not-standing':
            person_extra.not_standing.add(election_data)

def squash_whitespace(s):
    # Take any run of more than one whitespace character, and replace
    # it either with a newline (if that replaced text contained a
    # newline) or a space (otherwise).
    return re.sub(
        r'(?ims)\s+',
        lambda m: '\n' if '\n' in m.group(0) else ' ',
        s)

class localparserinfo(parser.parserinfo):
    MONTHS = [
        ('Jan', _l('Jan'), 'January', _l('January')),
        ('Feb', _l('Feb'), 'February', _l('February')),
        ('Mar', _l('Mar'), 'March', _l('March')),
        ('Apr', _l('Apr'), 'April', _l('April')),
        ('May', _l('May'), 'May', _l('May')),
        ('Jun', _l('Jun'), 'June', _l('June')),
        ('Jul', _l('Jul'), 'July', _l('July')),
        ('Aug', _l('Aug'), 'August', _l('August')),
        ('Sep', _l('Sep'), 'Sept', 'September', _l('September')),
        ('Oct', _l('Oct'), 'October', _l('October')),
        ('Nov', _l('Nov'), 'November', _l('November')),
        ('Dec', _l('Dec'), 'December', _l('December'))
    ]

    PERTAIN = ['of', _l('of')]

def parse_approximate_date(s):
    """Take any reasonable date string, and return an ApproximateDate for it

    >>> ad = parse_approximate_date('2014-02-17')
    >>> type(ad)
    <class 'django_date_extensions.fields.ApproximateDate'>
    >>> ad
    2014-02-17
    >>> parse_approximate_date('2014-02')
    2014-02-00
    >>> parse_approximate_date('2014')
    2014-00-00
    >>> parse_approximate_date('future')
    future
    """

    user_settings = get_current_usersettings()

    for regexp in [
        r'^(\d{4})-(\d{2})-(\d{2})$',
        r'^(\d{4})-(\d{2})$',
        r'^(\d{4})$'
    ]:
        m = re.search(regexp, s)
        if m:
            return ApproximateDate(*(int(g, 10) for g in m.groups()))
    if s == 'future':
        return ApproximateDate(future=True)
    if s:
        dt = parser.parse(
            s,
            parserinfo=localparserinfo(),
            dayfirst=user_settings.DD_MM_DATE_FORMAT_PREFERRED
        )
        return ApproximateDate(dt.year, dt.month, dt.day)
    raise ValueError("Couldn't parse '{0}' as an ApproximateDate".format(s))

class PersonExtraQuerySet(models.QuerySet):

    def missing(self, field):
        people_in_current_elections = self.filter(
            base__memberships__extra__election__current=True
        )
        # The field can be one of several types:
        simple_field = SimplePopoloField.objects.filter(name=field).first()
        if simple_field:
            return people_in_current_elections.filter(**{'base__' + field: ''})
        complex_field = ComplexPopoloField.objects.filter(name=field).first()
        if complex_field:
            kwargs = {
                'base__{relation}__{key}'.format(
                    relation=complex_field.popolo_array,
                    key=complex_field.info_type_key
                ):
                complex_field.info_type
            }
            return people_in_current_elections.exclude(**kwargs)
        extra_field = ExtraField.objects.filter(key=field).first()
        if extra_field:
            # This case is a bit more complicated because the
            # PersonExtraFieldValue class allows a blank value.
            pefv_completed = PersonExtraFieldValue.objects.filter(
                field=extra_field
            ).exclude(value='')
            return people_in_current_elections.exclude(
                base__id__in=[pefv.person_id for pefv in pefv_completed]
            )
        # If we get to this point, it's a non-existent field on the person:
        raise ValueError("Unknown field '{0}'".format(field))

    def joins_for_csv_output(self):
        return self.select_related('base') \
            .prefetch_related(
                models.Prefetch(
                    'base__memberships',
                    Membership.objects.select_related(
                        'extra',
                        'extra__election',
                        'on_behalf_of__extra',
                        'post__area',
                        'post__extra',
                    ).prefetch_related(
                        'on_behalf_of__identifiers',
                        'post__area__other_identifiers',
                    )
                ),
                'base__contact_details',
                'base__identifiers',
                'base__links',
                'images__extra__uploading_user',
            )

class MultipleTwitterIdentifiers(Exception):
    pass

class VersionNotFound(Exception):
    pass

@python_2_unicode_compatible
class PersonExtra(HasImageMixin, models.Model):
    base = models.OneToOneField(Person, related_name='extra')

    # This field stores JSON data with previous version information
    # (as it did in PopIt).
    versions = models.TextField(blank=True)

    images = GenericRelation(Image)

    objects = PersonExtraQuerySet.as_manager()

    @cached_property
    def complex_popolo_fields(self):
        return get_complex_popolo_fields()

    def __getattr__(self, name):
        # We don't want to trigger the population of the
        # complex_popolo_fields property just because Django is
        # checking whether the prefetch objects cache is there:
        if name == '_prefetched_objects_cache':
            return super(PersonExtra, self).__getattr__(self, name)
        field = self.complex_popolo_fields.get(name)
        if field:
            # Iterate rather than using filter because that would
            # cause an extra query when the relation has already been
            # populated via select_related:
            for e in getattr(self.base, field.popolo_array).all():
                info_type_key = getattr(e, field.info_type_key)
                if (info_type_key == field.info_type) or \
                   (info_type_key == field.old_info_type):
                    return getattr(e, field.info_value_key)
            return ''
        else:
            message = _("'PersonExtra' object has no attribute '{name}'")
            raise AttributeError(message.format(name=name))

    def get_slug(self):
        return slugify(self.base.name)

    def get_absolute_url(self, request=None):
        path = reverse(
            'person-view',
            kwargs={
                'person_id': self.base.id,
                'ignored_slug': self.get_slug(),
            }
        )
        if request is None:
            return path
        return request.build_absolute_uri(path)

    @property
    def current_candidacies(self):
        result = self.base.memberships.filter(
            extra__election__current=True,
            role=models.F('extra__election__candidate_membership_role')
        ).select_related('person', 'on_behalf_of', 'post') \
            .prefetch_related('post__extra')
        return list(result)

    @property
    def last_candidacy(self):
        ordered_candidacies = Membership.objects. \
            filter(person=self.base, extra__election__isnull=False). \
            order_by('extra__election__current', 'extra__election__election_date')
        return ordered_candidacies.last()

    def last_party(self):
        last_candidacy = self.last_candidacy
        if last_candidacy is None:
            return None
        return last_candidacy.on_behalf_of

    def name_with_honorifics(self):
        name_parts = []
        pre = self.base.honorific_prefix
        post = self.base.honorific_suffix
        if pre:
            name_parts.append(pre)
        name_parts.append(self.base.name)
        if post:
            name_parts.append(post)
        return ' '.join(name_parts)

    @property
    def dob_as_approximate_date(self):
        return parse_approximate_date(self.base.birth_date)

    def dob_as_date(self):
        approx = self.dob_as_approximate_date
        return date(approx.year, approx.month, approx.day)

    @property
    def age(self):
        """Return a string representing the person's age"""

        dob = self.dob_as_approximate_date
        if not dob:
            return None
        today = date.today()
        approx_age = today.year - dob.year
        if dob.month == 0 and dob.day == 0:
            min_age = approx_age - 1
            max_age = approx_age
        elif dob.day == 0:
            min_age = approx_age - 1
            max_age = approx_age
            if today.month < dob.month:
                max_age = min_age
            elif today.month > dob.month:
                min_age = max_age
        else:
            # There's a complete date:
            dob_as_date = self.dob_as_date()
            try:
                today_in_birth_year = date(dob.year, today.month, today.day)
            except ValueError:
                # It must have been February 29th
                today_in_birth_year = date(dob.year, 3, 1)
            if today_in_birth_year > dob_as_date:
                min_age = max_age = today.year - dob.year
            else:
                min_age = max_age = (today.year - dob.year) -1
        if min_age == max_age:
            # We know their exact age:
            return str(min_age)
        return _("{min_age} or {max_age}").format(min_age=min_age,
                                                  max_age=max_age)

    """
    Return the elected state for a person in an election.
    Takes the election object as an arg.
    Returns True if they were elected, False if not and None if
    the results have not been set.
    This assumes that someone can only be elected in a single
    post in any election.
    """
    def get_elected(self, election):
        role = election.candidate_membership_role
        if role is None:
            role = ''
        membership = self.base.memberships \
            .select_related('extra') \
            .filter(
                role=role,
                extra__election=election,
            )

        result = membership.first()
        if result:
            return result.extra.elected

        return None

    @property
    def twitter_identifiers(self):
        screen_name = None
        user_id = None
        # Get the Twitter screen name and user ID if they exist:
        try:
            screen_name = self.base.contact_details \
                .get(contact_type='twitter').value
        except ContactDetail.DoesNotExist:
            screen_name = None
        except ContactDetail.MultipleObjectsReturned:
            msg = "Multiple Twitter screen names found for {name} ({id})"
            raise MultipleTwitterIdentifiers(
                _(msg).format(name=self.base.name, id=self.base.id))
        try:
            user_id = self.base.identifiers \
                .get(scheme='twitter').identifier
        except Identifier.DoesNotExist:
            user_id = None
        except Identifier.MultipleObjectsReturned:
            msg = "Multiple Twitter user IDs found for {name} ({id})"
            raise MultipleTwitterIdentifiers(
                _(msg).format(name=self.base.name, id=self.base.id))
        return user_id, screen_name

    @property
    def version_diffs(self):
        versions = self.versions
        if not versions:
            versions = []
        return get_version_diffs(json.loads(versions))

    def diff_for_version(self, version_id, inline_style=False):
        versions = self.versions or []
        all_version_diffs = get_version_diffs(json.loads(versions))
        right_version_diff = None
        for version_diff in all_version_diffs:
            if version_diff['version_id'] == version_id:
                right_version_diff = version_diff
                break
        if not right_version_diff:
            msg = "Couldn't find version {0} for person with ID {1}"
            raise VersionNotFound(msg.format(version_id, self.base.id))
        template = loader.get_template('candidates/_diffs_against_parents.html')
        context = Context({
            'diffs_against_all_parents': right_version_diff['diffs'],
            'inline_style': inline_style,
        })
        rendered = template.render(context)
        return squash_whitespace('<dl>{0}</dl>'.format(rendered))

    def record_version(self, change_metadata):
        versions = []
        if self.versions:
            versions = json.loads(self.versions)
        new_version = change_metadata.copy()
        new_version['data'] = get_person_as_version_data(self.base)
        versions.insert(0, new_version)
        self.versions = json.dumps(versions)

    def update_complex_field(self, location, new_value):
        existing_info_types = [location.info_type]
        if location.old_info_type:
            existing_info_types.append(location.old_info_type)
        related_manager = getattr(self.base, location.popolo_array)
        # Remove the old entries of that type:
        kwargs = {
            (location.info_type_key + '__in'): existing_info_types
        }
        related_manager.filter(**kwargs).delete()
        if new_value:
            kwargs = {
                location.info_type_key: location.info_type,
                location.info_value_key: new_value,
            }
            related_manager.create(**kwargs)

    def get_initial_form_data(self):
        initial_data = {}
        for field in SimplePopoloField.objects.all():
            initial_data[field.name] = getattr(self.base, field.name)
        for field in ComplexPopoloField.objects.all():
            initial_data[field.name] = getattr(self, field.name)
        for extra_field_value in PersonExtraFieldValue.objects.filter(
                person=self.base
        ).select_related('field'):
            initial_data[extra_field_value.field.key] = extra_field_value.value
        not_standing_elections = set(self.not_standing.all())
        election_to_membershipextra = {
            me.election: me for me in
            MembershipExtra.objects.filter(
                base__person__extra=self,
                election__current=True,
            ).select_related(
                'election',
                'base__post__extra',
                'base__on_behalf_of',
            )
        }
        for election_data in Election.objects.current().by_date():
            constituency_key = 'constituency_' + election_data.slug
            standing_key = 'standing_' + election_data.slug
            candidacy = election_to_membershipextra.get(election_data)
            if election_data in not_standing_elections:
                initial_data[standing_key] = 'not-standing'
            elif candidacy:
                initial_data[standing_key] = 'standing'
                post_id = candidacy.base.post.extra.slug
                initial_data[constituency_key] = post_id
                party_set = PartySet.objects.get(postextra__slug=post_id)
                party = candidacy.base.on_behalf_of
                party_key = 'party_' + party_set.slug + '_' + election_data.slug
                initial_data[party_key] = party.id
                position = candidacy.party_list_position
                position_key = 'party_list_position_' + party_set.slug + '_' + election_data.slug
                if position:
                    initial_data[position_key] = position
            else:
                initial_data[standing_key] = 'not-sure'
                initial_data[constituency_key] = ''
        return initial_data

    def update_from_form(self, form):
        update_person_from_form(self.base, self, form)

    @classmethod
    def create_from_form(cls, form):
        person = Person.objects.create(
            name=form.cleaned_data['name'],
        )
        person_extra = PersonExtra.objects.create(
            base=person,
        )
        update_person_from_form(person, person_extra, form)
        return person_extra

    def as_list_of_dicts(self, election, base_url=None):
        result = []
        user_settings = get_current_usersettings()
        if not base_url:
            base_url = ''
        # Find the list of relevant candidacies. So as not to cause
        # extra queries, we don't use filter but instead iterate over
        # all objects:
        candidacies = []
        for m in self.base.memberships.all():
            try:
                m_extra = m.extra
            except ObjectDoesNotExist:
                continue
            if not m_extra.election:
                continue
            expected_role = m.extra.election.candidate_membership_role
            if election is None:
                if expected_role == m.role:
                    candidacies.append(m)
            else:
                if m_extra.election == election and expected_role == m.role:
                    candidacies.append(m)
        for candidacy in candidacies:
            candidacy_extra = candidacy.extra
            party = candidacy.on_behalf_of
            post = candidacy.post
            elected = candidacy_extra.elected
            elected_for_csv = ''
            image_copyright = ''
            image_uploading_user = ''
            image_uploading_user_notes = ''
            proxy_image_url_template = ''
            if elected is not None:
                elected_for_csv = str(elected)
            mapit_identifier = None
            for identifier in post.area.other_identifiers.all():
                if identifier.scheme == 'mapit-area-url':
                    mapit_identifier = identifier
            if mapit_identifier:
                mapit_url = mapit_identifier.identifier
            else:
                mapit_url = ''
            primary_image = None
            for image in self.images.all():
                if image.is_primary:
                    primary_image = image
            primary_image_url = None
            if primary_image:
                primary_image_url = urljoin(base_url, primary_image.image.url)
                if user_settings.IMAGE_PROXY_URL and base_url:
                    encoded_url = quote_plus(primary_image_url)
                    proxy_image_url_template = user_settings.IMAGE_PROXY_URL + \
                        encoded_url + '/{height}/{width}.{extension}'

                try:
                    image_copyright = primary_image.extra.copyright
                    user = primary_image.extra.uploading_user
                    if user is not None:
                        image_uploading_user = primary_image.extra.uploading_user.username
                    image_uploading_user_notes = primary_image.extra.user_notes
                except ObjectDoesNotExist:
                    pass
            twitter_user_id = ''
            for identifier in self.base.identifiers.all():
                if identifier.scheme == 'twitter':
                    twitter_user_id = identifier.identifier

            row = {
                'id': self.base.id,
                'name': self.base.name,
                'honorific_prefix': self.base.honorific_prefix,
                'honorific_suffix': self.base.honorific_suffix,
                'gender': self.base.gender,
                'birth_date': self.base.birth_date,
                'election': candidacy_extra.election.slug,
                'election_date': candidacy_extra.election.election_date,
                'election_current': candidacy_extra.election.current,
                'party_id': party.extra.slug,
                'party_lists_in_use': candidacy_extra.election.party_lists_in_use,
                'party_list_position': candidacy_extra.party_list_position,
                'party_name': party.name,
                'post_id': post.extra.slug,
                'post_label': post.extra.short_label,
                'mapit_url': mapit_url,
                'elected': elected_for_csv,
                'email': self.base.email,
                'twitter_username': self.twitter_username,
                'twitter_user_id': twitter_user_id,
                'facebook_page_url': self.facebook_page_url,
                'linkedin_url': self.linkedin_url,
                'party_ppc_page_url': self.party_ppc_page_url,
                'facebook_personal_url': self.facebook_personal_url,
                'homepage_url': self.homepage_url,
                'wikipedia_url': self.wikipedia_url,
                'image_url': primary_image_url,
                'proxy_image_url_template': proxy_image_url_template,
                'image_copyright': image_copyright,
                'image_uploading_user': image_uploading_user,
                'image_uploading_user_notes': image_uploading_user_notes,
            }
            from ..election_specific import get_extra_csv_values
            extra_csv_data = get_extra_csv_values(self.base, election, post)
            row.update(extra_csv_data)
            result.append(row)

        return result

    not_standing = models.ManyToManyField(
        Election, related_name='persons_not_standing'
    )

    def __str__(self):
        # WARNING: This will cause an extra query when getting the
        # repr() or unicode() of this object unless the base object
        # has been select_related.
        return self.base.name

@python_2_unicode_compatible
class OrganizationExtra(HasImageMixin, models.Model):
    base = models.OneToOneField(Organization, related_name='extra')
    slug = models.CharField(max_length=256, blank=True, unique=True)

    # For parties, which party register is it on:
    register = models.CharField(blank=True, max_length=512)

    images = GenericRelation(Image)

    def __str__(self):
        # WARNING: This will cause an extra query when getting the
        # repr() or unicode() of this object unless the base object
        # has been select_related.
        return self.base.name

@python_2_unicode_compatible
class PostExtra(HasImageMixin, models.Model):
    base = models.OneToOneField(Post, related_name='extra')
    slug = models.CharField(max_length=256, blank=True, unique=True)

    candidates_locked = models.BooleanField(default=False)
    elections = models.ManyToManyField(
        Election,
        related_name='posts',
        through='PostExtraElection'
    )
    group = models.CharField(max_length=1024, blank=True)
    party_set = models.ForeignKey('PartySet', blank=True, null=True)

    def __str__(self):
        # WARNING: This will cause an extra query when getting the
        # repr() or unicode() of this object unless the base object
        # has been select_related.
        return self.base.label

    @property
    def short_label(self):
        from candidates.election_specific import shorten_post_label
        return shorten_post_label(self.base.label)

class PostExtraElection(models.Model):
    postextra = models.ForeignKey(PostExtra)
    election = models.ForeignKey(Election)

    winner_count = models.IntegerField(blank=True, null=True)

class MembershipExtra(models.Model):
    base = models.OneToOneField(Membership, related_name='extra')

    elected = models.NullBooleanField()
    party_list_position = models.IntegerField(null=True)
    election = models.ForeignKey(
        Election, blank=True, null=True, related_name='candidacies'
    )

@python_2_unicode_compatible
class AreaExtra(models.Model):
    base = models.OneToOneField(Area, related_name='extra')

    type = models.ForeignKey(AreaType, blank=True, null=True, related_name='areas')

    def __str__(self):
        # WARNING: This will cause an extra query when getting the
        # repr() or unicode() of this object unless the base object
        # has been select_related.
        return self.base.name

@python_2_unicode_compatible
class PartySet(models.Model):
    slug = models.CharField(max_length=256, unique=True)
    name = models.CharField(max_length=1024)
    parties = models.ManyToManyField(Organization, related_name='party_sets')

    def __str__(self):
        return self.name

    def party_choices_basic(self):
        result = list(self.parties.order_by('name').values_list('id', 'name'))
        result.insert(0, ('party:none', ''))
        return result

    def party_choices(self):
        # For various reasons, we've found it's best to order the
        # parties by those that have the most candidates - this means
        # that the commonest parties to select are at the top of the
        # drop down.  The logic here tries to build such an ordered
        # list of candidates if there are enough that such an ordering
        # makes sense.  Otherwise the fallback is to rank
        # alphabetically.
        candidacies_current_qs = Membership.objects.filter(
            extra__election__current=True,
            role=models.F('extra__election__candidate_membership_role'),
            on_behalf_of__party_sets=self,
        )
        candidacies_ever_qs =  Membership.objects.filter(
            role=models.F('extra__election__candidate_membership_role'),
            on_behalf_of__party_sets=self,
        )
        minimum_count = get_site_setting(
            'CANDIDATES_REQUIRED_FOR_WEIGHTED_PARTY_LIST')
        qs = None
        if candidacies_current_qs.count() > minimum_count:
            qs = candidacies_current_qs
        elif candidacies_ever_qs.count() > minimum_count:
            qs = candidacies_ever_qs
        else:
            return self.party_choices_basic()
        result = [('party:none', '')]
        parties_with_candidates = []
        for party_tuple in qs \
                .values('on_behalf_of', 'on_behalf_of__name') \
                .order_by() \
                .annotate(party_count=models.Count('pk')) \
                .order_by('-party_count', 'on_behalf_of__name'):
            parties_with_candidates.append(party_tuple['on_behalf_of'])
            name_with_count = \
                _('{party_name} ({number_of_candidates} candidates)').format(
                    party_name=party_tuple['on_behalf_of__name'],
                    number_of_candidates=party_tuple['party_count']
                )
            result.append(
                (party_tuple['on_behalf_of'], name_with_count)
            )
        result += self.parties.exclude(id__in=parties_with_candidates) \
            .order_by('name').values_list('id', 'name')
        return result

class ImageExtraManager(models.Manager):

    def create_from_file(
            self, image_filename, ideal_relative_name, base_kwargs, extra_kwargs
    ):
        # Import the file to media root and create the ORM
        # objects.
        storage = FileSystemStorage()
        desired_storage_path = join('images', ideal_relative_name)
        with open(image_filename, 'rb') as f:
            storage_filename = storage.save(desired_storage_path, f)
        image = Image.objects.create(image=storage_filename, **base_kwargs)
        return ImageExtra.objects.create(base=image, **extra_kwargs)

    def update_or_create_from_file(
            self, image_filename, ideal_relative_name, defaults, **kwargs
    ):
        try:
            image_extra = ImageExtra.objects \
                .select_related('base').get(**kwargs)
            for k, v in defaults.items():
                if k.startswith('base__'):
                    base_k = re.sub(r'^base__', '', k)
                    setattr(image_extra.base, base_k, v)
                else:
                    setattr(image_extra, k, v)
            image_extra.save()
            image_extra.base.save()
            return image_extra, False
        except ImageExtra.DoesNotExist:
            # Prepare args for the base object first:
            base_kwargs = {
                re.sub(r'base__', '', k): v for k, v in defaults.items()
                if k.startswith('base__')
            }
            base_kwargs.update({
                re.sub(r'base__', '', k): v for k, v in kwargs.items()
                if k.startswith('base__')
            })
            # And now the extra object:
            extra_kwargs = {
                k: v for k, v in defaults.items() if not k.startswith('base__')
            }
            extra_kwargs.update({
                k: v for k, v in kwargs.items() if not k.startswith('base__')
            })
            image_extra = self.create_from_file(
                image_filename, ideal_relative_name, base_kwargs, extra_kwargs
            )
        return image_extra

class ImageExtra(models.Model):
    base = models.OneToOneField(Image, related_name='extra')

    copyright = models.CharField(max_length=64, default='other', blank=True)
    uploading_user = models.ForeignKey(User, blank=True, null=True)
    user_notes = models.TextField(blank=True)
    md5sum = models.CharField(max_length=32, blank=True)
    user_copyright = models.CharField(max_length=128, blank=True)
    notes = models.TextField(blank=True)

    objects = ImageExtraManager()


import pandas as pd
from glob import glob
from pprint import pprint as pp
import os
import src

# TODO: add path to psi4/db to PYTHONPATH to get access to database
# ex. import BBI
#     BBI.bind[key]

# might look at cdsgroup/qcdb --branch data
#   has citations for binds based on references

def extrapolate_energies(C1, C2, E1, E2):
    """Test extrapolation of two individual correlation energies, to
    help test the operations on the dataframe

    Arguments:
        C1: Cardinal number of basis set 1
        C2: Cardinal number of basis set 2
        E1: Energy in basis set 1
        E2: Energy in basis set 2

    Returns:
        Extrapolated energy

    Note:
        Always be careful about mixing integer and floating point
        arithmetic.  It looks like Python converts stuff for us correctly
        in this case.
    """

    # Set "large" basis (L) and "small" basis (S) cardinal numbers and energies
    # (the order matters)
    if C1 == C2 + 1:
        L = C1
        S = C2
        EL = E1
        ES = E2
    elif C2 == C1 + 1:
        L = C2
        S = C1
        EL = E2
        ES = E1
    else:
        raise ValueError("Cardinal numbers must be adjacent integers")

    E_CBS = (L**3 * EL - S**3 * ES) / (L**3 - S**3)

    return E_CBS

def extrapolate_energies_df(
    c1_data: dict = {
        "df_path": "sapt_ref_data/adz/hbc6-plat-adz-all.pkl",
        "c_label": "DZ",
        "C1": 2,
    },
    c2_data: dict = {
        "df_path": "sapt_ref_data/atz/hbc6-plat-atz-all.pkl",
        "c_label": "TZ",
        "C2": 3,
    },
    df_out: str = "sapt_ref_data/cbs/hbc6-plat-adtz-all.pkl",
):
    """Take in two dictionaries, one for results in a smaller basis
    (c1_data), and one for results in a larger basis (c2_data), and
    a path for an output dataframe in pkl format.  The dictionaries
    contain a path for a dataframe in pkl format, a label (e.g.,
    DZ, TZ), and a cardinal number for the basis set (e.g., 2 for DZ,
    3 for TZ, etc.).  The cardinal numbers must be adjacent.

    The function then does a 2-point Helgaker extrapolation on columns
    with SAPT data matching one of the labels below in extrap_columns
    (only electron-correlation related columns).  It copies over
    Hartree-Fock level columns (e.g., SAPT ELST10,R ENERGY and others
    listed in copy_from_large_basis_columns).

    The resulting dataframe is written out to the pkl file specified

    A couple of default dictionaries are provided for testing purposes.
    """

    # some notes about exchange-scaling:
    # many of the fundamental SAPT terms are computed using the
    # single-exchange (S^2) approximation.  In Ed Hohenstein's original
    # SAPT code, we scaled all of these by the ratio
    # E_{exch}^{(10)} / E_{exch}^{(10)(S^2)} = "SAPT EXCHSCAL1"
    # to approximately compensate for the S^2 approximation.  However,
    # I do not recall any theory paper that really demonstrated that
    # this scaling is necessarily very effective (in SAPT(DFT) Hesselmann
    # found that scaling E_{exch-disp}^{(20)} by a simple constant
    # was more effective than using the above ratio.
    #
    # Ultimately, we decided at some point to reverse Ed's exchange
    # scaling and not scale these terms, especially for higher-order
    # SAPT.  I don't recall if other codes commonly scale
    # E_{exch-disp}^{(20)} and E_{exch-ind}^{(20)} or not, but even
    # if there is some literature precedent for that, I got concerned
    # about scaling the higher-order (S^2) terms without more evidence
    # that it was a good idea.  We universally stopped scaling any of
    # these terms (even for SAPT0) by default, although the scaling
    # can be turned back on, which would then default to using
    # the above ratio, SAPT EXCHSCAL1, or alternatively this factor
    # can also be used raised to higher powers, determined by
    # "SAPT ALPHA", providing the final scaling factor "SAPT EXCHSCAL".
    #
    # If no exchange scaling is done, then that is effectively the same
    # as using SAPT ALPHA = 0, yielding SAPT EXCHSCAL = 1.0.
    #
    # A special value of SAPT ALPHA = 3 is used to obtain the
    # so-called scaled SAPT0, or sSAPT0, which we found to work better
    # for many short-range contacts (e.g., 10.1039/c8cp02029a).
    # (On the other hand, we found that some *very* close contacts
    # get over-corrected by sSAPT0, e.g., the Splinter dataset paper 2023).
    # The exchange scaling ratio to the third power is stored in
    # SAPT EXCHSCAL3, which is used the formulas to compute sSAPT energies.
    # -CDS 7/27/23

    df_c1 = pd.read_pickle(c1_data["df_path"])
    df_c2 = pd.read_pickle(c2_data["df_path"])
    C1 = c1_data["C1"]
    C2 = c2_data["C2"]
    c1_label = c1_data["c_label"]
    c2_label = c2_data["c_label"]

    # now extrapolate the SAPT0 dispersion energy
    # df_tmp = (3**3) / (3**3 - 2**3) * df_t["SAPT0 DISP ENERGY"] - (
    #     (2**3) / (3**3 - 2**3)
    # ) * df_d["SAPT0 DISP ENERGY"]
    # Need to grab all columns that are 'correlated' terms

    # list all possible columns to extrapolate.  if a lower level of
    # SAPT was computed, some of these columns might not be available
    # do not extrapolate quantities that are derived from more fundamental
    # quantities ... just re-compute those using our SAPT variable
    # computation code adapted from Psi4
    extrap_columns = [
        "SAPT DISP20 ENERGY",
        "SAPT DISP21 ENERGY",
        "SAPT DISP22(S)(CCD) ENERGY",
        'SAPT DISP2(CCD) ENERGY',
        "SAPT DISP22(SDQ) ENERGY",
        "SAPT DISP22(T) ENERGY",
        "SAPT DISP22(T)(CCD) ENERGY",
        "SAPT DISP30 ENERGY",
        "SAPT ELST12,R ENERGY",
        "SAPT ELST13,R ENERGY",
        "SAPT EST.DISP22(T) ENERGY",
        "SAPT EST.DISP22(T)(CCD) ENERGY",
        "SAPT EXCH-DISP20 ENERGY",
        "SAPT EXCH-DISP30 ENERGY",
        "SAPT EXCH-IND-DISP30 ENERGY",
        "SAPT EXCH-IND22 ENERGY",
        "SAPT EXCH-IND30,R ENERGY",
        "SAPT EXCH11(S^2) ENERGY",
        "SAPT EXCH12(S^2) ENERGY",
        "SAPT IND-DISP30 ENERGY",
        "SAPT IND22 ENERGY",
        "SAPT IND30,R ENERGY",
        "SAPT MP2 CORRELATION ENERGY",  # supermolecular MP2 E_corr for dMP2
    ]
    copy_from_larger_basis_columns = [
        "CURRENT REFERENCE ENERGY",
        "CURRENT CORRELATION ENERGY",
        "SAPT ALPHA",
        "SAPT ELST10,R ENERGY",
        "SAPT EXCH10 ENERGY",
        "SAPT EXCH10(S^2) ENERGY",
        "SAPT IND20,R ENERGY",
        "SAPT EXCH-IND20,R ENERGY",
        "SAPT IND20,U ENERGY",
        "SAPT EXCH-IND20,U ENERGY",
        "SAPT HF TOTAL ENERGY",
    ]

    df_c1.columns = df_c1.columns.values + f" ({c1_label})"
    df_c2.columns = df_c2.columns.values + f" ({c2_label})"
    df = pd.concat([df_c1, df_c2], axis=1)

    # copy HF-level data (not depending on electron correlation) from the
    # larger basis, just like we would do in focal-point methods
    for i in copy_from_larger_basis_columns:
        if f"{i} ({c2_label})" in df_c2.columns.values:
            df[i] = df[i + f" ({c2_label})"]
            r1 = df.iloc[0]
            v = r1[i]
            print(f"{i} {v}")

    # extrapolate the electron-correlation dependent terms
    for i in extrap_columns:
        df[i] = df.apply(
            lambda r: extrapolate_energies(
                2, 3, r[i + f" ({c1_label})"], r[i + f" ({c2_label})"]
            ),
            axis=1,
        )
    subset = [
        i
        for i in df.columns.values
        if f" ({c1_label})" not in i and f" ({c2_label})" not in i
    ]
    df = df[subset].copy()
    # now compute SAPT terms from the extrapolated energies
    df = src.compute_sapt_terms.compute_sapt_terms(df)
    # print(df.columns.values)
    # now select the new data for export
    df.to_pickle(df_out)
    return

def generate_output_pkls():
    # Ok mess around for now and test
    fs_adz = glob("sapt_ref_data/adz/*.pkl")
    fs_atz = glob("sapt_ref_data/atz/*.pkl")
    fs_qz = glob("sapt_ref_data/aqz/*.pkl")
    print("Creating: aDZ + aTZ -> aDTZ")
    for i in fs_atz:
        db_atz = i.split("/")[-1].split("-")[0]
        for j in fs_adz:
            db_adz = j.split("/")[-1].split("-")[0]
            if db_atz == db_adz:
                df_path_out = i.replace("atz", "adtz")
                print(db_atz, i, j)
                print("Extrapolated results:", df_path_out, end="\n\n")
                dir_path = "/".join(df_path_out.split("/")[:-1])
                if not os.path.exists(dir_path):
                    os.mkdir(dir_path)
                extrapolate_energies_df(
                    c1_data={
                        "df_path": j,
                        "c_label": "DZ",
                        "C1": 2,
                    },
                    c2_data={
                        "df_path": i,
                        "c_label": "TZ",
                        "C2": 3,
                    },
                    df_out=df_path_out,
                )
    print("Creating: aTZ + aQZ -> aTQZ")
    for i in fs_atz:
        db_atz = i.split("/")[-1].split("-")[0]
        for j in fs_qz:
            db_aqz = j.split("/")[-1].split("-")[0]
            if db_atz == db_aqz:
                print(db_atz, i, j)
                print("Extrapolated results:", df_path_out, end="\n\n")
                df_path_out = i.replace("atz", "atqz")
                dir_path = "/".join(df_path_out.split("/")[:-1])
                if not os.path.exists(dir_path):
                    os.mkdir(dir_path)
                extrapolate_energies_df(
                    c1_data={
                        "df_path": i,
                        "c_label": "TZ",
                        "C1": 3,
                    },
                    c2_data={
                        "df_path": j,
                        "c_label": "QZ",
                        "C2": 4,
                    },
                    df_out=df_path_out,
                )
    return

def main():
    generate_output_pkls()
    # df = pd.read_pickle("sapt_ref_data/adz/hbc6-plat-adz-all.pkl")
    # print(df.columns.values)
    # pd.set_option("display.max_columns", None)
    # pd.set_option("display.max_rows", None)
    # print(df.iloc[0])
    # df['SAPT0_test'] = df.apply(lambda r: r['SAPT HF TOTAL ENERGY'] + r['SAPT DISP20 ENERGY'] + r["SAPT EXCH-DISP20 ENERGY"], axis=1)
    # print(df.iloc[0][["SAPT0_test", "SAPT0 TOTAL ENERGY"]])
    # r1 = df.iloc[0][["SAPT0_test", "SAPT0 TOTAL ENERGY"]].values
    # assert r1[0] == r1[1]
    return

if __name__ == "__main__":
    main()


import os
import sys
import json
import logging as logger
import boto3
from botocore.config import Config
from botocore.exceptions import ClientError
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes

import time

logger.getLogger().setLevel(logger.INFO)

def get_aws_client(name):
    return boto3.client(
        name,
        config=Config(retries={"max_attempts": 10, "mode": "standard"}),
    )

def create_resources(
    thing_name: str,
    iot_policy: str,
    iot_policy_name: str,
    stack_name: str,
    encryption_algo: str,
):
    """Create AWS IoT thing, certificate and attach certificate with policy and thing.
    Returns the Arns for the values and a Parameter Store Arn for the private key
    """
    c_iot = get_aws_client("iot")
    c_ssm = get_aws_client("ssm")

    result = {}

    # Create thing
    try:
        response = c_iot.create_thing(thingName=thing_name)
        result["ThingArn"] = response["thingArn"]
        result["ThingName"] = response["thingName"]
    except ClientError as e:
        logger.error(f"Error creating thing {thing_name}, {e}")
        sys.exit(1)

    # Create certificate and private key
    if encryption_algo == "ECC":
        key = ec.generate_private_key(curve=ec.SECP256R1(), backend=default_backend())
    elif encryption_algo == "RSA":
        key = rsa.generate_private_key(
            public_exponent=65537, key_size=4096, backend=default_backend()
        )
    else:
        logger.error(
            f"Should not get here. Encryption algorithm of 'ECC' or 'RSA' expected, received {encryption_algo}. Exiting"
        )
        sys.exit(1)

    private_key = key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=serialization.NoEncryption(),
    ).decode("utf-8")

    # Generate a CSR and set subject (CN=dispenserId)
    csr = (
        x509.CertificateSigningRequestBuilder()
        .subject_name(
            x509.Name(
                [
                    # Provide various details about who we are.
                    x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
                    x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "CO"),
                    x509.NameAttribute(NameOID.LOCALITY_NAME, "Denver"),
                    x509.NameAttribute(
                        NameOID.ORGANIZATION_NAME, "Greengrass Accelerator Testing"
                    ),
                    x509.NameAttribute(NameOID.COMMON_NAME, thing_name),
                ]
            )
        )
        .sign(key, hashes.SHA256(), default_backend())
    )
    try:
        response = c_iot.create_certificate_from_csr(
            certificateSigningRequest=str(
                csr.public_bytes(serialization.Encoding.PEM), "utf-8"
            ),
            setAsActive=True,
        )
        certificate_pem = response["certificatePem"]
        result["CertificateArn"] = response["certificateArn"]
    except ClientError as e:
        logger.error(f"Error creating certificate, {e}")
        sys.exit(1)

    # policy
    try:
        response = c_iot.create_policy(
            policyName=iot_policy_name, policyDocument=iot_policy
        )
        result["IotPolicyArn"] = response["policyArn"]
    except ClientError as e:
        logger.error(f"Error creating policy {iot_policy_name}, {e}")
        sys.exit(1)

    # attach cert-pol
    try:
        c_iot.attach_policy(policyName=iot_policy_name, target=result["CertificateArn"])
    except ClientError as e:
        logger.error(
            f"Error attaching certificate {result['CertificateArn']} to policy {iot_policy_name}, {e}"
        )
        sys.exit(1)

    # attach cert-thing
    try:
        c_iot.attach_thing_principal(
            thingName=thing_name,
            principal=result["CertificateArn"],
        )
    except ClientError as e:
        logger.error(
            f"Error attaching certificate {result['CertificateArn']} to thing {thing_name}, {e}"
        )
        sys.exit(1)

    # store certificate and private key in SSM param store
    try:
        parameter_private_key = f"/{stack_name}/{thing_name}/private_key"
        parameter_certificate_pem = f"/{stack_name}/{thing_name}/certificate_pem"
        # private key
        response = c_ssm.put_parameter(
            Name=parameter_private_key,
            Description=f"Certificate private key for IoT thing {thing_name}",
            Value=private_key,
            Type="SecureString",
            Tier="Advanced",
        )
        result["PrivateKeySecretParameter"] = parameter_private_key
        # certificate pem
        response = c_ssm.put_parameter(
            Name=parameter_certificate_pem,
            Description=f"Certificate PEM for IoT thing {thing_name}",
            Value=certificate_pem,
            Type="String",
            Tier="Advanced",
        )
        result["CertificatePemParameter"] = parameter_certificate_pem
    except ClientError as e:
        logger.error(f"Error creating secure string parameters, {e}")
        sys.exit(1)

    # Additional data - these calls and responses are used in other constructs or external applications

    # Get the IoT-Data endpoint
    try:
        response = c_iot.describe_endpoint(endpointType="iot:Data-ATS")
        result["DataAtsEndpointAddress"] = response["endpointAddress"]
    except ClientError as e:
        logger.error(f"Could not obtain iot:Data-ATS endpoint, {e}")
        result["DataAtsEndpointAddress"] = "stack_error: see log files"

    # Get the Credential Provider endpoint
    try:
        response = c_iot.describe_endpoint(endpointType="iot:CredentialProvider")
        result["CredentialProviderEndpointAddress"] = response["endpointAddress"]
    except ClientError as e:
        logger.error(f"Could not obtain iot:CredentialProvider endpoint, {e}")
        result["CredentialProviderEndpointAddress"] = "stack_error: see log files"

    return result

def delete_resources(
    thing_name: str, certificate_arn: str, iot_policy_name: str, stack_name: str
):
    """Delete thing, certificate, and policy in reverse order. Check for modifications
    since create (policy versions, etc)"""

    c_iot = get_aws_client("iot")
    c_ssm = get_aws_client("ssm")

    # delete ssm param store
    try:
        parameter_private_key = f"/{stack_name}/{thing_name}/private_key"
        parameter_certificate_pem = f"/{stack_name}/{thing_name}/certificate_pem"
        c_ssm.delete_parameters(
            Names=[parameter_private_key, parameter_certificate_pem]
        )
    except ClientError as e:
        logger.error(f"Unable to delete parameter store values, {e}")

    # delete policy (prune versions, detach from targets)
    # delete all non active policy versions
    try:
        response = c_iot.list_policy_versions(policyName=iot_policy_name)
        for version in response["policyVersions"]:
            if not version["isDefaultVersion"]:
                c_iot.delete_policy_version(
                    policyName=iot_policy_name, policyVersionId=version["versionId"]
                )
    except ClientError as e:
        logger.error(
            f"Unable to delete policy versions for policy {iot_policy_name}, {e}"
        )
    # Detach any principals
    try:
        response = c_iot.list_targets_for_policy(policyName=iot_policy_name)
        for target in response["targets"]:
            c_iot.detach_policy(policyName=iot_policy_name, target=target)
    except ClientError as e:
        logger.error(f"Unable to detach targets from policy {iot_policy_name}, {e}")
    # delete policy
    try:
        c_iot.delete_policy(policyName=iot_policy_name)
    except ClientError as e:
        logger.error(f"Unable to delete policy {iot_policy_name}, {e}")

    # delete cert
    # detach all policies and things from cert
    try:
        response = c_iot.list_principal_things(principal=certificate_arn)
        for thing in response["things"]:
            c_iot.detach_thing_principal(thingName=thing, principal=certificate_arn)
        response = c_iot.list_attached_policies(target=certificate_arn)
        for policy in response["policies"]:
            c_iot.detach_policy(policyName=policy["policyName"], target=certificate_arn)
    except ClientError as e:
        logger.error(
            f"Unable to list or detach things or policies from certificate {certificate_arn}, {e}"
        )
    try:
        c_iot.update_certificate(
            certificateId=certificate_arn.split("/")[-1], newStatus="REVOKED"
        )
        c_iot.delete_certificate(certificateId=certificate_arn.split("/")[-1])
    except ClientError as e:
        logger.error(f"Unable to delete certificate {certificate_arn}, {e}")

    # delete thing
    # Check and detach principals attached to thing
    try:
        response = c_iot.list_thing_principals(thingName=thing_name)
        for principal in response["principals"]:
            c_iot.detach_thing_principal(thingName=thing_name, principal=principal)
    except ClientError as e:
        logger.error(f"Unable to list or detach principals from {thing_name}, {e}")
    try:
        c_iot.delete_thing(thingName=thing_name)
    except ClientError as e:
        logger.error(f"Error calling iot.delete_thing() for thing: {thing_name}, {e}")

def handler(event, context):
    logger.info("Received event: %s", json.dumps(event, indent=2))
    logger.info("Environment: %s", dict(os.environ))
    props = event["ResourceProperties"]
    physical_resource_id = ""
    try:
        # Check if this is a Create and we're failing Creates
        if event["RequestType"] == "Create" and event["ResourceProperties"].get(
            "FailCreate", False
        ):
            raise RuntimeError("Create failure requested, logging")
        elif event["RequestType"] == "Create":
            logger.info("Request CREATE")
            resp = create_resources(
                thing_name=props["ThingName"],
                iot_policy=props["IotPolicy"],
                iot_policy_name=props["IoTPolicyName"],
                encryption_algo=props["EncryptionAlgorithm"],
                stack_name=props["StackName"],
            )

            # set response data (PascalCase key)
            response_data = {
                "ThingArn": resp["ThingArn"],
                "CertificateArn": resp["CertificateArn"],
                "IotPolicyArn": resp["IotPolicyArn"],
                "PrivateKeySecretParameter": resp["PrivateKeySecretParameter"],
                "CertificatePemParameter": resp["CertificatePemParameter"],
                "DataAtsEndpointAddress": resp["DataAtsEndpointAddress"],
                "CredentialProviderEndpointAddress": resp[
                    "CredentialProviderEndpointAddress"
                ],
            }
            # Certificate arn is resource id since it is created with an unknown
            # value, which is needed for the delete operation
            physical_resource_id = response_data["CertificateArn"]
        elif event["RequestType"] == "Update":
            logger.info("Request UPDATE")
            response_data = {}
        elif event["RequestType"] == "Delete":
            logger.info("Request DELETE")
            resp = delete_resources(
                thing_name=props["ThingName"],
                # resource id contains the cert arn
                certificate_arn=event["PhysicalResourceId"],
                iot_policy_name=props["IoTPolicyName"],
                stack_name=props["StackName"],
            )
            response_data = {}
            physical_resource_id = event["PhysicalResourceId"]
        else:
            logger.info("Should not get here in normal cases - could be REPLACE")
        output = {"PhysicalResourceId": physical_resource_id, "Data": response_data}
        logger.info("Output from Lambda: %s", json.dumps(output, indent=2))
        return output
    except Exception as e:
        logger.exception(e)
        sys.exit(1)


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import django.utils.timezone
import django.db.models.deletion
from django.conf import settings
import djangobmf.numbering.validators

class Migration(migrations.Migration):

    dependencies = [
        ('contenttypes', '__first__'),
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(
            name='Activity',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('topic', models.CharField(max_length=100, null=True, verbose_name='Topic', blank=True)),
                ('text', models.TextField(null=True, verbose_name='Text', blank=True)),
                ('action', models.PositiveSmallIntegerField(default=1, verbose_name='Action', null=True, editable=False, choices=[(1, 'Comment'), (2, 'Created'), (3, 'Updated'), (4, 'Workflow'), (5, 'File')])),
                ('template', models.CharField(verbose_name='Template', max_length=100, null=True, editable=False)),
                ('parent_id', models.PositiveIntegerField()),
                ('modified', models.DateTimeField(auto_now=True, verbose_name='Modified')),
                ('parent_ct', models.ForeignKey(to='contenttypes.ContentType', related_name='bmf_history_parent')),
                ('user', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True)),
            ],
            options={
                'ordering': ('-modified',),
                'get_latest_by': 'modified',
                'verbose_name': 'History',
                'verbose_name_plural': 'History',
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='Configuration',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('app_label', models.CharField(verbose_name='Application', max_length=100, null=True, editable=False)),
                ('field_name', models.CharField(verbose_name='Fieldname', max_length=100, null=True, editable=False)),
                ('value', models.TextField(null=True, verbose_name='Value')),
            ],
            options={
                'default_permissions': ('change',),
                'verbose_name': 'Configuration',
                'verbose_name_plural': 'Configurations',
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='Dashboard',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('name', models.CharField(max_length=100, null=True, verbose_name='Name')),
                ('user', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True)),
            ],
            options={
                'ordering': ('name', 'id'),
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='Notification',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('obj_id', models.PositiveIntegerField()),
                ('unread', models.NullBooleanField(default=True)),
                ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
                ('changed', models.DateTimeField(auto_now=True, verbose_name='Changed')),
                ('activity', models.ForeignKey(to='djangobmf.Activity', null=True)),
                ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, editable=False, to=settings.AUTH_USER_MODEL, null=True, related_name='+')),
                ('obj_ct', models.ForeignKey(to='contenttypes.ContentType')),
                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True)),
            ],
            options={
                'ordering': ('-created',),
                'default_permissions': (),
                'get_latest_by': 'modified',
                'verbose_name': 'Notification',
                'verbose_name_plural': 'Notifications',
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='NumberCycle',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('name_template', models.CharField(max_length=64, null=True, verbose_name='Template', validators=[djangobmf.numbering.validators.template_name_validator])),
                ('counter_start', models.PositiveIntegerField(default=1, null=True)),
                ('current_period', models.DateField(default=django.utils.timezone.now, null=True)),
                ('ct', models.OneToOneField(null=True, editable=False, to='contenttypes.ContentType')),
            ],
            options={
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='Report',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('reporttype', models.CharField(max_length=20, verbose_name='Reporttype')),
                ('mimetype', models.CharField(default='pdf', verbose_name='Mimetype', max_length=20, editable=False)),
                ('options', models.TextField(help_text='Options for the renderer. Empty this field to get all available options with default values', verbose_name='Options', blank=True)),
                ('modified', models.DateTimeField(auto_now=True, verbose_name='Modified')),
                ('contenttype', models.ForeignKey(blank=True, to='contenttypes.ContentType', help_text='Connect a Report to an BMF-Model', null=True)),
            ],
            options={
                'get_latest_by': 'modified',
                'verbose_name': 'Report',
                'verbose_name_plural': 'Reports',
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='View',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('category', models.CharField(max_length=100, null=True, blank=True)),
                ('name', models.CharField(max_length=100, null=True)),
                ('url', models.CharField(max_length=80, null=True)),
                ('kwargs', models.CharField(max_length=255, null=True, blank=True)),
                ('search', models.CharField(max_length=255, null=True, blank=True)),
                ('dashboard', models.ForeignKey(to='djangobmf.Dashboard', null=True)),
            ],
            options={
                'ordering': ('name',),
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='Watch',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('watch_id', models.PositiveIntegerField()),
                ('active', models.BooleanField(default=False, verbose_name='Active', db_index=True, editable=False)),
                ('new_entry', models.BooleanField(default=False, db_index=True, verbose_name='New entry')),
                ('comment', models.BooleanField(default=False, db_index=True, verbose_name='Comment written')),
                ('file', models.BooleanField(default=False, db_index=True, verbose_name='File added')),
                ('changed', models.BooleanField(default=False, db_index=True, verbose_name='Object changed')),
                ('workflow', models.BooleanField(default=False, db_index=True, verbose_name='Workflowstate changed')),
                ('modified', models.DateTimeField(auto_now=True, verbose_name='Modified')),
                ('user', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True)),
                ('watch_ct', models.ForeignKey(to='contenttypes.ContentType')),
            ],
            options={
                'get_latest_by': 'modified',
                'ordering': ('-modified',),
                'verbose_name_plural': 'Watched activities',
                'default_permissions': (),
                'verbose_name': 'Watched activity',
            },
            bases=(models.Model,),
        ),
        migrations.AlterUniqueTogether(
            name='watch',
            unique_together=set([('user', 'watch_ct', 'watch_id')]),
        ),
    ]


from urllib.request import urlopen
from bs4 import BeautifulSoup
#import ssl
import re

# Ignore SSL certificate errors
#ctx = ssl.create_default_context()
#ctx.check_hostname = False
#ctx.verify_mode = ssl.CERT_NONE

url = input('Enter - ')
html = urlopen(url).read()
soup = BeautifulSoup(html, "html.parser")

# Retrieve all of the span tags
tags = soup('span')
total = 0
for tag in tags:
    # Look at the parts of a tag
    content = re.findall('[0-9]+', tag.contents[0])
    total += int(content[0])
print(total)


import scrapy
# to scrape pages:
# python3 scrapy crawl prices -o prices.json

class PriceSpider(scrapy.Spider):
    name = 'prices'

    # Change query variable to search for different items
    def start_requests(self):
        query = 'iphone'
        urls = [
            f'https://newyork.craigslist.org/search/sss?query={query}&sort=rel'
        ]

        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        for item in response.css('li.result-row'):
            yield {
                'title': item.css('.result-title::text')[0].get(),
                'price': item.css('.result-price::text')[0].get(),
                'link': item.css('.result-title::attr(href)').get()
            }

        next_page = response.css('a.button.next::attr(href)').get()
        if next_page is not None:
            next_page = response.urljoin(next_page)
            yield scrapy.Request(next_page, callback=self.parse)


#!/usr/bin/env python3

import asyncio
import sys

#该协程函数使用了递归。
async def stopwatch(sec):
    await asyncio.sleep(1)
    if sec <= 0:
        sys.stdout.write('0\n')
        sys.stdout.flush()
        return
    else:
        sys.stdout.write(str(sec) + ' ')
        sys.stdout.flush()
        await(stopwatch(sec -1))

async def main():
    if len(sys.argv) < 2:
        sys.exit(1)
    try:
        sec = int(sys.argv[1])
        await stopwatch(sec)
        sys.exit(0)
    except Exception:
        sys.exit(1)

if __name__ == '__main__':
    asyncio.run(main())


import json
import os
import time

with open("client/backend/test/olimpiadirobotica-2021-default-rtdb-export.json") as db_json:
    db_all = json.load(db_json)

db1 = db_all["db1"]
db1_new = {}
print(str(db1))
for data_type in db1:
    db1_new[data_type] = {"data":{}}
    for date in db1[data_type]["data"]:
        new_date = date.split("-")[2] + "-" + date.split("-")[1] + "-" + date.split("-")[0]
        print(data_type)
        print(new_date)
        db1_new[data_type]["data"][new_date] = db1[data_type]["data"][date]
        #del db1_new[data_type]["data"][date]
print(str(db1_new))

db_all["db1"] = db1_new

with open("client/backend/test/updatedb.json", "w") as update_db:
    json.dump(db_all, update_db)


import numpy as np
from numpy import (max,min,pi,exp,log)
import pandas as pd
import matplotlib.pyplot as plt
from tqdm import tqdm
gna, gk, gl = 120, 36, 0.3
vna, vk, vl = 50, -77, -54.5

def get_am(v):
    return 0.1*(v+40)/(1-exp(-0.1*(v+40)))

def get_ah(v):
    return 0.07*exp(-0.05*(v+65))

def get_an(v):
    return 0.01*(v+55)/(1-exp(-0.1*(v+55)))

def get_bm(v):
    return 4*exp(-(v+65)/18)

def get_bh(v):
    return 1/(1+exp(-0.1*(v+35)))

def get_bn(v):
    return 0.125*exp(-(v+65)/80)

def get_m(v):
    return get_am(v) / (get_am(v)+get_bm(v))

def get_h(v):
    return get_ah(v) / (get_ah(v) + get_bh(v))

def get_n(v):
    return get_an(v) / (get_an(v) + get_bn(v))

def get_i(v):
    return gna*get_m(v)**3*get_h(v)*(v-vna)+gk*get_n(v)**4*(v-vk)+gl*(v-vl)

def two_divide(left=-100,right=-20,i=0.0):
    nl = get_i(left)
    nr = get_i(right)
    nm = get_i((left+right)/2)
    while abs(nm-i)>1e-6:
        if nm-i > 0:
            right = (left+right)/2
        else:
            left = (left+right)/2
        nm = get_i((left+right)/2)
        # print(nm)
        # print(left,right)
    return (left+right)/2

def get_jac(v):
    m=get_m(v)
    h=get_h(v)
    n=get_n(v)

    df1_dv = -gna*m**3*h-gk*n**4-gl
    df1_dm = -3*m**2*gna*h*(v-vna)
    df1_dh = -gna*m**3*(v-vna)
    df1_dn = -4*gk*n**3*(v-vk)

    dam_dv = (0.1-(0.5+0.01*v)*exp(-0.1*(v+40)))/(1-exp(-0.1*(v+40)))**2
    dbm_dv = -4/18*exp(-(v+65)/18)
    dah_dv = -0.05*0.07*exp(-0.05*(v+65))
    dbh_dv = -0.1*exp(0.1*(v+35))/(1+exp(0.1*(v+35)))**2
    dan_dv = (0.1-(0.65+0.01*v)*exp(-0.1*(v+55)))/(1-exp(-0.1*(v+55)))**2
    dbn_dv = -1/640*exp(-(65+v)/80)

    df2_dv = (1-m)*dam_dv-m*dbm_dv
    df2_dm = -(get_am(v)+get_bm(v))

    df3_dv = (1-h)*dah_dv - h*dbh_dv
    df3_dh = -(get_ah(v)+get_bh(v))

    df4_dv = (1-n)*dan_dv - n*dbn_dv
    df4_dn = -(get_an(v)+get_bn(v))

    jac = np.zeros((4, 4))
    jac[0,0] = df1_dv
    jac[0,1] = df1_dm
    jac[0,2] = df1_dh
    jac[0,3] = df1_dn

    jac[1,0] = df2_dv
    jac[1,1] = df2_dm

    jac[2,0] = df3_dv
    jac[2,2] = df3_dh

    jac[3,0] = df4_dv
    jac[3,3] = df4_dn
    return jac
# df = []
# for k in np.arange(6.24,6.25,0.0001):
#     v0 = two_divide(i=k)
#     if v0 in [-40,-65,-55]:
#         v0 += 0.02
#     jac = get_jac(v0)
#     eig = np.linalg.eigvals(jac)
#     df.append(eig)
# df1 = pd.DataFrame(df)
def simu(i):
    v0 = two_divide(i=0)
    am = get_am(v0)
    bm = get_bm(v0)
    ah = get_ah(v0)
    bh = get_bh(v0)
    an = get_an(v0)
    bn = get_bn(v0)
    m = get_m(v0)
    h = get_h(v0)
    n = get_n(v0)
    delta = 0.01
    v0_l = [v0]
    for j in range(19999):
        # print(v0)
        v0 += (i-(gna*m**3*h*(v0-vna)+gk*n**4*(v0-vk)+gl*(v0-vl)))*delta
        v0_l.append(v0)
        am = get_am(v0)
        bm = get_bm(v0)
        ah = get_ah(v0)
        bh = get_bh(v0)
        an = get_an(v0)
        bn = get_bn(v0)
        m += delta*(-(am+bm)*m+am)
        h += delta*(-(ah+bh)*h+ah)
        n += delta*(-(an+bn)*n+an)

    return v0_l
i = 6.245
l = simu(i)
plt.subplot(2,2,1)
plt.plot(0.01*np.arange(20000),l)
plt.xlabel(f'I={i}mA/cm2')
plt.ylabel('V(mV)')

plt.subplot(2,2,2)
i = 6.255
l = simu(i)
plt.plot(0.01*np.arange(20000),l)
plt.xlabel(f'I={i}mA/cm2')
plt.ylabel('V(mV)')

plt.subplot(2,2,3)
i = 100
l = simu(i)
plt.plot(0.01*np.arange(20000),l)
plt.xlabel(f'I={i}mA/cm2')
plt.ylabel('V(mV)')

plt.subplot(2,2,4)
i = 158
l = simu(i)
plt.plot(0.01*np.arange(20000),l)
plt.xlabel(f'I={i}mA/cm2')
plt.ylabel('V(mV)')
def get_iv():
    v_l=[]
    for k in tqdm(np.arange(0,180,0.5)):
        l = simu(k)
        v_l.append(max(l[-3000:])-min(l[-3000:]))
    return v_l

# v_l = get_iv()


import gridfs,pymongo
from bson import ObjectId
from datetime import datetime, timedelta
from werkzeug.exceptions import BadRequest
from src.definitions.status_codes import ResponseStatus, RESPONSE_MSG_MAP
from src.definitions.status_codes import key_mapper, default_data

class DbFunctions:
    def __init__(self):
        pass

    @classmethod
    def update_one(cls, database, collection_name, query={}, fields={}, upsert=False,email=""):
        fields.update({"updated_by":email,"updated_at":datetime.now()})
        database.get_collection(collection_name).update_one(query, {"$set": fields}, upsert=upsert)

    @classmethod
    def update_many(cls, database, collection_name, query={}, fields={}):
        database.get_collection(collection_name).update_many(query, {"$set": fields})

    @classmethod
    def insert_one(cls, database, collection_name, data,email=""):
        data.update({"createdby": email, "created_at": datetime.now()})
        object_id = (
            database.get_collection(collection_name).insert_one(data).inserted_id
        )
        return object_id

    @classmethod
    def insert_many(cls, database, collection_name, data):
        database.get_collection(collection_name).insert_many(data)

    @classmethod
    def delete_one(cls, database, collection_name, data, query={}):
        database.get_collection(collection_name).delete_one(data, query)

    @classmethod
    def find_all(cls, database, collection_name, data, query={}):
        data = {i: 1 for i in data}
        read_data = list(
            database.get_collection(collection_name).find(
                query, data, sort=[("_id", pymongo.DESCENDING)]
            )
        )
        for item in read_data:
            if isinstance(item, dict):
                item["_id"] = str(item["_id"])
        return read_data

    @classmethod
    def find_one(cls, database, collection_name, data, query={}):
        data = {i: 1 for i in data}
        data.update({"_id": 0})
        read_data = database.get_collection(collection_name).find_one(query, data) or {}
        return read_data

    @classmethod
    def update_inc(cls, database, collection_name, query={}, fields={}, upsert=False):
        database.get_collection(collection_name).update_one(
            query, {"$inc": fields}, upsert=upsert
        )

    @classmethod
    def push(cls, database, collection_name, query={}, fields={}, ip="", upsert=False):
        try:
            fields["history"].update({"ip_address": ip})
        except:
            pass
        database.get_collection(collection_name).update_one(
            query, {"$push": fields}, upsert=upsert
        )

    @classmethod
    def aggregate(cls, database, collection_name, pip):
        piplines = ["$match", "$unwind", "$match", "$project"]
        project = pip[3]
        data = {"_id": 0}
        for field in project:
            if "." in field:
                data.update({field.split(".")[-1]: field})
            else:
                data.update({field[1:]: 1})
        pip[3] = data
        pipline = [{val: pip[indx]} for indx, val in enumerate(piplines)]
        return list(database.get_collection(collection_name).aggregate(pipline))

    @classmethod
    def distinct(cls, database, collection_name, fields, query={}):
        return database.get_collection(collection_name).distinct(fields, query)

    @classmethod
    def latest_record(cls, database, collection_name, query={}):
        try:
            data = list(
                database.get_collection(collection_name)
                .find(query)
                .sort("_id", -1)
                .limit(1)
            )[0]
        except IndexError:
            data = {
                "email": query["email"],
                "attempt": 0,
                "valid_upto": datetime(1900, 1, 1),
                "status": False,
                "logged_in_once": False,
            }
            data["_id"] = cls.insert_one(
                database=database, collection_name=collection_name, data=data
            )
        return data

    @staticmethod
    def savefile(database, batch_file, filename):
        gridfs.GridFS(database, collection="CMS_FILE").put(
            batch_file, filename=filename
        )

    @classmethod
    def update_many_(
        cls, database, collection_name, queries=[], fields=[], upsert=False
    ):
        for num, query in enumerate(queries):
            database.get_collection(collection_name).update_one(
                query, {"$set": fields[num]}, upsert=upsert
            )

class RequestResponse:
	__slots__ = ["coll_name", "query", "db_name", "operation", "data", "fields", "pipeline", "response", "email",
	             "password", "res", "token", "logger_id", "ip_address","cifrado"]

	def __init__(self, request):
		self.response = ResponseStatus.Sucess
		self.res = default_data.NULL
		self.logger_id, self.email = default_data.STR, ""
		try:
			self.ip_address = request.remote_addr
		except AttributeError:
			pass
		self.attr_setter(request)
		try:
			if not self.email:
				self.email = self.query.get(key_mapper.EMAIL, "")
		except AttributeError:
			pass
		self.email = self.email.lower()

	def attr_setter(self, request):
		try:
			for key, val in request.args.items():
				setattr(self, key, val)
		except:
			pass
		try:
			try:
				request_json = request.get_json()
			except:
				request_json = request
			for key, val in request_json.items():
				if key == "query":
					if isinstance(val, list):
						for num, rec in enumerate(val):
							if rec.get("_id") is not None:
								val[num]["_id"] = ObjectId(rec.get("_id"))
					else:
						if val.get("_id") is not None:
							val["_id"] = ObjectId(val.get("_id"))
				setattr(self, key, val)
		except (BadRequest,AttributeError,TypeError):
			pass

	def to_dict(self):

		return {"msg": RESPONSE_MSG_MAP.get(self.response.name),
		        "res": self.res, "response_code": self.response.value,
		        "logger_id": self.logger_id}

class AttrDict(dict):

	def __getattr__(self, attr):
		return self.get(attr, "")

	def __setattr__(self, attr, value):
		self[attr] = value

def type_convertor(a):
	return str(a)

def time_convertor(time_obj, weeks=default_data.ZERO, minutes=default_data.ZERO, days=default_data.ZERO,
                   years=default_data.ZERO, hours=default_data.ZERO, months=default_data.ZERO,
                   seconds=default_data.ZERO):
	return time_obj + timedelta(
		minutes=minutes,
		days=days,
		seconds=seconds,
		hours=hours,
		weeks=weeks)


'''This project is a inventory checking system for a shoe company
it takes in information from a txt file, adds each line to a shoe object
the list of shoe objects is then used in various ways as chosen by the user'''
#========Importing Modules Section=======
no_table = False
try:
    from tabulate import tabulate
except:
    no_table = True
#========The beginning of the class==========
class Shoe:

    def __init__(self, country, code, product, cost, quantity):
        self.country = country
        self.code = code
        self.product = product
        self.cost = cost
        self.quantity = quantity
    def get_cost(self):
        #this method returns the int of the shoes price
        return(int(self.cost))

    def get_quantity(self):
        #this method returns an int of the shoes quantity
        return(int(self.quantity))

    def __str__(self):
        #prints a string representing all the data about the shoe
        print(f"For {self.product}: {self.code} in {self.country}, there are {self.quantity} units. The unit price is {self.cost}")

#=============Shoe list===========
shoe_list = []
#==========Functions outside the class==============
def read_shoes_data():
    #this function takes the information from the inventory file then appends it to the shoe list
    try:
        inventory= open("inventory.txt","r+",encoding='utf8')
        #first the try-except avoids errors if the inventory file is not found
    except IOError:
        print("Inventory file not found, please ensure it is in the correct folder.")
        exit()
        #if error is found program is exited to allow the user to fix the location issue
    line_counter = 0
    #with the file opened the information can be read after the first line is skipped
    for line in inventory:
        if line != "\n" and line_counter > 0:
            current_line = line
            current_line = current_line.replace("\n","")
            current_line = current_line.split(",")
            #The split line is then converted into a shoe class object
            new_shoe = Shoe(current_line[0],current_line[1],current_line[2],current_line[3],current_line[4])
            shoe_list.append(new_shoe)
            #which is then appended to the shoe list
        line_counter += 1
    inventory.close()

def capture_shoes():
    #this function allows the user to input a shoe object with various methods to avoid errors
    while True:
        new_shoe_country = input("Please enter the location of the stock of shoes: ")
        #this ensures the entry is a reasonable size for a location and that there are no commas
        if len(new_shoe_country) < 35:
            if ',' not in new_shoe_country:
                break
            else:
                print("Please do not include any commas in the location")
        else:
            print("Please enter a location less than 35 characters long")
    while True:
        new_shoe_code = input("Please enter the shoe's 5-digit product code [SKU*****]: ")
        try:
            #this try-except block casts the code to int
            new_shoe_code = int(new_shoe_code)
        except:
            pass
        #this logic ensures the code is a number and the correct length
        if isinstance(new_shoe_code,int) and len(str(new_shoe_code)) == 5:
            new_shoe_code = str(new_shoe_code)
            new_shoe_code_string = "SKU"+new_shoe_code
            break
        else:
            print("Please enter the 5 digit shoe code, numbers only")
    while True:
        #the only check needed here is to ensure the brand name is a realistic length and for ','
        new_shoe_product = input("Please enter the brand of the shoe: ")
        if len(new_shoe_product) < 35:
            if ',' not in new_shoe_product:
                break
            else:
                print("Please do not include any commas in the brand name")
        else:
            print("Please enter a brand name less than 35 characters long")
    while True:
        #this checks that the cost entered is a number and prints an appropriate error message
        try:
            new_shoe_cost = int(input("Please enter the cost of the shoe: "))
            break
        except:
            print("Please only enter a number for the shoe's cost")
    while True:
        try:
            new_shoe_quantity = int(input("Please enter the quantity of the shoe: "))
            break
        except:
            print("Please only enter a number for the quantity of the shoe")
    #with all the errors checked and avoided, the shoe object is created
    new_shoe = Shoe(new_shoe_country,new_shoe_code_string,new_shoe_product,new_shoe_cost,new_shoe_quantity)
    #this new shoe object is then added to the shoe list
    shoe_list.append(new_shoe)
    #as the shoe being on the list means it could be added to the file, checks for commas are used

def view_all():
    #this function uses the __str__ function for each instance of shoe in the shoe list
    #if the user doesnt have the tabulate module installed then the no_table version is run
    if no_table == True:
        for shoe in shoe_list:
            Shoe.__str__(shoe)
    #otherwise a table is built for more easy viewing
    else:
        table_list = []
        for shoe in shoe_list:
            table_list.append([shoe.country,shoe.code,shoe.product,shoe.cost,shoe.quantity])
            #this loop creates a nested list with all the shoe data
        head = ["Country","Code","Product","Cost","Quantity"]
        #the header plus the new list are used to build the table
        print(tabulate(table_list,headers=head,tablefmt="grid"))

def re_stock():
    #first the shoe_list is sorted by quantity and assigned to a new list
    low_stock_list = sorted(shoe_list, key=Shoe.get_quantity)
    low_stock_shoe = low_stock_list[0]
    #the first entry to this list is the shoe object that may be changed
    print(f"The shoe with the lowest stock is {low_stock_shoe.product} in {low_stock_shoe.country}. It has {low_stock_shoe.quantity} remaining\n")
    #the user can choose to add a new number for the quantity of this shoe
    while True:
        user_choice = input("Would you like to add additional stock to this shoe? y/n: ")
        print(user_choice)
        if user_choice.lower() == "y" or user_choice.lower() == "n":
            break
        else:
            print("Only enter 'y' or 'n' to make your selection")
    #this avoids errors from unexpected user behaviour
    if user_choice.lower() == 'y':
    #if the user selects y the new stock will be written to the file after error checking
        while True:
            new_quantity = input("Please enter the new quantity you wish to add to the current stock: ")
            try:
                new_quantity = int(new_quantity)
                break
            except:
                print("Please only enter a numerical value")
        new_quantity = new_quantity + int(low_stock_shoe.quantity)
        #this adds the current quantity and the desired addition together for a new total
        for shoe in shoe_list:
            #the shoe list is searched through for the matching shoe
            if Shoe.get_quantity(shoe) == int(low_stock_shoe.quantity) and shoe.product == low_stock_shoe.product:
                x = shoe_list.index(shoe)
                print(shoe_list[x].quantity)
                shoe_list[x].quantity = new_quantity
                #the value is updated in the main list
                print(shoe_list[x].quantity)
        #with the list updated now the file must be rewritten in format with the updated stock
        inventory = open('inventory.txt','w+',encoding='utf8')
        #the first line is written with the labels as before
        inventory.write(("Country,Code,Product,Cost,Quantity")+("\n"))
        #then the rest of the file is written in format with , as seperators
        for shoe in shoe_list:
            inventory.write((f"{shoe.country},{shoe.code},{shoe.product},{shoe.cost},{shoe.quantity}") + ("\n"))
        inventory.close()

def search_shoe():
    #this function takes the SKU code from the user and prints the information for the shoe
    while True:
        search_code = input("Please enter the 5 digit code for the desired shoe: ")
        try:
            #block prevents errors from unexpected inputs
            search_code = int(search_code)
            #cast to int to check that the input only contains numbers
            search_code = str(search_code)
            #then cast back to ensure the length is correct
            if len(search_code) == 5:
                break
                #only if the entry is both numbers and 5 digits long will the loop exit
            else:
                print("Code is not 5-digits long, please enter a correct SKU code")
        except:
            print("Please only enter a 5-digit number for the shoe code")
    search = "SKU"+search_code
    #code is concatenated with SKU
    print("")
    #improves ease of reading the console with some seperation
    found = False
    for shoe in shoe_list:
        if shoe.code == search:
            found = True
            Shoe.__str__(shoe)

            #all shoes with matching SKU are displayed to the user
    if found == False:
        #if a match is never found through the whole list this message is printed
        print(f"No matching shoes with code {search} were found.")
    print("")
def value_per_item():
    #this function prints to total value for each shoe in the shoe list
    value_table = []
    for shoe in shoe_list:
        total = Shoe.get_cost(shoe) * Shoe.get_quantity(shoe)
        #this checks if the user has the tabulate module, if not the information is printed
        if no_table == True:
            print(f"The total value for {shoe.product} is {total} ({shoe.cost} X {shoe.quantity})\n")
        else:
            value_table.append([shoe.product,shoe.cost,shoe.quantity,total])
    #if the user does have the module the value per item is displayed in a table
    if no_table == False:
        head = ["Product","Cost","Quantity","Total Value"]
        print(tabulate(value_table,headers=head,tablefmt="grid"))

        #the result is then printed in a more readable way for each shoe

def highest_qty():
    #this function creates a sorted list to find the highest quantity shoe, then displays the info
    highest_list = sorted(shoe_list, key=Shoe.get_quantity,reverse=True)
    #the list is reversed so the first value is the greatest
    highest_shoe = highest_list[0]
    print(f"\nThe shoe with the highest stock remaining is the {highest_shoe.product} in {highest_shoe.country}. It has {highest_shoe.quantity} units remaining")
    print(f"As such the {highest_shoe.product} is for sale!\n")
#==========Main Menu=============
read_shoes_data()
#the shoe list is created before the user is presented with a menu as it is needed in all functions
menu_choice = 0
#the menu loops until -1 is entered which exits the loop and ends the program
while menu_choice != -1:
    if no_table == True:
        print('''Please select the function you would like to perform
        1: Add Shoe - Creates a new shoe in the system
        2: View All - Displays information associated with each shoe
        3: Re-stock - Find the lowest stock shoe and add new units if desired
        4: Search Shoes - Use SKU code to find shoe in the system
        5: Value per item - Calculate the total cost of the stock for each shoe
        6: Highest Quantity - Find the highest quantity shoe in the system
        Or enter -1 to exit the program''')
    else:
        menu_table = [["1:", "Add Shoe", "Creates a new shoe in the system"],
        ["2:" , "View All" , "Displays information associated with each shoe"],
        ["3:" , "Re-stock" , "Find the lowest stock shoe and add new units if desired"],
        ["4:" , "Search Shoes" , "Use SKU code to find shoe in the system"],
        ["5:" , "Value per item" , "Calculate the total cost of the stock for each shoe"],
        ["6:", "Highest Quantity" , "Find the highest quantity shoe in the system"],
        ["-1:" , "Exit" , "Quits the program"]]
        menu_header = ["","Option","Description"]
        print("Please select the function you would like to perform:")
        print(tabulate(menu_table,headers=menu_header,tablefmt="grid"))
    while True:
        menu_choice = input("")
        try:
            menu_choice = int(menu_choice)
            break
        except:
            print("Please enter a number from the menu")
    if menu_choice == 1:
        #calls the capture shoes function which adds a shoe object to the list
        capture_shoes()
    elif menu_choice == 2:
        #calls the view all function
        view_all()
    elif menu_choice == 3:
        #calls the re_stock function
        re_stock()
    elif menu_choice == 4:
        #calls the search shoes function
        search_shoe()
    elif menu_choice == 5:
        #calls the value per item function
        value_per_item()
    elif menu_choice == 6:
        #calls the highest quantity function
        highest_qty()
print("Thank you for using the system")


"""
This file contains methods to evaluate the trained model
"""
from read_args import read_command

import numpy as np
import tensorflow as tf
from sklearn.metrics import confusion_matrix, recall_score, precision_score, accuracy_score

import model, read_input

tf.app.flags.DEFINE_integer('e', 1, 'eval measure to use')
tf.app.flags.DEFINE_string('c', '', 'directory to store checkpoints')
tf.app.flags.DEFINE_boolean('no-train', True, 'whether to train the inside weights')

def eval_measure_1(sess, logits, labels, total_examples):
	true_count = 0

	top_k_op = tf.nn.in_top_k(logits, labels, 1)

	iter_n = 0
	while True:
		try:
			print(iter_n)
			trues = sess.run([top_k_op])
			true_count += np.sum(trues)
			iter_n += 1
		except tf.errors.OutOfRangeError:
			break

	accuracy = true_count / total_examples
	print(accuracy)

def eval_measure_2(sess, logits, labels):

	iter_n = 0
	predictions_all = np.array([])
	labels_all = np.array([])
	while True:
		try:
			print(iter_n)
			predictions, labels_r = sess.run([tf.argmax(logits, axis=1), labels])

			predictions_all = np.append(predictions_all, predictions)
			labels_all = np.append(labels_all, labels_r)

			iter_n += 1
		except tf.errors.OutOfRangeError:
			break

	print(labels_all.shape, predictions_all.shape)
	conf_matrix = confusion_matrix(labels_all, predictions_all)
	print(conf_matrix)

	precision_macro = precision_score(labels_all, predictions_all, average='macro')
	precision_weighted = precision_score(labels_all, predictions_all, average='weighted')
	print(precision_macro, precision_weighted)

	recall_macro = recall_score(labels_all, predictions_all, average='macro')
	recall_weighted = recall_score(labels_all, predictions_all, average='weighted')
	print(recall_macro, recall_weighted)

	accuracy = accuracy_score(labels_all, predictions_all)
	print(accuracy)

def eval_once(saver, eval_measure, logits, labels, args, checkpoint_dir):

	with tf.Session() as sess:
		ckpt = tf.train.get_checkpoint_state(checkpoint_dir)
		if ckpt and ckpt.model_checkpoint_path:
			saver.restore(sess, ckpt.model_checkpoint_path)
		else:
			print('No checkpoint file found')
			return

		eval_measure(sess, logits, labels, *args)

def eval(args):
	with tf.Graph().as_default():

		images, labels = args['inputs'](train=False)

		# logits = args['inference'](images, args['n_classes'])
		logits = model.inference(images, args['n_classes'], args['relu'],
								 args['lrn'], args['pool_type'], args['trainable'])

		# top_k_op = tf.nn.in_top_k(logits, labels, 1)

		saver = tf.train.Saver()

		eval_once(saver, args['eval_measure'], logits, labels,
				  args['eval_measure_args'], args['checkpoint_dir'])

def mnist_eval(args):

	args['inputs'] = read_input.mnist_input
	args['n_classes'] = 10

	if args['eval_measure_id'] == 1:
		args['eval_measure'] = eval_measure_1
		args['eval_measure_args'] = (10000,) # examples per epoch
	else:
		args['eval_measure'] = eval_measure_2
		args['eval_measure_args'] = tuple()

	eval(args)

def caltech_101_eval(args):
	args['inputs'] = read_input.caltech_101_input
	args['n_classes'] = 102

	if args['eval_measure_id'] == 1:
		args['eval_measure'] = eval_measure_1
		args['eval_measure_args'] = (30*102,) # examples per epoch
	else:
		args['eval_measure'] = eval_measure_2
		args['eval_measure_args'] = tuple()

	eval(args)

if __name__ == '__main__':
	args = read_command()

	if args['dataset'] == 'mnist':
		mnist_eval(args)
	elif args['dataset'] == 'caltech_101':
		caltech_101_eval(args)


settings = {
    # comparison settings
    'intr_area_x': 100,
    'intr_area_y': 100,
    'step_x': 0.25,  # 0.5
    'step_y': 0.25,  # 0.5
    'scan_area_ratio_x': 0.5,
    'scan_area_ratio_y': 0.5,
    'criterion': 0.98,  # (intr / step)^2
    'ite_xs' : None,
    'ite_ys' : None,

    # image attribute
    'delta_x': None,
    'delta_y': None,

    # input file info
    'total_images1': None,
    'total_images2': None,
    'rotate1': '',  # 'cw, ccw or empty
    'rotate2': '',

    # output file info
    'output_filename': 'result',  # 拡張子はつけない

    # color
    'cv_pink': (203, 192, 255),
    'pink': (255, 192, 203),
    'cv_red' : (0, 0, 255),
    'red': (255, 0, 0),
    'deep_green': (0, 128, 0),
    'cv_green': (0, 128, 0),
    'blue' : (0, 0, 255),

    # others
    'display': 'overlap',  # comparison, overlap, raw
    'inverse_comparison' : 'left',  # 'left, right or empty'
    'check_origin' : True,
    'check_similarity' : False,
    'debug': False
}

settings['border_x'] = int(settings['intr_area_x'] * settings['scan_area_ratio_x'])
settings['border_y'] = int(settings['intr_area_y'] * settings['scan_area_ratio_y'])
settings['cv_blue'] = tuple(reversed(settings['blue']))


# Programmas nosaukums: Koha zvaigzne
# 3. uzdevums (1MPR03_Vladislavs_Babaņins)
# Uzdevuma formulējums: Sastādīt programmu, kas izveido Koha zvaigzni, izmantojot rekursiju.
# Programmas autors: Vladislavs Babaņins
# Versija 1.0

import math
import tkinter

def zimet_koha_zvaigzni(x1, y1, x5, y5):
    # Rekursīvi uzzīme daļu no Koha zvaigznes
    # x1 - nogriežņa sākumpunkta x koordināta
    # y1 - nogriežņa sākumpunkta y koordināta
    # x5 - nogriežņa galapunkta x koordināta
    # y5 - nogriežņa galapunkta y koordināta
    if math.sqrt((x1 - x5)**2 + (y1 - y5)**2) < 4: # kad nogriežņa garums paliek mazāks par 4, tad pārtraucam rekursiju
        kanva.create_line(x1, y1, x5, y5)
    else:
        dx = x5 - x1
        dy = y5 - y1
        x2 = x1 + dx // 3
        y2 = y1 + dy // 3
        x3 = (x1 + x5) // 2 + math.sqrt(3) * (y1 - y5) // 6
        y3 = (y1 + y5) // 2 + math.sqrt(3) * (x5 - x1) // 6
        x4 = x1 + 2 * dx // 3
        y4 = y1 + 2 * dy // 3
        zimet_koha_zvaigzni(x1, y1, x2, y2)
        zimet_koha_zvaigzni(x2, y2, x3, y3)
        zimet_koha_zvaigzni(x3, y3, x4, y4)
        zimet_koha_zvaigzni(x4, y4, x5, y5)

# ---------------------------------------------------------
# Galvenā programmas daļa
# ---------------------------------------------------------

logs = tkinter.Tk()
kanva = tkinter.Canvas(logs, bg="white", height=1000, width=1000)
kanva.pack()

zimet_koha_zvaigzni(250, 562, 792, 562)
zimet_koha_zvaigzni(792, 562, 521, 21)
zimet_koha_zvaigzni(521, 21, 250, 562)

logs.mainloop()

1. Kurss 2. semestris Programmešana un datori II/1MPR03/3uzd_MPR03.py


# 编写一个函数来查找字符串数组中的最长公共前缀。
#
#  如果不存在公共前缀，返回空字符串 ""。
#
#
#
#  示例 1：
#
#
# 输入：strs = ["flower","flow","flight"]
# 输出："fl"
#
#
#  示例 2：
#
#
# 输入：strs = ["dog","racecar","car"]
# 输出：""
# 解释：输入不存在公共前缀。
#
#
#
#  提示：
#
#
#  0 <= strs.length <= 200
#  0 <= strs[i].length <= 200
#  strs[i] 仅由小写英文字母组成
#
#  Related Topics 字符串
#  👍 1448 👎 0
class Solution(object):

    def longestCommonPrefix(strs):
        """
        :type strs: List[str]
        :rtype: str
        """
        if not strs: return ""
        str0 = min(strs)
        str1 = max(strs)
        for i in range(len(str0)):
            if str0[i] != str1[i]:
                return str0[:i]
        return str0

    result = longestCommonPrefix(["flower", "flow", "flight"])
    print(result)
    l = ["a18", "c5", "b21"]
    print(max(l))