from datetime import datetime, timezone, timedelta
from django.db.models import Q, Sum
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from django.conf import settings
from django.template.loader import render_to_string
from elasticsearch.helpers import bulk
from api.indexes import ES_PAGE_NAME
from api.esconnection import ES_CLIENT
from api.models import Country, Appeal, Event, FieldReport, ActionsTaken
from api.logger import logger
from notifications.models import RecordType, SubscriptionType, Subscription, SurgeAlert
from notifications.hello import get_hello
from notifications.notification import send_notification
from deployments.models import PersonnelDeployment, ERU
from main.frontend import frontend_url
import html

time_interval  = timedelta(minutes = 5)
time_interva2  = timedelta(   days = 1) # to check: the change was not between time_interval and time_interva2, so that the user don't receive email more frequent than a day.
time_interva7  = timedelta(   days = 7) # for digest mode
basetime       = int(20314) # weekday - hour - min for digest timing (5 minutes once a week)
daily_retro    = int(654) # hour - min for daily retropective email timing (5 minutes a day) | Should not contain a leading 0!
max_length     = 280 # after this length (at the first space) we cut the sent content
events_sent_to = {} # to document sent events before re-sending them via specific following
template_types = {
    99: 'design/generic_notification.html',
    RecordType.FIELD_REPORT: 'design/field_report.html',
    RecordType.APPEAL: 'design/new_operation.html',
    98: 'design/operation_update.html', # TODO: Either Operation Update needs a number or it should be constructed from other types (ask someone)
    RecordType.WEEKLY_DIGEST: 'design/weekly_digest.html',
}

class Command(BaseCommand):
    help = 'Index and send notifications about new/changed records'

    # Digest mode duration is 5 minutes once a week
    def is_digest_mode(self):
        today = datetime.utcnow().replace(tzinfo=timezone.utc)
        weekdayhourmin = int(today.strftime('%w%H%M'))
        return basetime <= weekdayhourmin and weekdayhourmin < basetime + 5

    def is_retro_mode(self):
        today = datetime.utcnow().replace(tzinfo=timezone.utc)
        hourmin = int(today.strftime('%H%M'))
        return daily_retro <= hourmin and hourmin < daily_retro + 5

    def get_time_threshold(self):
        return datetime.utcnow().replace(tzinfo=timezone.utc) - time_interval

    def get_time_threshold2(self):
        return datetime.utcnow().replace(tzinfo=timezone.utc) - time_interva2

    def get_time_threshold_digest(self):
        return datetime.utcnow().replace(tzinfo=timezone.utc) - time_interva7

    def gather_country_and_region(self, records):
        # Appeals only, since these have a single country/region
        countries = []
        regions = []
        for record in records:
            if record.country is not None:
                countries.append('c%s' % record.country.id)
                if record.country.region is not None:
                    regions.append('r%s' % record.country.region.id)
        countries = list(set(countries))
        regions = list(set(regions))
        return countries, regions

    def gather_countries_and_regions(self, records):
        # Applies to emergencies and field reports, which have a
        # many-to-many relationship to countries and regions
        countries = []
        for record in records:
            if record.countries is not None:
                countries += [country.id for country in record.countries.all()]
        countries = list(set(countries))
        qs = Country.objects.filter(pk__in=countries)
        regions = ['r%s' % country.region.id for country in qs if country.region is not None]
        countries = ['c%s' % id for id in countries]
        return countries, regions

    def gather_subscribers(self, records, rtype, stype):
        # Correction for the new notification types:
        if  rtype == RecordType.EVENT or rtype == RecordType.FIELD_REPORT:
            rtype_of_subscr = RecordType.NEW_EMERGENCIES
            stype = SubscriptionType.NEW
        elif rtype == RecordType.APPEAL:
            rtype_of_subscr = RecordType.NEW_OPERATIONS
            stype = SubscriptionType.NEW
        else:
            rtype_of_subscr = rtype

        # Gather the email addresses of users who should be notified
        if self.is_digest_mode():
            subscribers = User.objects.filter(subscription__rtype=RecordType.WEEKLY_DIGEST, \
                                          is_active=True).values('email')
            # In digest mode we do not care about other circumstances, just get every subscriber's email.
            emails = [subscriber['email'] for subscriber in subscribers]
            return emails
        else:
        # Start with any users subscribed directly to this record type.
            subscribers = User.objects.filter(subscription__rtype=rtype_of_subscr, \
                                          subscription__stype=stype, is_active=True).values('email')

        # For FOLLOWED_EVENTs and DEPLOYMENTs we do not collect other generic (d*, country, region) subscriptions, just one. This part is not called.
        if rtype_of_subscr != RecordType.FOLLOWED_EVENT and \
           rtype_of_subscr != RecordType.SURGE_ALERT and \
           rtype_of_subscr != RecordType.SURGE_DEPLOYMENT_MESSAGES:
            dtypes = list(set(['d%s' % record.dtype.id for record in records if record.dtype is not None]))

            if (rtype_of_subscr == RecordType.NEW_OPERATIONS):
                countries, regions = self.gather_country_and_region(records)
            else:
                countries, regions = self.gather_countries_and_regions(records)

            lookups = dtypes + countries + regions
            if len(lookups):
                subscribers = (subscribers | User.objects.filter(subscription__lookup_id__in=lookups, is_active=True).values('email')).distinct()
        emails = [subscriber['email'] for subscriber in subscribers]
        return emails

    def get_template(self, rtype=99):
        #older: return 'email/generic_notification.html'
        #old: return 'design/generic_notification.html'
        return template_types[rtype]

    # Get the front-end url of the resource
    def get_resource_uri (self, record, rtype):
        # Determine the front-end URL
        resource_uri = frontend_url
        if rtype == RecordType.SURGE_ALERT or rtype == RecordType.FIELD_REPORT: # Pointing to event instead of field report %s/%s/%s - Munu asked - ¤
            belonging_event = record.event.id if record.event is not None else 999 # Very rare
            resource_uri = '%s/emergencies/%s#overview' % (frontend_url, belonging_event)
        elif rtype == RecordType.SURGE_DEPLOYMENT_MESSAGES:
            resource_uri = '%s/%s' % (frontend_url, 'deployments')  # can be further sophisticated
        elif rtype == RecordType.APPEAL and (
                record.event is not None and not record.needs_confirmation):
            # Appeals with confirmed emergencies link to that emergency
            resource_uri = '%s/emergencies/%s#overview' % (frontend_url, record.event.id)
        elif rtype != RecordType.APPEAL:
            # One-by-one followed or globally subscribed emergencies
            resource_uri = '%s/%s/%s' % (
                frontend_url,
                'emergencies' if rtype == RecordType.EVENT or rtype == RecordType.FOLLOWED_EVENT else 'reports', # this else never occurs, see ¤
                record.id
            )
        return resource_uri

    def get_admin_uri (self, record, rtype):
        admin_page = {
            RecordType.FIELD_REPORT: 'api/fieldreport',
            RecordType.APPEAL: 'api/appeal',
            RecordType.EVENT: 'api/event',
            RecordType.FOLLOWED_EVENT: 'api/event',
            RecordType.SURGE_DEPLOYMENT_MESSAGES: 'deployments/personneldeployment',
            RecordType.SURGE_ALERT: 'notifications/surgealert',
        }[rtype]
        return 'https://%s/admin/%s/%s/change' % (
            settings.BASE_URL,
            admin_page,
            record.id,
        )

    def get_record_title(self, record, rtype):
        if rtype == RecordType.FIELD_REPORT:
            sendMe = record.summary
            if record.countries.all():
                country = record.countries.all()[0].name
                if country not in sendMe:
                    sendMe = sendMe + ' (' + country + ')'
            return sendMe
        elif rtype == RecordType.SURGE_ALERT:
            return record.operation + ' (' + record.atype.name + ', ' + record.category.name.lower() +')'
        elif rtype == RecordType.SURGE_DEPLOYMENT_MESSAGES:
            return '%s, %s' % (record.country_deployed_to, record.region_deployed_to)
        else:
            return record.name

    def get_record_content(self, record, rtype):
        if rtype == RecordType.FIELD_REPORT:
            sendMe = record.description
        elif rtype == RecordType.APPEAL:
            sendMe = record.sector
            if record.code:
                sendMe += ', ' + record.code
        elif rtype == RecordType.EVENT or rtype == RecordType.FOLLOWED_EVENT:
            sendMe = record.summary
        elif rtype == RecordType.SURGE_ALERT:
            sendMe = record.message
        elif rtype == RecordType.SURGE_DEPLOYMENT_MESSAGES:
            sendMe = record.comments
        else:
            sendMe = '?'
        return html.unescape(sendMe) # For contents we allow HTML markup. = autoescape off in generic_notification.html template.

    def get_record_display(self, rtype, count):
        display = {
            RecordType.FIELD_REPORT: 'field report',
            RecordType.APPEAL: 'operation',
            RecordType.EVENT: 'event',
            RecordType.FOLLOWED_EVENT: 'event',
            RecordType.SURGE_DEPLOYMENT_MESSAGES: 'surge deployment',
            RecordType.SURGE_ALERT: 'surge alert',
        }[rtype]
        if (count > 1):
            display += 's'
        return display

    def get_weekly_digest_data(self, field):
        today = datetime.utcnow().replace(tzinfo=timezone.utc)
        if field == 'dref':
            return Appeal.objects.filter(end_date__gt=today, atype=0).count()
        elif field == 'ea':
            return Appeal.objects.filter(end_date__gt=today, atype=1).count()
        elif field == 'fund':
            amount_req = (
                Appeal.objects
                    .filter(Q(end_date__gt=today, atype=1) | Q(end_date__gt=today, atype=2))
                    .aggregate(Sum('amount_requested'))['amount_requested__sum'] or 0
            )
            amount_fund = (
                Appeal.objects
                    .filter(Q(end_date__gt=today, atype=1) | Q(end_date__gt=today, atype=2))
                    .aggregate(Sum('amount_funded'))['amount_funded__sum'] or 0
            )
            percent = round(amount_fund / amount_req, 3) * 100
            return percent
        elif field == 'budget':
            amount = Appeal.objects.filter(end_date__gt=today).aggregate(Sum('amount_requested'))['amount_requested__sum'] or 0
            rounded_amount = round(amount / 1000000, 2)
            return rounded_amount
        elif field == 'pop':
            people = Appeal.objects.filter(end_date__gt=today).aggregate(Sum('num_beneficiaries'))['num_beneficiaries__sum'] or 0
            rounded_people = round(people / 1000000, 2)
            return rounded_people

    def get_weekly_digest_latest_ops(self):
        dig_time = self.get_time_threshold_digest()
        ops = Appeal.objects.filter(created_at__gte=dig_time).order_by('-created_at')
        ret_ops = []
        for op in ops:
            op_to_add = {
                'op_event_id': op.event_id,
                'op_country': Country.objects.values_list('name', flat=True).get(id=op.country_id) if op.country_id else '',
                'op_name': op.name,
                'op_created_at': op.created_at,
                'op_funding': op.amount_requested,
            }
            ret_ops.append(op_to_add)
        return ret_ops

    def get_weekly_digest_highlights(self):
        dig_time = self.get_time_threshold_digest()
        events = Event.objects.filter(is_featured=True, updated_at__gte=dig_time).order_by('-updated_at')
        ret_highlights = []
        for ev in events:
            amount_requested = Appeal.objects.filter(event_id=ev.id).aggregate(Sum('amount_requested'))['amount_requested__sum'] or 0
            amount_funded = Appeal.objects.filter(event_id=ev.id).aggregate(Sum('amount_funded'))['amount_funded__sum'] or 0
            data_to_add = {
                'hl_id': ev.id,
                'hl_name': ev.name,
                'hl_last_update': ev.updated_at,
                'hl_people': Appeal.objects.filter(event_id=ev.id).aggregate(Sum('num_beneficiaries'))['num_beneficiaries__sum'] or 0,
                'hl_funding': amount_requested,
                'hl_deployed_eru': ERU.objects.filter(event_id=ev.id).aggregate(Sum('units'))['units__sum'] or 0,
                'hl_deployed_sp': PersonnelDeployment.objects.filter(event_deployed_to_id=ev.id).count(),
                'hl_coverage': round(amount_funded / amount_requested, 1) if amount_requested != 0 else 0,
            }
            ret_highlights.append(data_to_add)
        return ret_highlights

    def get_actions_taken(self, frid):
        ret_actions_taken = {
            'NTLS': [],
            'PNS': [],
            'FDRN': [],
        }
        actions_taken = ActionsTaken.objects.filter(field_report_id=frid)
        for at in actions_taken:
            action_to_add = {
                'action_summary': at.summary,
                'actions': [],
            }
            if at.actions.all():
                for act in at.actions.all():
                    action_to_add['actions'].append(act)
            if at.organization == 'NTLS':
                ret_actions_taken['NTLS'].append(action_to_add)
            elif at.organization == 'PNS':
                ret_actions_taken['PNS'].append(action_to_add)
            elif at.organization == 'FDRN':
                ret_actions_taken['FDRN'].append(action_to_add)
        return ret_actions_taken

    def get_weekly_latest_frs(self):
        dig_time = self.get_time_threshold_digest()
        ret_fr_list = []
        fr_list = list(FieldReport.objects.filter(created_at__gte=dig_time).order_by('-created_at'))
        for fr in fr_list:
            fr_data = {
                'id': fr.id,
                'country': fr.countries.all()[0].name if fr.countries else None,
                'summary': fr.summary,
                'created_at': fr.created_at,
            }
            ret_fr_list.append(fr_data)
        return ret_fr_list

    # Based on the notification type this constructs the different type of objects needed for the different templates
    def construct_template_record(self, rtype, record):
        if rtype != RecordType.WEEKLY_DIGEST:
            shortened = self.get_record_content(record, rtype)
            if len(shortened) > max_length:
                shortened = shortened[:max_length] + \
                            shortened[max_length:].split(' ', 1)[0] + '...' # look for the first space

        # TODO: Operation Update and Announcement types are missing
        if rtype == RecordType.FIELD_REPORT:
            rec_obj = {
                'resource_uri': self.get_resource_uri(record, rtype),
                'admin_uri': self.get_admin_uri(record, rtype),
                'title': self.get_record_title(record, rtype),
                'description': shortened,
                'key_figures': {
                    'affected': (record.num_affected or 0) + (record.gov_num_affected or 0) + (record.other_num_affected or 0),
                    'injured': (record.num_injured or 0) + (record.gov_num_injured or 0) + (record.other_num_injured or 0),
                    'dead': (record.num_dead or 0) + (record.gov_num_dead or 0) + (record.other_num_dead or 0),
                    'missing': (record.num_missing or 0) + (record.gov_num_missing or 0) + (record.other_num_missing or 0),
                    'displaced': (record.num_displaced or 0) + (record.gov_num_displaced or 0) + (record.other_num_displaced or 0),
                    'assisted': (record.num_assisted or 0) + (record.gov_num_assisted or 0) + (record.other_num_assisted or 0),
                    'local_staff': record.num_localstaff or 0,
                    'volunteers': record.num_volunteers or 0,
                    'expat_delegates': record.num_expats_delegates or 0,
                },
                'actions_taken': self.get_actions_taken(record.id),
                'actions_others': record.actions_others,
                'gov_assistance': 'Yes' if record.request_assistance else 'No',
                'ns_assistance': 'Yes' if record.ns_request_assistance else 'No',
            }
        elif rtype == RecordType.APPEAL:
            # Maybe we need these in the future
            # localstaff = FieldReport.objects.filter(event_id=record.event_id).values_list('num_localstaff', flat=True)
            # volunteers = FieldReport.objects.filter(event_id=record.event_id).values_list('num_volunteers', flat=True)
            # expats = FieldReport.objects.filter(event_id=record.event_id).values_list('num_expats_delegates', flat=True)
            rec_obj = {
                'resource_uri': self.get_resource_uri(record, rtype),
                'admin_uri': self.get_admin_uri(record, rtype),
                'title': self.get_record_title(record, rtype),
                'situation_overview': Event.objects.values_list('summary', flat=True).get(id=record.event_id) if record.event_id != None else '',
                'key_figures': {
                    'people_targeted': record.num_beneficiaries or 0,
                    'funding_req': record.amount_requested or 0,
                    'appeal_code': record.code,
                    'start_date': record.start_date,
                    'end_date': record.end_date,
                    # 'local_staff': localstaff[0] if localstaff else 0,
                    # 'volunteers': volunteers[0] if volunteers else 0,
                    # 'expat_delegates': expats[0] if expats else 0,
                },
                'field_reports': list(FieldReport.objects.filter(event_id=record.event_id)) if record.event_id != None else None,
            }
        elif rtype == RecordType.WEEKLY_DIGEST:
            dig_time = self.get_time_threshold_digest()
            rec_obj = {
                'active_dref': self.get_weekly_digest_data('dref'),
                'active_ea': self.get_weekly_digest_data('ea'),
                'funding_coverage': self.get_weekly_digest_data('fund'),
                'budget': self.get_weekly_digest_data('budget'),
                'population': self.get_weekly_digest_data('pop'),
                'highlighted_ops': self.get_weekly_digest_highlights(),
                'latest_ops': self.get_weekly_digest_latest_ops(),
                'latest_deployments': list(SurgeAlert.objects.filter(created_at__gte=dig_time).order_by('-created_at')),
                'latest_field_reports': self.get_weekly_latest_frs(),
            }
        else: # The default (old) template
            rec_obj = {
                'resource_uri': self.get_resource_uri(record, rtype),
                'admin_uri': self.get_admin_uri(record, rtype),
                'title': self.get_record_title(record, rtype),
                'content': shortened,
            }
        return rec_obj

    def notify(self, records, rtype, stype, uid=None):
        record_count = 0
        if records:
            record_count = records.count()
        if not record_count and rtype != RecordType.WEEKLY_DIGEST:
            return

        # Decide if it is a personal notification or batch
        if uid is None:
            emails = self.gather_subscribers(records, rtype, stype)
            if not len(emails):
                return
        else:
            usr = User.objects.filter(pk=uid, is_active=True)
            if not len(usr):
                return
            else:
                emails = list(usr.values_list('email', flat=True))  # Only one email in this case

        # TODO: maybe this needs to be adjusted based on the new functionality (at first only handling Weekly Digest)
        # Only serialize the first 10 records
        record_entries = []
        if rtype == RecordType.WEEKLY_DIGEST:
            record_entries.append(self.construct_template_record(rtype, None))
        else:
            entries = list(records) if record_count <= 10 else list(records[:10])
            for record in entries:
                record_entries.append(self.construct_template_record(rtype, record))

        if uid is not None:
            is_staff = usr.values_list('is_staff', flat=True)[0]

        if rtype == RecordType.WEEKLY_DIGEST:
            record_type = 'weekly digest'
        else:
            record_type = self.get_record_display(rtype, record_count)
        if uid is None:
            adj = 'new' if stype == SubscriptionType.NEW else 'modified'
            #subject = '%s %s %s in IFRC GO' % (
            if rtype == RecordType.WEEKLY_DIGEST:
                subject = '%s %s' % (
                    adj,
                    record_type,
                )
            else:
                subject = '%s %s %s' % (
                    record_count,
                    adj,
                    record_type,
                )
        else:
            #subject = '%s followed %s modified in IFRC GO' % (
            subject = '%s followed %s modified' % (
                record_count,
                record_type,
            )

        if self.is_retro_mode():
            subject += ' [daily followup]'

        template_path = self.get_template()
        if rtype == RecordType.FIELD_REPORT or rtype == RecordType.APPEAL or rtype == RecordType.WEEKLY_DIGEST:
            template_path = self.get_template(rtype)

        html = render_to_string(template_path, {
            'hello': get_hello(),
            'count': record_count,
            'records': record_entries,
            'is_staff': True if uid is None else is_staff, # TODO: fork the sending to "is_staff / not ~" groups
            'subject': subject,
        })
        recipients = emails

        if uid is None:
            if record_count == 1:
                subject += ': ' + record_entries[0]['title'] # On purpose after rendering – the subject changes only, not email body

            # For new (email-documented :10) events we store data to events_sent_to{ event_id: recipients }
            if stype == SubscriptionType.EDIT: # Recently we do not allow EDIT substription
                for e in list(records.values('id'))[:10]:
                    i = e['id']
                    if i not in events_sent_to:
                        events_sent_to[i] = []
                    email_list_to_add = list(set(events_sent_to[i] + recipients))
                    if email_list_to_add:
                        events_sent_to[i] = list(filter(None, email_list_to_add)) # filter to skip empty elements

            plural = '' if len(emails) == 1 else 's' # record_type has its possible plural thanks to get_record_display()
            logger.info('Notifying %s subscriber%s about %s %s %s' % (len(emails), plural, record_count, adj, record_type))
            send_notification(subject, recipients, html)
        else:
            if len(recipients):
                # check if email is not in events_sent_to{event_id: recipients}
                if not emails:
                    logger.info('Silent about the one-by-one subscribed %s – user %s has not set email address' % (record_type, uid))
                # Recently we do not allow EDIT (modif.) subscription, so it is irrelevant recently (do not check the 1+ events in loop) :
                elif (records[0].id not in events_sent_to) or (emails[0] not in events_sent_to[records[0].id]):
                    logger.info('Notifying %s subscriber about %s one-by-one subscribed %s' % (len(emails), record_count, record_type))
                    send_notification(subject, recipients, html)
                else:
                    logger.info('Silent about a one-by-one subscribed %s – user already notified via generic subscription' % (record_type))

    def index_new_records(self, records):
        self.bulk([self.convert_for_bulk(record, create=True) for record in list(records)])

    def index_updated_records(self, records):
        self.bulk([self.convert_for_bulk(record, create=False) for record in list(records)])

    def convert_for_bulk(self, record, create):
        data = record.indexing()
        metadata = {
            '_op_type': 'create' if create else 'update',
            '_index': ES_PAGE_NAME,
            '_type': 'page',
            '_id': record.es_id()
        }
        if (create):
            metadata.update(**data)
        else:
            metadata['doc'] = data
        return metadata

    def bulk(self, actions):
        try:
            created, errors = bulk(client=ES_CLIENT , actions=actions)
            if len(errors):
                logger.error('Produced the following errors:')
                logger.error('[%s]' % ', '.join(map(str, errors)))
        except Exception as e:
            logger.error('Could not index records')
            logger.error('%s...' % str(e)[:512])

    # Remove items in a queryset where updated_at == created_at.
    # This leaves us with only ones that have been modified.
    def filter_just_created(self, queryset):
        if queryset.first() is None:
            return []
        if hasattr(queryset.first(), 'modified_at') and queryset.first().modified_at is not None:
            return [record for record in queryset if (
                record.modified_at.replace(microsecond=0) == record.created_at.replace(microsecond=0))]
        else:
            return [record for record in queryset if (
                record.updated_at.replace(microsecond=0) == record.created_at.replace(microsecond=0))]

    def handle(self, *args, **options):
        if self.is_digest_mode():
            t = self.get_time_threshold_digest() # in digest mode (1ce a week, for new_entities only) we use a bigger interval
        else:
            t = self.get_time_threshold()
        t2 = self.get_time_threshold2()

        cond1 = Q(created_at__gte=t)
        condU = Q(updated_at__gte=t)
        condR = Q(real_data_update__gte=t) # instead of modified at
        cond2 = ~Q(previous_update__gte=t2) # we negate (~) this, so we want: no previous_update in the last day. So: send once a day!
        condF = Q(auto_generated_source='New field report') # We exclude those events that were generated from field reports, to avoid 2x notif.

        # In this section we check if there was 2 FOLLOWED_EVENT modifications in the last 24 hours (for which there was no duplicated email sent, but now will be one).
        if self.is_retro_mode():
            condU = Q(updated_at__gte=t2)
            cond2 = Q(previous_update__gte=t2) # not negated. We collect those, who had 2 changes in the last 1 day.
            followed_eventparams = Subscription.objects.filter(event_id__isnull=False)
            users_of_followed_events = followed_eventparams.values_list('user_id', flat=True).distinct()
            for usr in users_of_followed_events: # looping in user_ids of specific FOLLOWED_EVENT subscriptions (8)
                eventlist = followed_eventparams.filter(user_id=usr).values_list('event_id', flat=True).distinct()
                cond3 = Q(pk__in=eventlist) # getting their events as a condition
                followed_events = Event.objects.filter(condU & cond2 & cond3)
                if len(followed_events): # usr - unique (we loop one-by-one), followed_events - more
                    self.notify(followed_events, RecordType.FOLLOWED_EVENT, SubscriptionType.NEW, usr)
        else:
            new_reports = FieldReport.objects.filter(cond1)
            updated_reports = FieldReport.objects.filter(condU & cond2)

            new_appeals = Appeal.objects.filter(cond1)
            updated_appeals = Appeal.objects.filter(condR & cond2)

            new_events = Event.objects.filter(cond1).exclude(condF)
            updated_events = Event.objects.filter(condU & cond2)

            new_surgealerts = SurgeAlert.objects.filter(cond1)

            new_pers_deployments = PersonnelDeployment.objects.filter(cond1) # CHECK: Best instantiation of Deployment Messages? Frontend appearance?!?
            # No need for indexing for personnel deployments

            # Approaching End of Mission ? new_approanching_end = PersonnelDeployment.objects.filter(end-date is close?)
            # No need for indexing for Approaching End of Mission

            # PER Due Dates ? new_per_due_date_warnings = User.objects.filter(PER admins of countries/regions, for whom the setting/per_due_date is in 1 week)
            # No need for indexing for PER Due Dates

            followed_eventparams = Subscription.objects.filter(event_id__isnull=False)
            ## followed_events = Event.objects.filter(updated_at__gte=t, pk__in=[x.event_id for x in followed_eventparams])

            # Merge Weekly Digest into one mail instead of separate ones
            if self.is_digest_mode():
                self.notify(None, RecordType.WEEKLY_DIGEST, SubscriptionType.NEW)
            else:
                self.notify(new_reports, RecordType.FIELD_REPORT, SubscriptionType.NEW)
                #self.notify(updated_reports, RecordType.FIELD_REPORT, SubscriptionType.EDIT)

                self.notify(new_appeals, RecordType.APPEAL, SubscriptionType.NEW)
                #self.notify(updated_appeals, RecordType.APPEAL, SubscriptionType.EDIT)

                self.notify(new_events, RecordType.EVENT, SubscriptionType.NEW)
                #self.notify(updated_events, RecordType.EVENT, SubscriptionType.EDIT)

                self.notify(new_surgealerts, RecordType.SURGE_ALERT, SubscriptionType.NEW)

                self.notify(new_pers_deployments, RecordType.SURGE_DEPLOYMENT_MESSAGES, SubscriptionType.NEW)

            users_of_followed_events = followed_eventparams.values_list('user_id', flat=True).distinct()
            for usr in users_of_followed_events: # looping in user_ids of specific FOLLOWED_EVENT subscriptions (8)
                eventlist = followed_eventparams.filter(user_id=usr).values_list('event_id', flat=True).distinct()
                cond3 = Q(pk__in=eventlist) # getting their events as a condition
                followed_events = Event.objects.filter(condU & cond2 & cond3)
                if len(followed_events): # usr - unique (we loop one-by-one), followed_events - more
                    self.notify(followed_events, RecordType.FOLLOWED_EVENT, SubscriptionType.NEW, usr)

            logger.info('Indexing %s updated field reports' % updated_reports.count())
            self.index_updated_records(self.filter_just_created(updated_reports))
            logger.info('Indexing %s updated appeals' % updated_appeals.count())
            self.index_updated_records(self.filter_just_created(updated_appeals))
            logger.info('Indexing %s updated events' % updated_events.count())
            self.index_updated_records(self.filter_just_created(updated_events))

            logger.info('Indexing %s new field reports' % new_reports.count())
            self.index_new_records(new_reports)
            logger.info('Indexing %s new appeals' % new_appeals.count())
            self.index_new_records(new_appeals)
            logger.info('Indexing %s new events' % new_events.count())
            self.index_new_records(new_events)


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

from collections import defaultdict
import struct

from sqlalchemy.sql.expression import text

from ambry.orm.dataset import Dataset

from ambry.library.search_backends.base import BaseDatasetIndex, BasePartitionIndex,\
    BaseIdentifierIndex, BaseSearchBackend, IdentifierSearchResult,\
    DatasetSearchResult, PartitionSearchResult, SearchTermParser

from ambry.util import get_logger
import logging

logger = get_logger(__name__, propagate=False)
#logger.setLevel(logging.DEBUG)

class SQLiteSearchBackend(BaseSearchBackend):

    def _get_dataset_index(self):
        """ Returns dataset index. """
        # returns initialized dataset index
        return DatasetSQLiteIndex(backend=self)

    def _get_partition_index(self):
        """ Returns partition index. """
        return PartitionSQLiteIndex(backend=self)

    def _get_identifier_index(self):
        """ Returns identifier index. """
        return IdentifierSQLiteIndex(backend=self)

    def _and_join(self, terms):
        """ AND join of the terms.

        Args:
            terms (list):

        Examples:
            self._and_join(['term1', 'term2'])

        Returns:
            str
        """
        if len(terms) > 1:
            return ' '.join([self._or_join(t) for t in terms])
        else:
            return self._or_join(terms[0])

class DatasetSQLiteIndex(BaseDatasetIndex):

    def __init__(self, backend=None):
        assert backend is not None, 'backend argument can not be None.'
        super(self.__class__, self).__init__(backend=backend)

        logger.debug('Creating dataset FTS table.')

        query = """\
            CREATE VIRTUAL TABLE IF NOT EXISTS dataset_index USING fts3(
                vid VARCHAR(256) NOT NULL,
                title TEXT,
                keywords TEXT,
                doc TEXT
            );
        """
        self.backend.library.database.connection.execute(query)

    def _make_query_from_terms(self, terms):
        """ Creates a query for dataset from decomposed search terms.

        Args:
            terms (dict or unicode or string):

        Returns:
            tuple of (str, dict): First element is str with FTS query, second is parameters of the query.

        """
        match_query = ''

        expanded_terms = self._expand_terms(terms)
        if expanded_terms['doc']:
            match_query = self.backend._or_join(expanded_terms['doc'])
        if expanded_terms['keywords']:
            if match_query:
                match_query += self.backend._and_join(
                    match_query, self.backend._join_keywords(expanded_terms['keywords']))
            else:
                match_query = self.backend._join_keywords(expanded_terms['keywords'])

        query = text("""
            SELECT vid, rank(matchinfo(dataset_index)) AS score
            FROM dataset_index
            WHERE dataset_index MATCH :match_query
            ORDER BY score DESC;
        """)
        query_params = {
            'match_query': match_query}

        return query, query_params

    def search(self, search_phrase, limit=None):
        """ Finds datasets by search phrase.

        Args:
            search_phrase (str or unicode):
            limit (int, optional): how many results to return. None means without limit.

        Returns:
            list of DatasetSearchResult instances.

        """
        # SQLite FTS can't find terms with `-`, therefore all hyphens were replaced with underscore
        # before save. Now to get appropriate result we need to replace all hyphens in the search phrase.
        # See http://stackoverflow.com/questions/3865733/how-do-i-escape-the-character-in-sqlite-fts3-queries
        search_phrase = search_phrase.replace('-', '_')
        query, query_params = self._make_query_from_terms(search_phrase)

        self._parsed_query = (query, query_params)

        connection = self.backend.library.database.connection
        # Operate on the raw connection
        connection.connection.create_function('rank', 1, _make_rank_func((1., .1, 0, 0)))

        logger.debug('Searching datasets using `{}` query.'.format(query))
        results = connection.execute(query,
                                     **query_params).fetchall()  # Query on the Sqlite proxy to the raw connection

        datasets = defaultdict(DatasetSearchResult)
        for result in results:
            vid, score = result
            datasets[vid] = DatasetSearchResult()
            datasets[vid].vid = vid
            datasets[vid].b_score = score

        logger.debug('Extending datasets with partitions.')
        for partition in self.backend.partition_index.search(search_phrase):
            datasets[partition.dataset_vid].p_score += partition.score
            datasets[partition.dataset_vid].partitions.add(partition)
        return list(datasets.values())

    def list_documents(self, limit=None):
        """ Generates vids of all indexed datasets.

        Args:
            limit (int, optional): If not empty, the maximum number of results to return

        Generates:
            str: vid of the dataset.
        """
        limit_str = ''
        if limit:
            try:
                limit_str = 'LIMIT {}'.format(int(limit))
            except (TypeError, ValueError):
                pass

        query = ('SELECT vid FROM dataset_index ' + limit_str)

        for row in self.backend.library.database.connection.execute(query).fetchall():
            yield row['vid']

    def _as_document(self, dataset):
        """ Converts dataset to document indexed by to FTS index.

        Args:
            dataset (orm.Dataset): dataset to convert.

        Returns:
            dict with structure matches to BaseDatasetIndex._schema.

        """
        assert isinstance(dataset, Dataset)

        doc = super(self.__class__, self)._as_document(dataset)

        # SQLite FTS can't find terms with `-`, replace it with underscore here and while searching.
        # See http://stackoverflow.com/questions/3865733/how-do-i-escape-the-character-in-sqlite-fts3-queries
        doc['keywords'] = doc['keywords'].replace('-', '_')
        doc['doc'] = doc['doc'].replace('-', '_')
        doc['title'] = doc['title'].replace('-', '_')
        return doc

    def _index_document(self, document, force=False):
        """ Adds document to the index. """
        query = text("""
            INSERT INTO dataset_index(vid, title, keywords, doc)
            VALUES(:vid, :title, :keywords, :doc);
        """)
        self.backend.library.database.connection.execute(query, **document)

    def reset(self):
        """ Drops index table. """
        query = """
            DROP TABLE dataset_index;
        """
        self.backend.library.database.connection.execute(query)

    def _delete(self, vid=None):
        """ Deletes given dataset from index.

        Args:
            vid (str): dataset vid.

        """
        query = text("""
            DELETE FROM dataset_index
            WHERE vid = :vid;
        """)
        self.backend.library.database.connection.execute(query, vid=vid)

    def is_indexed(self, dataset):
        """ Returns True if dataset is already indexed. Otherwise returns False. """
        query = text("""
            SELECT vid
            FROM dataset_index
            WHERE vid = :vid;
        """)
        result = self.backend.library.database.connection.execute(query, vid=dataset.vid)
        return bool(result.fetchall())

    def all(self):
        """ Returns list with all indexed datasets. """
        datasets = []

        query = text("""
            SELECT vid
            FROM dataset_index;""")

        for result in self.backend.library.database.connection.execute(query):
            res = DatasetSearchResult()
            res.vid = result[0]
            res.b_score = 1
            datasets.append(res)
        return datasets

class IdentifierSQLiteIndex(BaseIdentifierIndex):

    def __init__(self, backend=None):
        assert backend is not None, 'backend argument can not be None.'
        super(self.__class__, self).__init__(backend=backend)

        logger.debug('Creating identifier FTS table.')

        query = """\
            CREATE VIRTUAL TABLE IF NOT EXISTS identifier_index USING fts3(
                identifier VARCHAR(256) NOT NULL,
                type VARCHAR(256) NOT NULL,
                name TEXT
            );
        """
        self.backend.library.database.connection.execute(query)

    def search(self, search_phrase, limit=None):
        """ Finds identifiers by search phrase.

        Args:
            search_phrase (str or unicode):
            limit (int, optional): how many results to return. None means without limit.

        Returns:
            list of IdentifierSearchResult instances.

        """
        query_parts = [
            'SELECT identifier, type, name, 0',
            'FROM identifier_index',
            'WHERE name MATCH :part']
        query_params = {
            'part': '*{}*'.format(search_phrase)}

        query_parts.append('ORDER BY name')
        if limit:
            query_parts.append('LIMIT :limit')
            query_params['limit'] = limit
        query_parts.append(';')
        query = text('\n'.join(query_parts))

        results = self.backend.library.database.connection.execute(query, **query_params).fetchall()
        for result in results:
            vid, type, name, score = result
            yield IdentifierSearchResult(
                score=score, vid=vid,
                type=type, name=name)

    def list_documents(self, limit=None):
        """ Generates vids of all indexed identifiers.

        Args:
            limit (int, optional): If not empty, the maximum number of results to return

        Generates:
            str: vid of the document.
        """
        limit_str = ''
        if limit:
            try:
                limit_str = 'LIMIT {}'.format(int(limit))
            except (TypeError, ValueError):
                pass

        query = ('SELECT identifier FROM identifier_index ' + limit_str)

        for row in self.backend.library.database.connection.execute(query).fetchall():
            yield row['identifier']

    def _index_document(self, identifier, force=False):
        """ Adds identifier document to the index. """
        query = text("""
            INSERT INTO identifier_index(identifier, type, name)
            VALUES(:identifier, :type, :name);
        """)
        self.backend.library.database.connection.execute(query, **identifier)

    def reset(self):
        """ Drops index table. """
        query = """
            DROP TABLE identifier_index;
        """
        self.backend.library.database.connection.execute(query)

    def _delete(self, identifier=None):
        """ Deletes given identifier from index.

        Args:
            identifier (str): identifier of the document to delete.

        """
        query = text("""
            DELETE FROM identifier_index
            WHERE identifier = :identifier;
        """)
        self.backend.library.database.connection.execute(query, identifier=identifier)

    def is_indexed(self, identifier):
        """ Returns True if identifier is already indexed. Otherwise returns False. """
        query = text("""
            SELECT identifier
            FROM identifier_index
            WHERE identifier = :identifier;
        """)
        result = self.backend.library.database.connection.execute(query, identifier=identifier['identifier'])
        return bool(result.fetchall())

    def all(self):
        """ Returns list with all indexed identifiers. """
        identifiers = []

        query = text("""
            SELECT identifier, type, name
            FROM identifier_index;""")

        for result in self.backend.library.database.connection.execute(query):
            vid, type_, name = result
            res = IdentifierSearchResult(
                score=1, vid=vid, type=type_, name=name)
            identifiers.append(res)
        return identifiers

class PartitionSQLiteIndex(BasePartitionIndex):

    def __init__(self, backend=None):
        assert backend is not None, 'backend argument can not be None.'
        super(self.__class__, self).__init__(backend=backend)

        logger.debug('Creating partition FTS table.')

        query = """\
            CREATE VIRTUAL TABLE IF NOT EXISTS partition_index USING fts3(
                vid VARCHAR(256) NOT NULL,
                dataset_vid VARCHAR(256) NOT NULL,
                from_year INTEGER,
                to_year INTEGER,
                title TEXT,
                keywords TEXT,
                doc TEXT
            );
        """
        self.backend.library.database.connection.execute(query)

    def search(self, search_phrase, limit=None):
        """ Finds partitions by search phrase.

        Args:
            search_phrase (str or unicode):
            limit (int, optional): how many results to generate. None means without limit.

        Generates:
            PartitionSearchResult instances.
        """

        # SQLite FTS can't find terms with `-`, therefore all hyphens replaced with underscore before save.
        # Now to make proper query we need to replace all hyphens in the search phrase.
        # See http://stackoverflow.com/questions/3865733/how-do-i-escape-the-character-in-sqlite-fts3-queries
        search_phrase = search_phrase.replace('-', '_')
        terms = SearchTermParser().parse(search_phrase)
        from_year = terms.pop('from', None)
        to_year = terms.pop('to', None)

        query, query_params = self._make_query_from_terms(terms)

        self._parsed_query = (query, query_params)

        connection = self.backend.library.database.connection

        connection.connection.create_function('rank', 1, _make_rank_func((1., .1, 0, 0)))

        # SQLite FTS implementation does not allow to create indexes on FTS tables.
        # see https://sqlite.org/fts3.html 1.5. Summary, p 1:
        # ... it is not possible to create indices ...
        #
        # So, filter years range here.

        results = connection.execute(query, query_params).fetchall()

        for result in results:
            vid, dataset_vid, score, db_from_year, db_to_year = result
            if from_year and from_year < db_from_year:
                continue
            if to_year and to_year > db_to_year:
                continue
            yield PartitionSearchResult(
                vid=vid, dataset_vid=dataset_vid, score=score)

    def list_documents(self, limit=None):
        """ Generates vids of all indexed partitions.

        Args:
            limit (int, optional): If not empty, the maximum number of results to return

        Generates:
            str: vid of the document.
        """

        limit_str = ''
        if limit:
            try:
                limit_str = 'LIMIT {}'.format(int(limit))
            except (TypeError, ValueError):
                pass

        query = ('SELECT vid FROM partition_index ' + limit_str)

        for row in self.backend.library.database.connection.execute(query).fetchall():
            yield row['vid']

    def _as_document(self, partition):
        """ Converts partition to document indexed by to FTS index.

        Args:
            partition (orm.Partition): partition to convert.

        Returns:
            dict with structure matches to BasePartitionIndex._schema.

        """
        doc = super(self.__class__, self)._as_document(partition)

        # SQLite FTS can't find terms with `-`, replace it with underscore here and while searching.
        # See http://stackoverflow.com/questions/3865733/how-do-i-escape-the-character-in-sqlite-fts3-queries
        doc['keywords'] = doc['keywords'].replace('-', '_')
        doc['doc'] = doc['doc'].replace('-', '_')
        doc['title'] = doc['title'].replace('-', '_')

        # pass time_coverage to the _index_document.
        doc['time_coverage'] = partition.time_coverage
        return doc

    def _make_query_from_terms(self, terms):
        """ Creates a query for partition from decomposed search terms.

        Args:
            terms (dict or unicode or string):

        Returns:
            tuple of (str, dict): First element is str with FTS query, second is parameters of the query.

        """

        match_query = ''

        expanded_terms = self._expand_terms(terms)
        if expanded_terms['doc']:
            match_query = self.backend._and_join(expanded_terms['doc'])

        if expanded_terms['keywords']:
            if match_query:
                match_query = self.backend._and_join(
                    [match_query, self.backend._join_keywords(expanded_terms['keywords'])])
            else:
                match_query = self.backend._join_keywords(expanded_terms['keywords'])

        if match_query:
            query = text("""
                SELECT vid, dataset_vid, rank(matchinfo(partition_index)) AS score, from_year, to_year
                FROM partition_index
                WHERE partition_index MATCH :match_query
                ORDER BY score DESC;
            """)
            query_params = {
                'match_query': match_query}
        else:
            query = text("""
                SELECT vid, dataset_vid, rank(matchinfo(partition_index)), from_year, to_year AS score
                FROM partition_index""")
            query_params = {}

        return query, query_params

    def _index_document(self, document, force=False):
        """ Adds parition document to the index. """
        from ambry.util import int_maybe
        time_coverage = document.pop('time_coverage', [])
        from_year = None
        to_year = None
        if time_coverage:
            from_year = int_maybe(time_coverage[0])
            to_year = int_maybe(time_coverage[-1])

        query = text("""
            INSERT INTO partition_index(vid, dataset_vid, title, keywords, doc, from_year, to_year)
            VALUES(:vid, :dataset_vid, :title, :keywords, :doc, :from_year, :to_year); """)
        self.backend.library.database.connection.execute(
            query, from_year=from_year, to_year=to_year, **document)

    def reset(self):
        """ Drops index table. """
        query = """
            DROP TABLE partition_index;
        """
        self.backend.library.database.connection.execute(query)

    def _delete(self, vid=None):
        """ Deletes partition with given vid from index.

        Args:
            vid (str): vid of the partition document to delete.

        """
        query = text("""
            DELETE FROM partition_index
            WHERE vid = :vid;
        """)
        self.backend.library.database.connection.execute(query, vid=vid)

    def is_indexed(self, partition):
        """ Returns True if partition is already indexed. Otherwise returns False. """
        query = text("""
            SELECT vid
            FROM partition_index
            WHERE vid = :vid;
        """)
        result = self.backend.library.database.connection.execute(query, vid=partition.vid)
        return bool(result.fetchall())

    def all(self):
        """ Returns list with vids of all indexed partitions. """
        partitions = []

        query = text("""
            SELECT dataset_vid, vid
            FROM partition_index;""")

        for result in self.backend.library.database.connection.execute(query):
            dataset_vid, vid = result
            partitions.append(PartitionSearchResult(dataset_vid=dataset_vid, vid=vid, score=1))
        return partitions

def _make_rank_func(weights):
    def rank(matchinfo):
        # matchinfo is defined as returning 32-bit unsigned integers
        # in machine byte order
        # http://www.sqlite.org/fts3.html#matchinfo
        # and struct defaults to machine byte order
        matchinfo = struct.unpack('I' * int(len(matchinfo) / 4), matchinfo)
        it = iter(matchinfo[2:])
        return sum(x[0] * w / x[1]
                   for x, w in zip(list(zip(it, it, it)), weights)
                   if x[1])
    return rank


from math import dist, inf
from typing import Optional
from random import random, choice

from aasd.vehicle import Vehicle, VehicleType

class Environment:
    def __init__(self, width: int = 1280, height: int = 720, object_size: int = 10, chance_to_crash: float = 0.001):
        self.width = width
        self.height = height
        self.obj_size: int = object_size
        self.vehicles: list[Vehicle] = []
        self.chance_to_crash: float = chance_to_crash

    def register_vehicle(self, vehicle: Vehicle):
        self.vehicles.append(vehicle)

    def unregister_vehicle(self, vehicle: Vehicle):
        self.vehicles.remove(vehicle)

    def get_emergency_vehicles(self) -> list[Vehicle]:
        return [
            vehicle
            for vehicle in self.vehicles
            if vehicle.type is VehicleType.Emergency
        ]

    def get_nearby_vehicles(self, caller: Vehicle, radius: float):
        return [
            vehicle
            for vehicle in self.vehicles
            if is_nearby(vehicle, caller, radius) and vehicle is not caller
        ]

    def get_random_coordinates(self) -> tuple[float, float]:
        x = random() * self.width + 5
        y = random() * self.height + 5
        if x > self.width:
            x = float(self.width)
        if y > self.height:
            y = float(self.height)
        return x, y

    def move_vehicles(self):
        for vehicle in self.vehicles:
            vehicle.move(self.width - self.obj_size, self.height - self.obj_size)

    def make_random_accident(self) -> str:
        vehicle = choice(self.vehicles)
        vehicle.type = VehicleType.Crashed
        return vehicle.id

    def get_vehicle(self, vehicle_id: str) -> Optional[Vehicle]:
        for vehicle in self.vehicles:
            if vehicle.id == vehicle_id:
                return vehicle
        else:
            return None

    def get_closest_emergency_vehicle(self, x: float, y: float):
        emergency_vehicles = [v for v in self.vehicles if v.type is VehicleType.Emergency]
        closest_ev = None
        lowest_distance = inf
        for ev in emergency_vehicles:
            distance = dist(ev.get_coordinates(), (x, y))
            if distance < lowest_distance:
                lowest_distance = distance
                closest_ev = ev
        return closest_ev

    def are_vehicles_nearby(self, id1: str, id2: str, radius: float) -> bool:
        return is_nearby(self.get_vehicle(id1), self.get_vehicle(id2), radius)

def is_nearby(vehicle1: Optional[Vehicle], vehicle2: Optional[Vehicle], radius: float) -> bool:
    if vehicle1 is None or vehicle2 is None:
        return False
    return dist(vehicle1.get_coordinates(), vehicle2.get_coordinates()) <= radius


import random
import json
import znc

class slapanswer(znc.Module):
    description = 'Answer slaps'
    module_types = [znc.CModInfo.NetworkModule]

    def OnLoad(self, args, message):
        self.default_answers = [
            '"Be kind whenever possible. It is always possible." - Dalai Lama',
            '"Where ignorance is our master, there is no possibility of real'
            ' peace." - Dalai Lama',
            '"We can never obtain peace in the outer world until we make peace'
            ' with ourselves." - Dalai Lama',
            '"An eye for an eye will only make the whole world blind."'
            ' - Mahatma Gandhi',
            '"The best fighter is never angry" - Lao Tzu',
            '"Peace cannot be achieved through violence, it can only be'
            ' attained through understanding." - Ralph Waldo Emerson',
            '"Silence is sometimes the best answer" - Dalai Lama',
        ]
        if 'answers' in self.nv:
            self.ANSWERS = json.loads(self.nv['answers'])
        else:
            self.ANSWERS = self.default_answers
            self.save_answers()
        return True

    def OnModCommand(self, cmd):
        split = cmd.split()
        command = str(split[0]).lower()
        args = [a.lower() for a in split[1:]]
        if command == 'help':
            self.command_help()
        elif command == 'add':
            self.command_add(args)
        elif command == 'remove':
            self.command_remove(args)
        elif command == 'reset':
            self.command_reset()
        elif command == 'list':
            self.command_list()

    def save_answers(self):
        self.nv['answers'] = json.dumps(self.ANSWERS)

    def command_help(self):
        self.PutModule('\n'.join([
            'add <msg> | add a msg (replace nick with {nick})',
            'remove <id> | remove msg with id <id> (get id\'s with "list")',
            'reset | reset msgs to default',
            'list | get a list with msgs'
        ]))
        return True

    def command_add(self, args):
        msg = ' '.join(args)
        if '\n' in msg:
            self.PutModule('ERROR: Line-Breaks are not allowed in answers!')
            return True
        self.ANSWERS.append(msg)
        self.save_answers()
        self.PutModule('Successfully added answer!')
        return True

    def command_remove(self, args):
        try:
            answer_id = int(args[0])
        except ValueError:
            self.PutModule('ERROR: Invalid ID!')
        if answer_id < len(self.ANSWERS) and answer_id >= 0:
            del self.ANSWERS[answer_id]
            self.save_answers()
            self.PutModule('Successfully removed answer!')
        else:
            self.PutModule(
                'ERROR: Invalid ID! Try "list" for a list of id\'s!'
            )
        return True

    def command_reset(self):
        self.ANSWERS = self.default_answers
        self.save_answers()
        self.PutModule('Successfully reset answers!')
        return True

    def command_list(self):
        for index, value in enumerate(self.ANSWERS):
            self.PutModule('{} | {}'.format(index, value))
        return True

    def OnChanAction(self, invoker, channel, message):
        own_nick = self.GetNetwork().GetIRCNick().GetNick()
        own_host = self.GetNetwork().GetIRCNick().GetHostMask()
        nick = invoker.GetNick()
        channel = channel.GetName()
        msg = str(message)
        if 'slap' in msg and own_nick in msg:
            self.answer_slap(channel, nick, own_host)
        return znc.CONTINUE

    def answer_slap(self, channel, nick, own_host):
        msg = random.choice(self.ANSWERS)
        if '{nick}' in msg:
            msg = msg.format(nick=nick)
        msg = 'PRIVMSG {channel} :{msg}'.format(channel=channel, msg=msg)
        self.GetNetwork().PutIRC(msg)
        self.GetNetwork().PutUser(':{own_host} {msg}'.format(
            own_host=own_host, msg=msg))


import sklearn
import os
import numpy as np
import matplotlib.pyplot as plt
import timeit

current_dir = os.getcwd()
from tensorflow.keras.datasets import mnist

(X_train, Y_train), (X_test, Y_test) = mnist.load_data(path=current_dir + '/mnist.npz')

# FLATTING TRAIN DATA
X_train = X_train.reshape(60000, 784)
X_test = X_test.reshape(10000, 784)

# ONE HOT ENCODING
nb_classes = 10
targets1 = np.array(Y_train).reshape(-1)
Y_train = np.eye(nb_classes)[targets1]
targets2 = np.array(Y_test).reshape(-1)
Y_test = np.eye(nb_classes)[targets2]

# DATASET SHAPE
print('MNIST Dataset Shape:')
print('X_train: ' + str(X_train.shape))
print('Y_train: ' + str(Y_train.shape))
print('X_test:  ' + str(X_test.shape))
print('Y_test:  ' + str(Y_test.shape))

# SOFTMAX ACTIVATION FUNCTION
def activation_function(z):
    """returns y hat"""
    a = np.transpose(np.transpose(np.exp(z)) / (np.sum(np.exp(z), axis=1)))
    return a

def loss_function(y, y_hat):
    L = -1 * np.sum(np.matmul(np.transpose(y), np.log(y_hat)))
    return L

def gradient_descent(y, y_hat, x):
    grad = np.transpose(np.dot(np.transpose(x), (y - y_hat))) / y.size
    # grad = (y - y_hat)*0.001
    # print(grad[2])
    return grad

# INIT PARAMS
W = np.zeros((10, 784))
b = np.zeros(10)
y_hat = np.zeros(10)

learningRate = 0.001

# TRAIN
start = timeit.default_timer()

for i in range(50):
    for j in range(100):
        X_train_batch = X_train[(j * 600 + 0):(j + 1) * 600, ]
        Y_train_batch = Y_train[(j * 600 + 0):(j + 1) * 600, ]
        Z = np.dot(X_train_batch, W.transpose())
        y_hat = activation_function(Z)
        step = gradient_descent(Y_train_batch, y_hat, X_train_batch)
        W = W + learningRate * step

stop = timeit.default_timer()

# EVALUATE
Z = np.dot(X_test, W.transpose())
y_hat = activation_function(Z)
y_pred = y_hat.argmax(axis=1)
true_labels = Y_test.argmax(axis=1)

correct = 0
total = 10000
for i in range(total):
    if y_pred[i] == true_labels[i]:  # index
        correct += 1

accuracy = (correct / total)
print("ACCURANCY OF SOFTMAX TRAINING IS: ", accuracy)

print('Time: ', stop - start)


import requests
from bs4 import BeautifulSoup
from bs4.element import ResultSet
import json

from telprefix.path import JSON_DATA_PATH

def getHTMLText(result: ResultSet | None) -> str:
    if result is not None:
        result = result.text.strip()
    return result

# URL Artikel
# Sumber: https://www.pinhome.id
URL = "https://www.pinhome.id/blog/kode-nomor-prefix/"

class TelPrefixScrap():
    def __init__(self) -> None:
        self.data = {}
        self.URL = URL

    def request(self) -> BeautifulSoup:
        req = requests.get(URL)
        reqParse = BeautifulSoup(req.text, "html.parser")

        return reqParse

    def parse(self):
        reqParse = self.request()
        tables = reqParse.find_all("table")

        currentTable = 0
        for index, table in enumerate(tables):
            tableRow = table.find_all("tr")
            for row in tableRow[1:]:
                # Extract table rows data
                prefix = row.find_all("td")[0]

                tableRowJudul = tableRow[0]
                if( len(tableRowJudul.find_all("td")) == 4 ):
                    jenis = row.find_all("td")[1]
                    keterangan = row.find_all("td")[2]
                    provider = row.find_all("td")[3]
                else:
                    jenis = None
                    keterangan = row.find_all("td")[1]
                    provider = row.find_all("td")[2]

                # Get & strip text
                prefix = getHTMLText(prefix)
                provider = getHTMLText(provider)
                jenis = getHTMLText(jenis)
                keterangan = getHTMLText(keterangan)

                # Tidy up
                if provider == "":
                    if index == currentTable:
                        provider = self.data[list(self.data.keys())[-1]]["provider"]
                    else:
                        provider = None

                if jenis is not None:
                    if "atau" in jenis:
                        jenis = jenis.split(" atau ")
                    elif "dan" in jenis:
                        jenis = jenis.split(" dan ")

                self.data[prefix] = {
                    "provider": provider,
                    "jenis": jenis,
                    "keterangan": keterangan
                }

            currentTable += 1

        return self.data

    def save(self):
        with open(JSON_DATA_PATH, "w") as file:
            json.dump(self.data, file, indent=4)

    def scrap(self):
        parse = self.parse()
        save = self.save()

        return parse


import os
import urllib.request

WEIGHTS_URL = 'https://d17h27t6h515a5.cloudfront.net/topher/2016/October/580d880c_bvlc-alexnet/bvlc-alexnet.npy'
TRAINING_URL = 'https://d17h27t6h515a5.cloudfront.net/topher/2016/October/580a829f_train/train.p'

def download(url, filename):
    print('Downloading {}...'.format(filename))
    urllib.request.urlretrieve(url, filename)
    filestat = os.stat(filename)
    size = filestat.st_size
    print('Successfully downloaded {} ({} bytes)'.format(filename, size))
    return filename

def get_data():
    train_file = 'train.p'
    weights_file = 'bvlc-alexnet.npy'

    if not os.path.isfile(train_file):
        download(TRAINING_URL, train_file)

    if not os.path.isfile(weights_file):
        download(WEIGHTS_URL, weights_file)


#
#           SAKARYA ÜNİVERSİTESİ BİLGİSAYAR VE BİLİŞİM BİLİMLERİ FAKÜLTESİ
#                           BİLGİSAYAR MÜHENDİSLİĞİ BÖLÜMÜ
#               BİLGİSAYAR MÜHENDİSLİĞİ TASARIMI - 2. ÖĞRETİM P GRUBU
#           EDA NUR KARAMUK - G181210061 & ELİF RUMEYSA AYDIN - G181210031
#
#

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtGui import QImage, QPixmap
import cv2, imutils
import sqlite3 as sql
import os
os.system('python Connection.py')
os.system('python CreateTable.py')
from PlateRecognitionAlgorithm import plateRecognize
from PlateRecords import Ui_SecondWindow

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1065, 594)
        MainWindow.setAutoFillBackground(False)
        MainWindow.setStyleSheet("background-color: rgb(226, 226, 226);")
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("Icon/car.png"), QtGui.QIcon.Selected, QtGui.QIcon.On)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.plate_result = QtWidgets.QGroupBox(self.centralwidget)
        self.plate_result.setGeometry(QtCore.QRect(550, 10, 481, 551))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.plate_result.setFont(font)
        self.plate_result.setStyleSheet("background-color: rgb(235, 235, 235);")
        self.plate_result.setObjectName("plate_result")
        self.label_PlateResult = QtWidgets.QLabel(self.plate_result)
        self.label_PlateResult.setGeometry(QtCore.QRect(10, 40, 461, 351))
        self.label_PlateResult.setStyleSheet("background-color: rgb(200, 200, 200);")
        self.label_PlateResult.setText("")
        self.label_PlateResult.setAlignment(QtCore.Qt.AlignCenter)
        self.label_PlateResult.setObjectName("label_PlateResult")
        self.textPlateResult = QtWidgets.QTextEdit(self.plate_result)
        self.textPlateResult.setGeometry(QtCore.QRect(10, 400, 271, 61))
        font = QtGui.QFont()
        font.setPointSize(16)
        self.textPlateResult.setFont(font)
        self.textPlateResult.setStyleSheet("background-color: rgb(255, 255, 255);")
        self.textPlateResult.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.textPlateResult.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.textPlateResult.setReadOnly(True)
        self.textPlateResult.setObjectName("textPlateResult")
        self.textEdit = QtWidgets.QTextEdit(self.plate_result)
        self.textEdit.setGeometry(QtCore.QRect(10, 480, 271, 51))
        font = QtGui.QFont()
        font.setPointSize(10)
        self.textEdit.setFont(font)
        self.textEdit.setReadOnly(True)
        self.textEdit.setObjectName("textEdit")
        self.btnCheckPlateNumber = QtWidgets.QPushButton(self.plate_result)
        self.btnCheckPlateNumber.setGeometry(QtCore.QRect(290, 400, 181, 61))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.btnCheckPlateNumber.setFont(font)
        self.btnCheckPlateNumber.setStyleSheet("background-color: rgb(79, 79, 79);\n"
"color: rgb(255, 255, 255);")
        self.btnCheckPlateNumber.setIconSize(QtCore.QSize(30, 30))
        self.btnCheckPlateNumber.setObjectName("btnCheckPlateNumber")
        self.btnShowPlateRecords = QtWidgets.QPushButton(self.plate_result)
        self.btnShowPlateRecords.setGeometry(QtCore.QRect(290, 480, 181, 51))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.btnShowPlateRecords.setFont(font)
        self.btnShowPlateRecords.setStyleSheet("background-color: rgb(79, 79, 79);\n"
"color: rgb(255, 255, 255);")
        self.btnShowPlateRecords.setIconSize(QtCore.QSize(30, 30))
        self.btnShowPlateRecords.setObjectName("btnShowPlateRecords")
        self.vehicle_plate = QtWidgets.QGroupBox(self.centralwidget)
        self.vehicle_plate.setGeometry(QtCore.QRect(30, 10, 481, 551))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.vehicle_plate.setFont(font)
        self.vehicle_plate.setStyleSheet("background-color: rgb(235, 235, 235);")
        self.vehicle_plate.setObjectName("vehicle_plate")
        self.startPlateRecognition = QtWidgets.QPushButton(self.vehicle_plate)
        self.startPlateRecognition.setGeometry(QtCore.QRect(250, 430, 211, 51))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.startPlateRecognition.setFont(font)
        self.startPlateRecognition.setStyleSheet("background-color: rgb(79, 79, 79);\n"
"color: rgb(255, 255, 255);")
        self.startPlateRecognition.setIconSize(QtCore.QSize(30, 30))
        self.startPlateRecognition.setObjectName("startPlateRecognition")
        self.labelVehicle = QtWidgets.QLabel(self.vehicle_plate)
        self.labelVehicle.setGeometry(QtCore.QRect(10, 30, 461, 351))
        self.labelVehicle.setStyleSheet("background-color: rgb(200, 200, 200);")
        self.labelVehicle.setText("")
        self.labelVehicle.setAlignment(QtCore.Qt.AlignCenter)
        self.labelVehicle.setObjectName("labelVehicle")
        self.openImageFile = QtWidgets.QPushButton(self.vehicle_plate)
        self.openImageFile.setGeometry(QtCore.QRect(20, 430, 211, 51))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.openImageFile.setFont(font)
        self.openImageFile.setStyleSheet("background-color: rgb(79, 79, 79);\n"
"color: rgb(255, 255, 255);")
        self.openImageFile.setIconSize(QtCore.QSize(30, 30))
        self.openImageFile.setObjectName("openImageFile")
        self.vehicle_plate.raise_()
        self.plate_result.raise_()
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        self.openImageFile.clicked.connect(self.loadImage)
        self.startPlateRecognition.clicked.connect(self.showPlateRecognition)
        self.btnShowPlateRecords.clicked.connect(self.openWindow)
        self.btnCheckPlateNumber.clicked.connect(self.checkPlateNumber)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    imagePath = ""
    def loadImage(self):
        """ Bilgisayardan resim seçilmesini sağlayan fonksiyon.
        """
        self.filename = QFileDialog.getOpenFileName(filter="Image (*.png *.xmp *.jpg *.jpeg *.webp)")[0]
        self.image = cv2.imread(self.filename)
        self.setPhoto(self.image)
        global imagePath
        imagePath = self.image

    def setPhoto(self, image):
        """ Label bileşeninde resmin yeniden boyutlandırıp gösterilmesini sağlayan fonksiyon.
        """
        self.tmp = image
        image = imutils.resize(image, width=500)
        frame = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        image = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888)
        self.labelVehicle.setPixmap(QtGui.QPixmap.fromImage(image))

    # Plaka Okuma butonuna tıklandığında çalışan fonksiyon.
    def showPlateRecognition(self):
        self.writePlate(imagePath)
        self.showPlateImage(imagePath)

    # Plaka Okuma algoritmasında resmin sonuç label üzerinde gösterilmesini sağlayan fonksiyon.
    def showPlateImage(self, image):
        self.tmp = image
        txt, krp = plateRecognize(image)
        krp = imutils.resize(krp, width=350)
        frame = cv2.cvtColor(krp, cv2.COLOR_BGR2RGB)
        krp = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888)
        self.label_PlateResult.setPixmap(QtGui.QPixmap.fromImage(krp))

    # Plaka Okuma algoritmasında palaka metninin sonuç textBox'ın üzerinde gösterilmesini sağlayan fonksiyon.
    def writePlate(self, image):
        txt, krp = plateRecognize(image)
        self.textPlateResult.setText(txt)

    def openWindow(self):
        self.window = QtWidgets.QMainWindow()
        self.plateResult = self.textPlateResult.toPlainText()
        self.ui = Ui_SecondWindow(self.plateResult)
        self.ui.setupUi(self.window)
        self.window.show()

    def checkPlateNumber(self):
        plateNumber = self.textPlateResult.toPlainText()

        self.conn = sql.connect("Database/PlateRecognition.db")
        self.c = self.conn.cursor()
        self.c.execute("SELECT * FROM PlateNumberInformations WHERE plate_number = ?",(plateNumber,))
        data = self.c.fetchall()
        if len(data) == 0:
            self.textEdit.setText("Araç kayıtlı değil.")
        else:
            self.textEdit.setText("Araç kayıtlı.")
        self.conn.commit()
        self.c.close()
        self.conn.close()

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Plaka Tanıma Sistemi"))
        self.plate_result.setTitle(_translate("MainWindow", "Plaka Sonucu"))
        self.btnCheckPlateNumber.setText(_translate("MainWindow", "Plakayı Kontrol Et"))
        self.btnShowPlateRecords.setText(_translate("MainWindow", "Plaka Kayıtlarını Göster"))
        self.vehicle_plate.setTitle(_translate("MainWindow", "Araç/Plaka Görseli"))
        self.startPlateRecognition.setText(_translate("MainWindow", "Plaka Okuma Başlat"))
        self.openImageFile.setText(_translate("MainWindow", "Resim Dosyası Seç"))

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())


from django.shortcuts import render
from.models import friends

# Create your views here.
def showindex(request):
    id=request.GET.get("update_id")
    if id==None:
        res=friends.objects.all()
        return render(request,"index.html",{"res":res})
    else:
        id1=friends.objects.filter(entry=id).update()
        print(id1)
        return render(request,"index.html",{"id":id})

def displaydetails(request):
    entry= request.POST.get("eno")
    date= request.POST.get("date")
    amount= request.POST.get("amt")
    members= request.POST.getlist("t1")
    i=(", ".join(members))
    t=len(members)
    t1=int(amount)/t
    fr=friends(entry,date,amount,i,t1)
    fr.save()
    res=friends.objects.all()
    d1={"msg":"datasaved"}
    return render(request,"index.html",{"res":res})

def deletedetails(request):
    id=request.POST.get("delete_id")
    friends.objects.filter(entry=id).delete()
    res=friends.objects.all()
    return render(request,"index.html",{"res":res})


class Solution:
    def longestOnes(self, nums: List[int], k: int) -> int:
        start,end,maxOnes,noZeros=0,0,0,0
        for end in range(len(nums)):
            if nums[end]==0:
                noZeros+=1
            while noZeros >k:
                if nums[start]==0:
                    noZeros-=1
                start+=1
            maxOnes=max(maxOnes,end-start+1)

        return maxOnes


import pygame as pg

WIDTH = 700
HEIGHT = 500

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255,0)
BLUE = (0, 0, 255)

up_key = False
down_key = False
left_key = False
right_key = False

CAR_WIDTH = 30
CAR_HEIGHT = 30

car_x = WIDTH / 2
car_y = HEIGHT / 2

car_x_vel = 0
car_y_vel = 0

max_vel = 10

friction = 0.9
power = 4

pg.init()
pg.display.set_caption('Platformer 1')
screen = pg.display.set_mode([WIDTH, HEIGHT])

running = True
while running == True:

    for e in pg.event.get():
        if e.type == pg.QUIT:
            running = False

        elif e.type == pg.KEYDOWN:
            if e.key == pg.K_LEFT:
                left_key = True
            elif e.key == pg.K_RIGHT:
                right_key = True
            elif e.key == pg.K_DOWN:
                down_key = True
            elif e.key == pg.K_UP:
                up_key = True

        elif e.type == pg.KEYUP:
            if e.key == pg.K_LEFT:
                left_key = False
            elif e.key == pg.K_RIGHT:
                right_key = False
            elif e.key == pg.K_DOWN:
                down_key = False
            elif e.key == pg.K_UP:
                up_key = False

        # print(up_key, '\t', down_key, '\t', left_key, '\t', right_key)

    # After processing events:

    # Handle x velocity
    if left_key and not right_key:
        car_x_vel -= power
    elif not left_key and right_key:
        car_x_vel += power
    else:
        car_x_vel = car_x_vel * friction

    # Limit x velocity
    if car_x_vel > max_vel:
        car_x_vel = max_vel
    elif car_x_vel < -max_vel:
        car_x_vel = -max_vel

    # Handle y velocity
    if up_key and not down_key:
        car_y_vel -= power
    elif not up_key and down_key:
        car_y_vel += power
    else:
        car_y_vel = car_y_vel * friction

    # Limit y velocity
    if car_y_vel > max_vel:
        car_y_vel = max_vel
    elif car_y_vel < -max_vel:
        car_y_vel = -max_vel

    # Change position by velocity
    car_x += car_x_vel
    car_y += car_y_vel

    # Warp car over x axis
    if car_x + (CAR_WIDTH/2) < 0:
        car_x = WIDTH - car_x
    elif car_x - (CAR_WIDTH/2) > WIDTH:
        car_x = car_x - WIDTH - CAR_WIDTH

    # Warp car over y axis
    if car_y + (CAR_HEIGHT/2) < 0:
        car_y = HEIGHT - car_y
    elif car_y - (CAR_HEIGHT/2) > HEIGHT:
        car_y = car_y - HEIGHT - CAR_HEIGHT

    # Render car
    screen.fill(WHITE)
    pg.draw.rect(screen, BLUE, (int(car_x-(CAR_WIDTH/2)), int(car_y-(CAR_HEIGHT/2)), CAR_WIDTH, CAR_HEIGHT))
    pg.display.update()

pg.quit()


import audio_edit_utils
import vid_utils

# "C:\Program Files\VideoLAN\VLC\vlc.exe" --start-time=35 "C:\Users\Brandon\AppData\Roaming\I2P\i2psnark\Rick.and.Morty.S04E03.720p.WEBRip.x264-TBS[rarbg]\rick.and.morty.s04e03.720p.webrip.x264-tbs.mkv"
# "C:\Program Files\VideoLAN\VLC\vlc.exe" --start-time=150 "C:\\Users\\Brandon\\Documents\\Personal_Projects\\my_movie_tools_big_data\\transfer_dir\\Atonement_(720).m4v"

# import speech_recognition as sr
# from os import path
# from pydub import AudioSegment

#     if not isinstance(actual_result, dict) or len(actual_result.get("alternative", [])) == 0: raise UnknownValueError()
# speech_recognition.UnknownValueError

import time

# VID_FILE_PATH = "C:\\Users\\Brandon\\Documents\\Personal_Projects\\my_movie_tools_big_data\\dolbycanyon_outside.mkv"
VID_FILE_PATH = "C:\\Users\\Brandon\\Documents\\Personal_Projects\\my_movie_tools_big_data\\transfer_dir\\Atonement_(720).m4v"
# VID_FILE_PATH = "C:\\Users\\Brandon\\Documents\\Personal_Projects\\my_movie_tools_big_data\\Screen.mkv"

FULL_AUDIO_PATH   = '..\\my_movie_tools_big_data\\full_audio.wav'
CLIPED_AUDIO_PATH = '..\\my_movie_tools_big_data\\clipped_audio.wav'
AUDIO_CLIP_TIME = 10 # number of seconds to divide audio into to check for speech

# returns # of seconds after start_time before the first time someone speaks in the audio (.wav file)
# returns False if there is no speech in audio clip
def get_first_speech_time(full_audio_path, start_time):
    audio_duration = audio_edit_utils.get_audio_duration(full_audio_path)
    cur_time = start_time
    keep_looping = True

    while(keep_looping):
        # get end time for clipping audio
        end_time = cur_time + AUDIO_CLIP_TIME
        if end_time > audio_duration:
            end_time = audio_duration
            keep_looping = False

        # clip audio
        audio_edit_utils.clip_audio(full_audio_path, CLIPED_AUDIO_PATH, cur_time, end_time)

        # transcribe audio
        transcription = audio_edit_utils.transcribe_audio(CLIPED_AUDIO_PATH)

        # return time if speech was found in audio clip
        if transcription != False:
            return cur_time

        # set up for next loop if no speech was found in audio clip
        cur_time = end_time
    return False

# extract audio from vid
audio_edit_utils.get_audio_from_video(VID_FILE_PATH, FULL_AUDIO_PATH)

first_speech_time = get_first_speech_time(FULL_AUDIO_PATH, 0)

print(first_speech_time)

#
#
#
# # # convert mp3 file to wav
# sound = AudioSegment.from_mp3("C:\\Users\\Brandon\\Documents\\Personal_Projects\\my_movie_tools_big_data\\ste-021-dilan-herkunft.mp3")
# sound.export("transcript.wav", format="wav")
#
#
# # transcribe audio file
# AUDIO_FILE = "transcript.wav"
# # AUDIO_FILE = "C:\\Users\\Brandon\\Documents\\Personal_Projects\\my_movie_tools_big_data\\Screen_audio.wav"
# # AUDIO_FILE = "C:\\Users\\Brandon\\Documents\\Personal_Projects\\my_movie_tools_big_data\\youre-so-funny-1.wav"
# # AUDIO_FILE = "C:\\Users\\Brandon\\Documents\\Personal_Projects\\my_movie_tools_big_data\\file_example_WAV_1MG.wav"
#
# s = time.time()
#
#
# # use the audio file as the audio source
# r = sr.Recognizer()
# with sr.AudioFile(AUDIO_FILE) as source:
#         audio = r.record(source)  # read the entire audio file
#
#         print("Transcription: " + r.recognize_google(audio))
#
# print(time.time() - s)
#
# # ffmpeg -i "C:\Users\Brandon\Documents\Personal_Projects\my_movie_tools_big_data\Screen_audio.wav" -ss 10 -to 15 -c copy "C:\Users\Brandon\Documents\Personal_Projects\my_movie_tools_big_data\Screen_audio_10-15.wav"
# #
# #
# #
# # ffmpeg -i "C:\Users\Brandon\Documents\Personal_Projects\my_movie_tools_big_data\Screen_trimmed.mp4" -acodec pcm_s16le -ac 2 "C:\Users\Brandon\Documents\Personal_Projects\my_movie_tools_big_data\Screen_trimmed.wav"
# # # ffmpeg -i "C:\Users\Brandon\Documents\Personal_Projects\my_movie_tools_big_data\Screen.mkv" -acodec pcm_s16le -ac 2 "C:\Users\Brandon\Documents\Personal_Projects\my_movie_tools_big_data\Screen_audio.wav"
# # ffmpeg -i "C:\Users\Brandon\Documents\Personal_Projects\my_movie_tools_big_data\Screen_trimmed.wav" 2>&1 | grep Duration | sed 's/Duration: \(.*\), start/\1/g'
# # ffprobe -i "C:\Users\Brandon\Documents\Personal_Projects\my_movie_tools_big_data\Screen_trimmed.wav" -show_entries format=duration -v quiet -of csv="p=0"


# importing pycairo
import cairo
# creating a SVG surface
# here geek95 is file name & 700, 700 is dimension
with cairo.SVGSurface("geek95.svg", 700, 700) as surface:
	# creating a cairo context object for SVG surface
	# using Context method
	context = cairo.Context(surface)
	# move the context to x,y position
	context.move_to(50, 200)
	# Drawing Curve
	context.curve_to(150, 75, 225, 50, 350, 150)
	# setting color of the context
	context.set_source_rgb(1, 0, 0)
	# setting width of the context
	context.set_line_width(4)
	# stroke out the color and width property
	context.stroke()
# printing message when file is saved
print("File Saved")
# ---------------------------------------------------------------------------------------
import cv2
import numpy as np
def gammaCorrection(src, gamma):
    invGamma = 1 / gamma
    table = [((i / 255) ** invGamma) * 255 for i in range(256)]
    table = np.array(table, np.uint8)
    return cv2.LUT(src, table)
img = cv2.imread('image.jpg')
gammaImg = gammaCorrection(img, 2.2)
cv2.imshow('Original image', img)
cv2.imshow('Gamma corrected image', gammaImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
# -----------------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
from skimage import color, data, restoration
rng = np.random.default_rng()
astro = color.rgb2gray(data.astronaut())
from scipy.signal import convolve2d as conv2
psf = np.ones((5, 5)) / 25
astro = conv2(astro, psf, 'same')
astro += 0.1 * astro.std() * rng.standard_normal(astro.shape)
deconvolved, _ = restoration.unsupervised_wiener(astro, psf)
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(8, 5),
                       sharex=True, sharey=True)
plt.gray()
ax[0].imshow(astro, vmin=deconvolved.min(), vmax=deconvolved.max())
ax[0].axis('off')
ax[0].set_title('Data')
ax[1].imshow(deconvolved)
ax[1].axis('off')
ax[1].set_title('Self tuned restoration')
fig.tight_layout()
plt.show()
# ----------------------------------------------------------------------------------
import cv2
import numpy as np
# Read source image.
im_src = cv2.imread('image1.png')
# Four corners of the book in source image
pts_src = np.array([[141, 131], [480, 159], [493, 630], [64, 601]])
# Read destination image.
im_dst = cv2.imread('image.jpg')
# Four corners of the book in destination image.
pts_dst = np.array([[318, 256], [534, 372], [316, 670], [73, 473]])
# Calculate Homography
h, status = cv2.findHomography(pts_src, pts_dst)
# Warp source image to destination based on homography
im_out = cv2.warpPerspective(im_src, h, (im_dst.shape[1], im_dst.shape[0]))
# Display images
cv2.imshow("Source Image", im_src)
cv2.imshow("Destination Image", im_dst)
cv2.imshow("Warped Source Image", im_out)
cv2.waitKey(0)
#---------------------------------------------------------------------------
import cv2
import numpy as np
image = cv2.imread('image.jpg')
#Apply identity kernel
kernel1 = np.array([[0, 0, 0],
                    [0, 1, 0],
                    [0, 0, 0]])
# filter2D() function can be used to apply kernel to an image.
# Where ddepth is the desired depth of final image. ddepth is -1 if...
# ... depth is same as original or source image.
identity = cv2.filter2D(src=image, ddepth=-1, kernel=kernel1)
# We should get the same image
cv2.imshow('Original', image)
cv2.imshow('Identity', identity)
cv2.waitKey()
cv2.imwrite('identity.jpg', identity)
cv2.destroyAllWindows()
# ------------------------------------------------------------
import cv2
import numpy as np
image = cv2.imread('image.jpg')
#Apply blurring kernel
kernel2 = np.ones((5, 5), np.float32) / 25
img = cv2.filter2D(src=image, ddepth=-1, kernel=kernel2)
cv2.imshow('Original', image)
cv2.imshow('Kernel Blur', img)
cv2.waitKey()
cv2.imwrite('blur_kernel.jpg', img)
cv2.destroyAllWindows()


#!/usr/bin/env python
# coding=utf-8
import torch
import torchvision.models as models

#resnet169 = models.densenet169(pretrained=True).cuda()
inception_v3 = models.inception_v3(pretrained=True).cuda()
dummy_input = torch.randn(1, 3, 224, 224, device='cuda')
input_names = ['data']
output_names = ['outputs']

torch.onnx.export(inception_v3, dummy_input, f='inception_v3.onnx', verbose=True, input_names=input_names,
                                   output_names=output_names, opset_version=10)  # generate onnx model of 244M


import json
from falcon.status_codes import HTTP_404, HTTP_400
from marshmallow import ValidationError
from utils.HTTPError import HTTPError

class Serializer:
    """
    This middleware gives us a possibility to validate data from request body.

    It also allows to set a separate schema (validator) for every HTTP method.
    At the end, it sets serializer data in context so we can read the data in API
    endpoint (if needed). If the data is not correct, API returns HTTP 400 error
    with validation message returned by marshmallow.
    """

    def process_resource(self, req, _resp, resource, _params):
        body = json.load(req.bounded_stream)
        try:
            serializer = resource.serializers[req.method.lower()]
        except (AttributeError, IndexError, KeyError):
            raise HTTPError(status=HTTP_404)
        else:
            try:
                req.context["serializer"] = serializer().load(data=body)
            except ValidationError as err:
                raise HTTPError(error="Bad Request", status=HTTP_400, field_errors=err.messages)


import fileIO
import view

def menu(data):
    while True:
        answer = view.show_menu()
        if answer == 1:
            view.show_data(data)
        elif answer == 2:
            # fileIO.add_data(data)
            str_data = input('Enter your data delimited by tab> ')
            row = str_data.split('\t')
            data.append(row)
        elif answer == 3:
            str_del = input('Ente ID> ')
            for  i in range(len(data)):
                if data[i][0] == str_del:
                    del data[i]
        elif answer == 4:
            fileIO.write_data('data.csv', data)
        elif answer == 5:
            print('bye')
            break
        else:
            print('Wrong number')


"""
Merge, combine and mosaic
"""

def rsts_to_mosaic(inRasterS, o, api="grass", fformat='.tif', method=None):
    """
    Create Mosaic of Raster
    """

    if api == 'pygrass':
        """
        The GRASS program r.patch allows the user to build a new raster map the size
        and resolution of the current region by assigning known data values from
        input raster maps to the cells in this region. This is done by filling in
        "no data" cells, those that do not yet contain data, contain NULL data, or,
        optionally contain 0 data, with the data from the first input map.
        Once this is done the remaining holes are filled in by the next input map,
        and so on. This program is useful for making a composite raster map layer
        from two or more adjacent map layers, for filling in "holes" in a raster map
        layer's data (e.g., in digital elevation data), or for updating an older map
        layer with more recent data. The current geographic region definition and
        mask settings are respected.
        The first name listed in the string input=name,name,name, ... is the name of
        the first map whose data values will be used to fill in "no data" cells in
        the current region. The second through last input name maps will be used,
        in order, to supply data values for for the remaining "no data" cells.
        """

        from grass.pygrass.modules import Module

        m = Module(
            "r.patch", input=inRasterS, output=o,
            overwrite=True, run_=False, quiet=True
        )

        m()

    elif api == 'grass':
        from glass.pys import execmd

        rcmd = execmd((
            f"r.patch input={','.join(inRasterS)} output={o} "
            "--overwrite --quiet"
        ))

    elif api == 'rasterio':
        import rasterio
        from rasterio.merge import merge
        from glass.prop.df  import drv_name
        from glass.prop.prj import get_epsg, epsg_to_wkt

        if type(inRasterS) != list:
            from glass.pys.oss import lst_ff

            rsts = lst_ff(inRasterS, file_format=fformat)
        else: rsts = inRasterS

        methods = ['first', 'last', 'min', 'max']

        method = 'first' if not method or \
            method not in methods else method

        srcs = [rasterio.open(r) for r in rsts]

        mosaic, out_trans = merge(srcs, method=method)

        out_meta = srcs[0].meta.copy()

        out_meta.update({
            "driver"    : drv_name(o),
            "height"    : mosaic.shape[1],
            "width"     : mosaic.shape[2],
            "transform" : out_trans,
            "count"     : 1,
            "crs"       : epsg_to_wkt(get_epsg(rsts[0])),
            "compress"  : 'lzw'
        })

        with rasterio.open(o, "w", **out_meta) as dest:
            dest.write(mosaic)

    else:
        raise ValueError(f'api {api} is not available')

    return o

def rseries(lst, out, meth, as_cmd=None):
    """
    r.series - Makes each output cell value a function of the values
    assigned to the corresponding cells in the input raster map layers.

    Method Options:
    average, count, median, mode, minimum, min_raster, maximum,
    max_raster, stddev, range, sum, variance, diversity,
    slope, offset, detcoeff, tvalue, quart1, quart3, perc90,
    quantile, skewness, kurtosis
    """

    if type(lst) != list:
        raise ValueError("lst must be a list of rasters")

    if not as_cmd:
        from grass.pygrass.modules import Module

        serie = Module(
            'r.series', input=lst, output=out, method=meth,
            overwrite=True, quiet=True, run_=False
        )

        serie()

    else:
        from glass.pys import execmd

        ilst = ",".join(lst)

        rcmd = execmd((
            f"r.series input={ilst} output={out} "
            f"method={meth} "
            "--overwrite --quiet"
        ))

    return out

def fullgrass_rseries(ifolder, refrst, method, orst):
    """
    R. Series using grass
    """

    import os

    from glass.wenv.grs import run_grass
    from glass.pys.tm import now_as_str
    from glass.pys.oss import lst_ff, fprop

    loc = now_as_str()

    gbase = run_grass(ifolder, location=loc, srs=refrst)

    import grass.script.setup as gsetup

    gsetup.init(gbase, ifolder, loc, "PERMANENT")

    from glass.it.rst import rst_to_grs, grs_to_rst

    rsts = [rst_to_grs(
        r, fprop(r, 'fn')
    ) for r in lst_ff(ifolder, file_format='.tif')]

    prst = rseries(rsts, fprop(orst, 'fn'), method, as_cmd=True)

    grs_to_rst(prst, orst)

    return orst

def bnds_to_mosaic(bands, outdata, ref_raster, loc=None):
    """
    Satellite image To mosaic

    bands = {
        'bnd_2' : [path_to_file, path_to_file],
        'bnd_3' : [path_to_file, path_to_file],
        'bnd_4' : [path_to_file, path_to_file],
    }
    """

    """
    Start GRASS GIS Session
    """

    import os
    from glass.pys.oss  import fprop
    from glass.prop.prj import rst_epsg
    from glass.wenv.grs import run_grass

    # Get EPSG from refRaster
    epsg = rst_epsg(ref_raster, returnIsProj=None)

    LOC = loc if loc else 'gr_loc'
    grass_base = run_grass(
        outdata, grassBIN='grass78',
        location=LOC, srs=epsg
    )

    import grass.script.setup as gsetup

    gsetup.init(grass_base, outdata, LOC, 'PERMANENT')

    # ************************************************************************ #
    # GRASS MODULES #
    # ************************************************************************ #
    from glass.it.rst import rst_to_grs, grs_to_rst
    from glass.wenv.grs import rst_to_region
    # ************************************************************************ #
    # SET GRASS GIS LOCATION EXTENT #
    # ************************************************************************ #
    extRst = rst_to_grs(ref_raster, 'extent_raster')
    rst_to_region(extRst)
    # ************************************************************************ #
    # SEND DATA TO GRASS GIS #
    # ************************************************************************ #
    grs_bnds = {}

    for bnd in bands:
        l= []
        for b in bands[bnd]:
            bb = rst_to_grs(b, fprop(b, 'fn'))
            l.append(bb)

        grs_bnds[bnd] = l
    # ************************************************************************ #
    # PATCH bands and export #
    # ************************************************************************ #
    for bnd in grs_bnds:
        mosaic_band = rseries(grs_bnds[bnd], bnd, 'maximum')

        grs_bnds[bnd] = grs_to_rst(mosaic_band, os.path.join(
            outdata, mosaic_band + '.tif'
        ), as_cmd=True)

    return grs_bnds


import logging

from edera.exceptions import ExcusableError
from edera.exceptions import ExcusableWorkflowExecutionError
from edera.exceptions import WorkflowExecutionError
from edera.queue import Queue
from edera.routine import deferrable
from edera.routine import routine
from edera.workflow.executor import WorkflowExecutor

class BasicWorkflowExecutor(WorkflowExecutor):
    """
    A basic workflow executor.

    Expects tasks to be ranked in advance.
    Runs tasks in the current thread one by one, handles exceptions, and performs logging.

    This executor is interruptible.

    See also:
        $TaskRanker
    """

    @routine
    def execute(self, workflow):
        queue = Queue(workflow)
        stopped_tasks = []
        failed_tasks = []
        while queue:
            task = queue.pick()
            if task.phony:
                queue.accept()
                continue
            try:
                logging.getLogger(__name__).debug("Picked task %r", task)
                if task.target is not None:
                    completed = yield deferrable(task.target.check).defer()
                    if completed:
                        queue.accept()
                        continue
                logging.getLogger(__name__).info("Running task %r", task)
                yield deferrable(task.execute).defer()
            except ExcusableError as error:
                logging.getLogger(__name__).info("Task %r stopped: %s", task, error)
                stopped_tasks.append(task)
                queue.discard()
            except Exception:
                logging.getLogger(__name__).exception("Task %r failed:", task)
                failed_tasks.append(task)
                queue.discard()
            else:
                logging.getLogger(__name__).info("Task %r completed", task)
                queue.accept()
        if failed_tasks:
            raise WorkflowExecutionError(failed_tasks)
        if stopped_tasks:
            raise ExcusableWorkflowExecutionError(stopped_tasks)


#!/usr/bin/env python
"""
author: Jun Ding
date: 2020-07-06

function: plot the expression of input gene

copy and modification of this code is allowed for academic purposes.
Please don NOT remove this author statement under any condition.
"""

import sys,os,pdb,argparse
import anndata
import scanpy as sc

def plotGene(exFn,gene):
	prRes=anndata.read_h5ad(exFn)
	if gene in prRes.var.index:
		sc.pl.umap(prRes,color=[gene])
	else:
		print("Error! please check your input gene ID, it must be the same as in your expression file")
		print("Also, the missing gene could be caused by the dispersion based gene filtering by the prerun program")

def main():
	parser=argparse.ArgumentParser(description="scdiff2 plotGene")
	parser.add_argument('-i','--input',required=True,help='input h5ad prerun result')
	parser.add_argument('-g','--gene',required=True, help='gene name you want to explore, must be the same ID as in your original input expression file')

	args = parser.parse_args()
	exFn=args.input
	gene=args.gene
	plotGene(exFn,gene)

if __name__=="__main__":
	main()