'''Written by Cael Shoop.'''

import os
import random
import discord
import timestamps
import datetime
from time import sleep
from typing import Literal
from dotenv import load_dotenv
from discord import app_commands, Interaction, Intents, Client, ButtonStyle, EventStatus, EntityType, TextChannel, VoiceChannel, ScheduledEvent, Guild
from discord.ui import View, Button
from discord.ext import tasks

load_dotenv()

def get_time():
    ct = str(datetime.datetime.now())
    hour = int(ct[11:13])
    minute = int(ct[14:16])
    return hour, minute

def get_datetime_from_label(label: str):
    partitioned_time = label.partition(':')
    hour = int(partitioned_time[0])
    minute = int(partitioned_time[2])
    time = datetime.datetime.now().astimezone().replace(hour=hour, minute=minute, second=0, microsecond=0)
    if time.hour < 2 and datetime.datetime.now().astimezone().hour > 6:
        time += datetime.timedelta(days=1)
    return time

def get_log_time():
    time = datetime.datetime.now().astimezone()
    output = ''
    if time.hour < 10:
        output += '0'
    output += f'{time.hour}:'
    if time.minute < 10:
        output += '0'
    output += f'{time.minute}:'
    if time.second < 10:
        output += '0'
    output += f'{time.second}'
    return output

def main():
    class Participant():
        class Availability:
            def __init__(self):
                self.thirteen_hundred            = False
                self.thirteen_hundred_thirty     = False
                self.fourteen_hundred            = False
                self.fourteen_hundred_thirty     = False
                self.fifteen_hundred             = False
                self.fifteen_hundred_thirty      = False
                self.sixteen_hundred             = False
                self.sixteen_hundred_thirty      = False
                self.seventeen_hundred           = False
                self.seventeen_hundred_thirty    = False
                self.eighteen_hundred            = False
                self.eighteen_hundred_thirty     = False
                self.nineteen_hundred            = False
                self.nineteen_hundred_thirty     = False
                self.twenty_hundred              = False
                self.twenty_hundred_thirty       = False
                self.twenty_one_hundred          = False
                self.twenty_one_hundred_thirty   = False
                self.twenty_two_hundred          = False
                self.twenty_two_hundred_thirty   = False
                self.twenty_three_hundred        = False
                self.twenty_three_hundred_thirty = False
                self.zero_hundred                = False
                self.zero_hundred_thirty         = False
                self.one_hundred                 = False
                self.one_hundred_thirty          = False

        def __init__(self, member):
            self.member = member
            self.availability = Participant.Availability()
            self.answered = False
            self.subscribed = True
            self.msg_lock = False
            self.weekly = False

        def toggle_availability(self, label):
            if label == timestamps.thirteen_hundred_hours:                 self.availability.thirteen_hundred               = not self.availability.thirteen_hundred
            elif label == timestamps.thirteen_hundred_thirty_hours:        self.availability.thirteen_hundred_thirty        = not self.availability.thirteen_hundred_thirty
            elif label == timestamps.fourteen_hundred_hours:               self.availability.fourteen_hundred               = not self.availability.fourteen_hundred
            elif label == timestamps.fourteen_hundred_thirty_hours:        self.availability.fourteen_hundred_thirty        = not self.availability.fourteen_hundred_thirty
            elif label == timestamps.fifteen_hundred_hours:                self.availability.fifteen_hundred                = not self.availability.fifteen_hundred
            elif label == timestamps.fifteen_hundred_thirty_hours:         self.availability.fifteen_hundred_thirty         = not self.availability.fifteen_hundred_thirty
            elif label == timestamps.sixteen_hundred_hours:                self.availability.sixteen_hundred                = not self.availability.sixteen_hundred
            elif label == timestamps.sixteen_hundred_thirty_hours:         self.availability.sixteen_hundred_thirty         = not self.availability.sixteen_hundred_thirty
            elif label == timestamps.seventeen_hundred_hours:              self.availability.seventeen_hundred              = not self.availability.seventeen_hundred
            elif label == timestamps.seventeen_hundred_thirty_hours:       self.availability.seventeen_hundred_thirty       = not self.availability.seventeen_hundred_thirty
            elif label == timestamps.eighteen_hundred_hours:               self.availability.eighteen_hundred               = not self.availability.eighteen_hundred
            elif label == timestamps.eighteen_hundred_thirty_hours:        self.availability.eighteen_hundred_thirty        = not self.availability.eighteen_hundred_thirty
            elif label == timestamps.nineteen_hundred_hours:               self.availability.nineteen_hundred               = not self.availability.nineteen_hundred
            elif label == timestamps.nineteen_hundred_thirty_hours:        self.availability.nineteen_hundred_thirty        = not self.availability.nineteen_hundred_thirty
            elif label == timestamps.twenty_hundred_hours:                 self.availability.twenty_hundred                 = not self.availability.twenty_hundred
            elif label == timestamps.twenty_hundred_thirty_hours:          self.availability.twenty_hundred_thirty          = not self.availability.twenty_hundred_thirty
            elif label == timestamps.twenty_one_hundred_hours:             self.availability.twenty_one_hundred             = not self.availability.twenty_one_hundred
            elif label == timestamps.twenty_one_hundred_thirty_hours:      self.availability.twenty_one_hundred_thirty      = not self.availability.twenty_one_hundred_thirty
            elif label == timestamps.twenty_two_hundred_hours:             self.availability.twenty_two_hundred             = not self.availability.twenty_two_hundred
            elif label == timestamps.twenty_two_hundred_thirty_hours:      self.availability.twenty_two_hundred_thirty      = not self.availability.twenty_two_hundred_thirty
            elif label == timestamps.twenty_three_hundred_hours:           self.availability.twenty_three_hundred           = not self.availability.twenty_three_hundred
            elif label == timestamps.twenty_three_hundred_thirty_hours:    self.availability.twenty_three_hundred_thirty    = not self.availability.twenty_three_hundred_thirty
            elif label == timestamps.zero_hundred_hours:                   self.availability.zero_hundred                   = not self.availability.zero_hundred
            elif label == timestamps.zero_hundred_thirty_hours:            self.availability.zero_hundred_thirty            = not self.availability.zero_hundred_thirty
            elif label == timestamps.one_hundred_hours:                    self.availability.one_hundred                    = not self.availability.one_hundred
            elif label == timestamps.one_hundred_thirty_hours:             self.availability.one_hundred_thirty             = not self.availability.one_hundred_thirty

        def is_available(self, label):
            if label == timestamps.thirteen_hundred_hours:                 return self.availability.thirteen_hundred
            elif label == timestamps.thirteen_hundred_thirty_hours:        return self.availability.thirteen_hundred_thirty
            elif label == timestamps.fourteen_hundred_hours:               return self.availability.fourteen_hundred
            elif label == timestamps.fourteen_hundred_thirty_hours:        return self.availability.fourteen_hundred_thirty
            elif label == timestamps.fifteen_hundred_hours:                return self.availability.fifteen_hundred
            elif label == timestamps.fifteen_hundred_thirty_hours:         return self.availability.fifteen_hundred_thirty
            elif label == timestamps.sixteen_hundred_hours:                return self.availability.sixteen_hundred
            elif label == timestamps.sixteen_hundred_thirty_hours:         return self.availability.sixteen_hundred_thirty
            elif label == timestamps.seventeen_hundred_hours:              return self.availability.seventeen_hundred
            elif label == timestamps.seventeen_hundred_thirty_hours:       return self.availability.seventeen_hundred_thirty
            elif label == timestamps.eighteen_hundred_hours:               return self.availability.eighteen_hundred
            elif label == timestamps.eighteen_hundred_thirty_hours:        return self.availability.eighteen_hundred_thirty
            elif label == timestamps.nineteen_hundred_hours:               return self.availability.nineteen_hundred
            elif label == timestamps.nineteen_hundred_thirty_hours:        return self.availability.nineteen_hundred_thirty
            elif label == timestamps.twenty_hundred_hours:                 return self.availability.twenty_hundred
            elif label == timestamps.twenty_hundred_thirty_hours:          return self.availability.twenty_hundred_thirty
            elif label == timestamps.twenty_one_hundred_hours:             return self.availability.twenty_one_hundred
            elif label == timestamps.twenty_one_hundred_thirty_hours:      return self.availability.twenty_one_hundred_thirty
            elif label == timestamps.twenty_two_hundred_hours:             return self.availability.twenty_two_hundred
            elif label == timestamps.twenty_two_hundred_thirty_hours:      return self.availability.twenty_two_hundred_thirty
            elif label == timestamps.twenty_three_hundred_hours:           return self.availability.twenty_three_hundred
            elif label == timestamps.twenty_three_hundred_thirty_hours:    return self.availability.twenty_three_hundred_thirty
            elif label == timestamps.zero_hundred_hours:                   return self.availability.zero_hundred
            elif label == timestamps.zero_hundred_thirty_hours:            return self.availability.zero_hundred_thirty
            elif label == timestamps.one_hundred_hours:                    return self.availability.one_hundred
            elif label == timestamps.one_hundred_thirty_hours:             return self.availability.one_hundred_thirty

    class Event:
        def __init__(self, name: str, entity_type: EntityType, voice_channel: VoiceChannel, participants: list, guild: Guild, text_channel: TextChannel, duration: int): #, weekly: bool
            self.name = name
            self.guild = guild
            self.entity_type = entity_type
            self.text_channel = text_channel
            self.voice_channel = voice_channel
            self.participants = participants
            #self.requested_weekly = weekly
            self.buttons = []
            self.reason = ''
            self.nudges = ['respond', 'I showed you my event, pls respond', 'I\'m waiting for you', 'my brother in christ, click button(s)', 'your availability. hand it over', 'nudge', 'plz respond 🥺', 'I\'m literally crying rn omg, I need your availability', 'click button(s)', 'HURRY HURRY HURRY!', 'I want to create event: you sleep', 'I **NEED** AVAILABILITY!']
            self.nudge_unresponded_timer = 30
            self.ready_to_create = False
            self.created = False
            self.scheduled_event: ScheduledEvent
            self.changed = False
            self.start_time = None
            self.end_time = None
            self.duration = duration
            self.valid = True

        def check_times(self):
            # Find first available shared time block and configure start/end times
            print(f'{get_log_time()}> {self.name}> Comparing availabilities for {self.name}')
            shared_time_slot = ''
            for time_slot in timestamps.all_timestamps:
                check_time_obj = get_datetime_from_label(time_slot)
                if datetime.datetime.now().astimezone() > check_time_obj - datetime.timedelta(minutes=5):
                    continue
                skip_time_slot = False
                for event in client.events:
                    if self != event and check_time_obj == event.start_time and (self.shares_participants(event) or self.voice_channel == event.voice_channel):
                        print(f'{get_log_time()}> {self.name}> Skipping {time_slot} due to event {event.name} already existing at that time with shared participant(s) or shared location')
                        skip_time_slot = True
                        break
                if not skip_time_slot:
                    shared_availability = True
                    for participant in self.participants:
                        shared_availability = shared_availability and participant.is_available(time_slot)
                    if shared_availability:
                        shared_time_slot = time_slot
                        break
            if shared_time_slot == '':
                print(f'{get_log_time()}> {self.name}> Unable to find common availability')
                self.valid = False
                return
            self.start_time = get_datetime_from_label(shared_time_slot)
            self.end_time = self.start_time + datetime.timedelta(minutes=self.duration)
            print(f'{get_log_time()}> {self.name}> Ready to create event on {self.start_time.month}/{self.start_time.day}/{self.start_time.year} at {self.start_time.hour}:{self.start_time.minute}')
            self.ready_to_create = True

        def shares_participants(self, event):
            for self_participant in self.participants:
                for other_participant in event.participants:
                    if self_participant.member == other_participant.member:
                        return True
            return False

        def has_everyone_answered(self):
            everyoneAnswered = True
            for participant in self.participants:
                if participant.subscribed:
                    everyoneAnswered = everyoneAnswered and participant.answered
            return everyoneAnswered

        async def dm_all_participants(self, interaction: Interaction, duration: int = 30, reschedule: bool = False):
            curHour, curMinute = get_time()
            curTimeObj = datetime.datetime(2000, 1, 1, curHour, curMinute).replace(second=0, microsecond=0)
            for participant in self.participants:
                buttonFlag = False
                while (participant.msg_lock):
                    sleep(3)
                participant.msg_lock = True
                if reschedule:
                    await participant.member.send(f'⬇⬇⬇⬇⬇ __**{self.name}**__ ⬇⬇⬇⬇⬇\n**Loading buttons, please wait.**\n{interaction.user.name} wants to **reschedule** {self.name}.\nThe event will last {duration} minutes.')
                else:
                    await participant.member.send(f'⬇⬇⬇⬇⬇ __**{self.name}**__ ⬇⬇⬇⬇⬇\n**Loading buttons, please wait.**\n{interaction.user.name} wants to create an event called {self.name}.\nThe event will last {duration} minutes.')
                print(f'{get_log_time()}> {self.name}> Sending buttons to {participant.member.name}')

                for button_label in timestamps.all_timestamps:
                    labelTime = button_label.partition(':')
                    labelHour = int(labelTime[0])
                    labelMinute = int(labelTime[2])

                    if not buttonFlag:
                        labelTimeObj = datetime.datetime(2000, 1, 1, labelHour, labelMinute).replace(second=0, microsecond=0)
                        if labelHour < 2:
                            labelTimeObj += datetime.timedelta(days=1)
                        buttonFlag = curTimeObj + datetime.timedelta(minutes=5) < labelTimeObj

                    if buttonFlag:
                        await participant.member.send(view=TimeButton(label=button_label, participant=participant, event=self))

                await participant.member.send(view=OtherButtons(participant=participant, event=self))
                await participant.member.send(f'Select **all** of the 30 minute blocks you could be available to attend {self.name}!\n"None" will stop the event from being created, so click "Unsubscribe" if you want the event to occur with or without you.\n'
                                            f'The event will be either created or cancelled 1-2 minutes after the last person responds, which renders the buttons useless.\n'f'⬆⬆⬆⬆⬆ __**{self.name}**__ ⬆⬆⬆⬆⬆')
                participant.msg_lock = False
            print(f'{get_log_time()}> {self.name}> Done DMing participants')

        def nudge_timer(self):
            self.nudge_unresponded_timer -= 1
            if self.nudge_unresponded_timer == 0:
                self.nudge_unresponded_timer = 30
                return True
            return False

        async def nudge_unresponded_participants(self):
            for participant in self.participants:
                if participant.answered:
                    await participant.member.send(random.choice(self.nudges))
                    print(f'{get_log_time()}> {self.name}> Nudged {participant.member.name}')

    class TimeButton(View):
        def __init__(self, label: str, participant: Participant, event: Event):
            super().__init__(timeout=None)
            self.label = label
            self.participant = participant
            self.event = event
            self.add_button()

        def add_button(self):
            button = Button(label=self.label + ' EST', style=ButtonStyle.red)
            async def button_callback(interaction: Interaction):
                self.event.changed = True
                self.event.ready_to_create = False
                self.participant.answered = True
                self.participant.toggle_availability(self.label)
                if self.participant.is_available(self.label):
                    button.style = ButtonStyle.green
                else:
                    button.style = ButtonStyle.red
                await interaction.response.edit_message(view=self)
                print(f'{get_log_time()}> {self.event.name}> {self.participant.member.name} toggled availability to {self.participant.is_available(self.label)} at {self.label}')

            button.callback = button_callback
            self.add_item(button)

    class OtherButtons(View):
        def __init__(self, participant: Participant, event: Event):
            super().__init__(timeout=None)
            self.all_label = "All"
            self.none_label = "None"
            self.unsub_label = "Unsubscribe"
            self.weekly_label = "Can Attend Weekly"
            self.participant = participant
            self.event = event
            self.add_all_button()
            self.add_none_button()
            self.add_unsub_button()
            #if self.event.requested_weekly:
            #    self.add_weekly_button()

        def add_all_button(self):
            button = Button(label=self.all_label, style=ButtonStyle.blurple)
            async def all_button_callback(interaction: Interaction):
                self.event.changed = True
                self.event.ready_to_create = False
                self.participant.answered = True
                if button.style == ButtonStyle.blurple:
                    button.style = ButtonStyle.green
                    for time_slot in timestamps.all_timestamps:
                        if not self.participant.is_available(time_slot):
                            self.participant.toggle_availability(time_slot)
                    print(f'{get_log_time()}> {self.event.name}> {self.participant.member.name} selected full availability')
                else:
                    button.style = ButtonStyle.blurple
                    print(f'{get_log_time()}> {self.event.name}> {self.participant.member.name} deselected full availability')
                await interaction.response.edit_message(view=self)

            button.callback = all_button_callback
            self.add_item(button)

        def add_none_button(self):
            button = Button(label=self.none_label, style=ButtonStyle.blurple)
            async def none_button_callback(interaction: Interaction):
                self.event.changed = True
                self.event.ready_to_create = False
                self.participant.answered = True
                if button.style == ButtonStyle.blurple:
                    button.style = ButtonStyle.gray
                    for time_slot in timestamps.all_timestamps:
                        if self.participant.toggle_availability(time_slot):
                            self.participant.toggle_availability(time_slot)
                    self.event.reason += f'{self.participant.member.name} has no availability. '
                    self.event.valid = False
                    print(f'{get_log_time()}> {self.event.name}> {self.participant.member.name} selected no availability')
                else:
                    button.style = ButtonStyle.blurple
                    self.event.reason.replace(f'{self.participant.member.name} has no availability. ', '')
                    self.event.valid = True
                    print(f'{get_log_time()}> {self.event.name}> {self.participant.member.name} deselected no availability')
                await interaction.response.edit_message(view=self)

            button.callback = none_button_callback
            self.add_item(button)

        def add_unsub_button(self):
            button = Button(label=self.unsub_label, style=ButtonStyle.blurple)
            async def unsub_button_callback(interaction: Interaction):
                self.event.changed = True
                self.event.ready_to_create = False
                self.participant.answered = True
                self.participant.subscribed = not self.participant.subscribed
                if button.style == ButtonStyle.blurple:
                    button.style = ButtonStyle.gray
                    print(f'{get_log_time()}> {self.event.name}> {self.participant.member.name} unsubscribed')
                else:
                    button.style = ButtonStyle.blurple
                    print(f'{get_log_time()}> {self.event.name}> {self.participant.member.name} resubscribed')
                await interaction.response.edit_message(view=self)

            button.callback = unsub_button_callback
            self.add_item(button)

        # def add_weekly_button(self):
        #     button = Button(label=self.weekly_label, style=ButtonStyle.gray)
        #     async def weekly_button_callback(interaction: Interaction):
        #         self.event.changed = True
        #         self.event.ready_to_create = False
        #         if button.style == ButtonStyle.gray:
        #             button.style = ButtonStyle.green
        #             self.participant.weekly = True
        #             print(f'{get_log_time()}> {self.event.name}> {self.participant.member.name} can attend weekly')
        #         else:
        #             button.style = ButtonStyle.gray
        #             self.participant.weekly = False
        #             print(f'{get_log_time()}> {self.event.name}> {self.participant.member.name} cannot attend weekly')

        #     button.callback = weekly_button_callback
        #     self.add_item(button)

    class SchedulerClient(Client):
        FILENAME = 'info.json'

        def __init__(self, intents):
            super(SchedulerClient, self).__init__(intents=intents)
            self.tree = app_commands.CommandTree(self)
            self.events = []
            self.scheduled_events = []

        async def parse_scheduled_events(self):
            # track events that we find an existing scheduled event for
            touched_events = {}
            for event in self.events:
                touched_events[event] = False
            for scheduled_event in self.scheduled_events:
                if scheduled_event.status == EventStatus.scheduled or scheduled_event.status == EventStatus.active:
                    found = False
                    for event in self.events:
                        if not event.created:
                            continue
                        if scheduled_event.id == event.scheduled_event.id:
                            found = True
                            touched_events[event] = True
                            event.name = scheduled_event.name
                            event.created = True
                            event.start_time = scheduled_event.start_time.replace(second=0, microsecond=0)
                            event.end_time = scheduled_event.end_time
                            if scheduled_event.entity_type == EntityType.external:
                                location = scheduled_event.location
                            else:
                                location = client.get_channel(scheduled_event.channel_id)
                            event.voice_channel = location
                            participants = []
                            async for user in scheduled_event.users():
                                participants.append(Participant(user))
                            event.participants = participants
                            break
                    # create event in memory to match existing scheduled event
                    if not found:
                        participants = []
                        async for user in scheduled_event.users():
                            participants.append(Participant(user))
                        if scheduled_event.end_time:
                            time_difference = scheduled_event.end_time.replace(second=0, microsecond=0) - scheduled_event.start_time.replace(second=0, microsecond=0)
                            duration = int(time_difference.total_seconds() / 60)
                        else:
                            duration = 30
                        if scheduled_event.entity_type == EntityType.external:
                            location = scheduled_event.location
                        else:
                            location = client.get_channel(scheduled_event.channel_id)
                        event = Event(scheduled_event.name, scheduled_event.entity_type, location, participants, scheduled_event.guild, scheduled_event.guild.text_channels[0], duration)
                        event.created = True
                        event.start_time = scheduled_event.start_time.replace(second=0, microsecond=0)
                        event.end_time = event.start_time.replace(second=0, microsecond=0) + datetime.timedelta(minutes=duration)
                        event.voice_channel = location
                        event.scheduled_event = scheduled_event
                        self.events.append(event)
                        print(f'{get_log_time()}> Found event {scheduled_event.name} and added to memory')
                        for event in self.events:
                            print(f'{get_log_time()}> {event.name}> participants:')
                            for participant in participants:
                                print(f'{get_log_time()}> {event.name}> \t{participant.member.name}')
            # if a memory event is marked as created but doesn't have a scheduled event, delete it
            for event in touched_events:
                if not touched_events[event] and event.created and not event.scheduled_event:
                    self.events.remove(event)
                    print(f'{get_log_time()}> Did not find event {event.name} and removed from memory')

        async def setup_hook(self):
            await self.tree.sync()

    discord_token = os.getenv('DISCORD_TOKEN')
    client = SchedulerClient(intents=Intents.all())

    @client.event
    async def on_ready():
        print(f'{get_log_time()}> {client.user} has connected to Discord!')
        for guild in client.guilds:
            for scheduled_event in guild.scheduled_events:
                if scheduled_event.start_time < datetime.datetime.now().astimezone() + datetime.timedelta(hours=13):
                    client.scheduled_events.append(scheduled_event)
        await client.parse_scheduled_events()
        if not create_guild_event.is_running():
            create_guild_event.start()

    @client.tree.command(name='schedule', description='Create a scheduling event.')
    @app_commands.describe(event_name='Name for the event.')
    @app_commands.describe(voice_channel="Voice channel for the event.")
    @app_commands.describe(duration="Event duration in minutes (30 minutes default).")
    #@app_commands.describe(weekly="Whether you want this to be a weekly reoccuring event.")
    async def schedule_command(interaction: Interaction, event_name: str, voice_channel: discord.VoiceChannel, duration: int = 30): #, weekly: bool = False
        # Put participants into a list
        participants = []
        print(f'{get_log_time()}> {event_name}> Received event request from {interaction.user.name}')
        for member in interaction.channel.members:
            if not member.bot:
                participant = Participant(member)
                participants.append(participant)

        curHour, curMinute = get_time()
        if curHour == 1 and curMinute >= 25 and curHour < 7:
            await interaction.response.send_message(f'It\'s late, you should go to bed. Try again later today.')
            return

        # Make event object
        event = Event(event_name, EntityType.voice, voice_channel, participants, interaction.guild, interaction.channel, duration) #, weekly
        client.events.append(event)
        await interaction.response.send_message(f'{interaction.user.mention} wants to create an event called {event.name}. Check your DMs to share your availability!')

        await event.dm_all_participants(interaction, duration)

    @client.tree.command(name='reschedule', description='Reschedule an existing scheduled event.')
    @app_commands.describe(event_name='Type the name of the event you want to reschedule.')
    @app_commands.describe(duration='Event duration in minutes (default 30 minutes).')
    async def reschedule_command(interaction: Interaction, event_name: str, duration: int = 30):
        for guild in client.guilds:
            for scheduled_event in guild.scheduled_events:
                if scheduled_event.start_time < datetime.datetime.now().astimezone() + datetime.timedelta(hours=13):
                    client.scheduled_events.append(scheduled_event)
        await client.parse_scheduled_events()
        event_name = event_name.lower()
        for event in client.events:
            if event_name == event.name.lower():
                print(f'{get_log_time()}> {event.name}> {interaction.user.name} requested reschedule')
                if event.created:
                    new_event = Event(event.name, event.entity_type, event.voice_channel, event.participants, event.guild, interaction.channel, duration) #, weekly
                    await event.scheduled_event.cancel()
                    client.events.remove(event)
                    client.events.append(new_event)
                    await interaction.response.send_message(f'{interaction.user.mention} wants to reschedule {new_event.name}. Check your DMs to share your availability!')
                    await new_event.dm_all_participants(interaction, duration, reschedule=True)
                else:
                    await interaction.response.send_message(f'{event.name} has not been created yet. Your buttons will work until it is created or cancelled.')
                return
        await interaction.response.send_message(f'Could not find event {event_name}.\n\n__Existing events:__\n{", ".join([event.name for event in client.events])}')

    @tasks.loop(minutes=1)
    async def create_guild_event():
        curTime = datetime.datetime.now().astimezone().replace(second=0, microsecond=0)
        for event in client.events.copy():
            if not event.valid:
                await event.text_channel.send('No shared availability has been found. The event scheduling has been cancelled.\n' + event.reason)
                client.events.remove(event)
                print(f'{get_log_time()}> {event.name}> Event invalid, removed event from memory')
            elif event.created and get_datetime_from_label('01:30') <= curTime:
                client.events.remove(event)
                print(f'{get_log_time()}> {event.name}> last time slot passed, removed event from memory')

        for guild in client.guilds:
            # Clean removed events
            for scheduled_event in client.scheduled_events:
                if scheduled_event not in guild.scheduled_events:
                    client.scheduled_events.remove(scheduled_event)
            # Add new events
            for scheduled_event in guild.scheduled_events:
                if scheduled_event not in client.scheduled_events:
                    if scheduled_event.start_time < datetime.datetime.now().astimezone() + datetime.timedelta(hours=13):
                        client.scheduled_events.append(scheduled_event)
        await client.parse_scheduled_events()

        for event in client.events:
            everyoneAnswered = event.has_everyone_answered()

            if not event.scheduled_event and not everyoneAnswered and event.nudge_timer():
                await event.nudge_unresponded_participants()

            if event.created:
                if curTime + datetime.timedelta(minutes=5) == event.start_time:
                    await event.text_channel.send(f'**5 minute warning!** {event.name} will start in 5 minutes.')
                elif curTime == event.start_time:
                    await event.text_channel.send(f'**Event starting now!** {event.name} is starting now.')
                    await event.scheduled_event.start()
                elif curTime == event.end_time:
                    await event.text_channel.send(f'**Event ending now!** {event.name} is ending now.')
                    if event.scheduled_event.status == EventStatus.active:
                        await event.scheduled_event.end()
                    else:
                        await event.scheduled_event.cancel()
                    client.scheduled_events.remove(event.scheduled_event)
                    client.events.remove(event)
                continue
            if event.changed or not everyoneAnswered:
                event.changed = False
                continue

            event.check_times()

            if event.ready_to_create:
                privacy_level = discord.PrivacyLevel.guild_only
                event.scheduled_event = await event.guild.create_scheduled_event(name=event.name, description='Bot generated event based on participant availabilities provided', start_time=event.start_time, end_time=event.end_time, entity_type=event.entity_type, channel=event.voice_channel, privacy_level=privacy_level)
                for participant in event.participants:
                    event.scheduled_event._add_user(participant.member)
                client.scheduled_events.append(event.scheduled_event)
                event.ready_to_create = False
                event.created = True
                print(f'{get_log_time()}> {event.name}> Created event starting at {event.start_time.hour}:{event.start_time.minute} and ending at {event.end_time.hour}:{event.end_time.minute}')

                mentions = ''
                unsubbed = ''
                for participant in event.participants:
                    if participant.subscribed:
                        mentions += f'{participant.member.mention} '
                    else:
                        unsubbed += f'{participant.member.name} '
                if unsubbed != '':
                    unsubbed = '\nUnsubscribed: ' + unsubbed
                if event.start_time.hour < 10 and event.start_time.minute < 10:
                    await event.text_channel.send(f'{mentions}\nHeads up! You are all available for {event.name} starting at 0{event.start_time.hour}:0{event.start_time.minute}.\n' + unsubbed)
                elif event.start_time.hour < 10 and event.start_time.minute >= 10:
                    await event.text_channel.send(f'{mentions}\nHeads up! You are all available for {event.name} starting at 0{event.start_time.hour}:{event.start_time.minute}.\n' + unsubbed)
                elif event.start_time.hour >= 10 and event.start_time.minute < 10:
                    await event.text_channel.send(f'{mentions}\nHeads up! You are all available for {event.name} starting at {event.start_time.hour}:0{event.start_time.minute}.\n' + unsubbed)
                else:
                    await event.text_channel.send(f'{mentions}\nHeads up! You are all available for {event.name} starting at {event.start_time.hour}:{event.start_time.minute}.\n' + unsubbed)

    client.run(discord_token)

if __name__ == '__main__':
    main()


#!/usr/bin/python3

import atexit
import re
import subprocess
import time
from typing import List

import log

_DEGREE_SIGN = "\N{DEGREE SIGN}"
DEGREE_SYMBOL = f"{_DEGREE_SIGN}C"

IPMI_INLET_SENSORNAME = "Inlet Temp"

class Sensor:
    def __init__(self, command: str, pattern: str) -> None:
        self.command = command
        self.pattern = pattern
        self.temps: List[float] = []
        self.last_sensor_reading = time.time()

    def set_last_sensor_reading(self) -> None:
        """Update time of last sensor reading."""
        self.last_sensor_reading = time.time()

    def clear_temps(self) -> None:
        """Clear stored tempartures."""
        self.temps = []

    def fetch_temps(self) -> List[float]:
        """Get sensor data and parse results.

        Returns:
            List[float]: Parsed tempatures
        """
        process = subprocess.run(
            self.command, capture_output=True, shell=True, text=True
        )
        matches = re.findall(self.pattern, process.stdout)
        self.temps = [float(match) for match in matches]
        self.set_last_sensor_reading()
        return self.temps

    def get_average_temp(self) -> float:
        """Calculates the average temparture.

        Returns:
            float: Average of input values
        """
        _divisor = 0
        _total = 0
        average = 0

        for temp in self.temps:
            if temp:
                _total += temp
                _divisor += 1

        if _divisor > 0:
            average = _total / _divisor

        return average

    def get_max_temp(self) -> float:
        """Get max temparture for a given sensor.

        Returns:
            float: Max temparture in degress celcius
        """
        if self.temps:
            return max(self.temps)
        return 0

class FanController:
    def __init__(self) -> None:
        self.static_speed_low: int = 0x02
        self.static_speed_high: int = 0x12
        self.default_threshold: int = 32
        self.base_temp: int = 30
        self.desired_temp_1: int = 40
        self.desired_temp_2: int = 45
        self.desired_temp_3: int = 55
        self.demand_1: int = 5
        self.demand_2: int = 40
        self.demand_3: int = 200
        self.hysteresis: int = 2
        self.current_mode: str = ""
        self.last_fan_setting: int = 0

    def set_fans_default(self):
        if self.current_mode != "default":
            log.info("::: Enable Dynamic Fan Control :::")
            for attempt in range(10):
                process = subprocess.run(
                    "ipmitool raw 0x30 0x30 0x01 0x01",
                    capture_output=True,
                    shell=True,
                    text=True,
                )
                if process.returncode == 0:
                    log.info("::: Enable Dynamic Fan Control Successful :::")
                    self.current_mode = "default"
                    self.last_fan_setting = 0
                    return True
                time.sleep(1)
                log.warning("::: Retrying Dynamic Fan Control :::")
                log.warning(f"Attempt: {attempt}")
            log.error("::: Retries of Dynamic Fan Control All Failed :::")
            return False
        return True

    def set_fans_servo(
        self,
        ambient_sensor: Sensor,
        cpu_sensor: Sensor,
        core_sensor: Sensor,
        hdd_sensor: Sensor,
    ):
        temps = [
            core_sensor.get_average_temp(),
            cpu_sensor.get_average_temp(),
            hdd_sensor.get_average_temp(),
        ]
        average_temp = sum(temps) / len(temps)
        weighted_average = max(average_temp, hdd_sensor.get_average_temp())

        log.info(
            f"Ambient temparture: {ambient_sensor.get_average_temp()}{DEGREE_SYMBOL}"
        )
        log.info(f"Weighted average temparture: {weighted_average}{DEGREE_SYMBOL}")

        if not weighted_average or weighted_average == 0:
            log.error(
                "::: ERROR READING ALL TEMPARTURES! FALLING BACK TO IDRAC CONTROL :::"
            )
            self.set_fans_default()

        if self.current_mode != "set":
            log.info("::: Disable Dynamic Fan Control :::")
            process = subprocess.run(
                "ipmitool raw 0x30 0x30 0x01 0x00",
                capture_output=True,
                shell=True,
                text=True,
            )
            if process.returncode == 0:
                log.info("::: Disable Dynamic Fan Control Successful :::")
                self.current_mode = "set"
            else:
                log.error("::: Disable Dynamic Fan Control Failed :::")

        demand = 0
        if weighted_average > self.base_temp and weighted_average < self.desired_temp_1:
            demand = 0 + (weighted_average - self.base_temp) * (self.demand_1 - 0) / (
                self.desired_temp_1 - self.base_temp
            )
        elif weighted_average >= self.desired_temp_2:
            demand = self.demand_2 + (weighted_average - self.desired_temp_2) * (
                self.demand_3 - self.demand_2
            ) / (self.desired_temp_3 - self.desired_temp_2)
        elif weighted_average >= self.desired_temp_1:
            demand = self.demand_1 + (weighted_average - self.desired_temp_1) * (
                self.demand_2 - self.demand_1
            ) / (self.desired_temp_2 - self.desired_temp_1)

        demand = int(
            self.static_speed_low
            + demand / 100 * (self.static_speed_high - self.static_speed_low)
        )
        if demand > 255:
            demand = 255

        log.info(f"Demand: {demand}")
        log.info(f"Fan setting: {self.last_fan_setting}")

        #   # ramp down the fans quickly upon lack of demand, don't ramp them up
        #   # to tiny spikes of 1 fan unit.  FIXME: But should implement long
        #   # term smoothing of +/- 1 fan unit
        if (
            not self.last_fan_setting
            or demand < self.last_fan_setting
            or demand > self.last_fan_setting + self.hysteresis
        ):
            log.info(f"::: ipmitool raw 0x30 0x30 0x02 0xff {demand:#x} :::")
            process = subprocess.run(
                f"ipmitool raw 0x30 0x30 0x02 0xff {demand:#x}",
                capture_output=True,
                shell=True,
                text=True,
            )
            if process.returncode == 0:
                log.info("::: Set Fan Speed Successful :::")
                self.current_mode = "set"
                self.last_fan_setting = demand
                return True
        log.info("No changes were made")
        return False

def main() -> None:
    """Main service loop."""
    while True:
        if not ambient.temps:
            ambient.fetch_temps()
        if not hdd.temps:
            hdd.fetch_temps()

        core.fetch_temps()
        cpu.fetch_temps()

        log.info(f"Ambient Temparture: {ambient.temps}")
        log.info(f"Cores Temparture: {core.temps}")
        log.info(f"CPUs Temparture: {cpu.temps}")
        log.info(f"HDDs Temparture: {hdd.temps}")

        if ambient.get_average_temp() > fc.default_threshold:
            log.info(
                f"::: Falling back because of high ambient temparture; {ambient.get_average_temp()} > {fc.default_threshold}"
            )
            if not fc.set_fans_default():
                continue
        else:
            if not fc.set_fans_servo(
                ambient_sensor=ambient, cpu_sensor=cpu, core_sensor=core, hdd_sensor=hdd
            ):
                continue

        if (time.time() - ambient.last_sensor_reading) > 60:
            ambient.temps = []
            ambient.set_last_sensor_reading()
            fc.current_mode = "reset"

        if (time.time() - hdd.last_sensor_reading) > 1200:
            hdd.temps = []
            hdd.set_last_sensor_reading()

        time.sleep(3)

@atexit.register
def end_application() -> None:
    log.info("::: Resetting Fans Back to Default :::")
    fc.set_fans_default()
    log.info("::: GOODBYE :::")

if __name__ == "__main__":
    log.init()
    ambient = Sensor(
        command="timeout -k 1 20 ipmitool sdr type temperature | awk '/Inlet Temp/'",
        pattern=r".*\| ([^ ]+) degrees C.*",
    )
    core = Sensor(
        command="timeout -k 1 20 sensors | awk '/Core/'",
        pattern=r".*:\s+\+([^ ]+).C.*",
    )
    cpu = Sensor(
        command="timeout -k 1 20 sensors | awk '/Package id/'",
        pattern=r".*:\s+\+([^ ]+).C.*",
    )
    hdd = Sensor(
        command="hddtemp /dev/sd? | awk '!/255/'",
        pattern=r"/dev/sd[a-z]:\s[a-zA-Z0-9-]*\s[a-zA-Z0-9-]*:\s([^ ]+).C",
    )
    fc = FanController()
    main()


# Processing to create the individual token dictionaries within a sentence dictionary
# Called by nlp_sentence_dictionary.py

from spacy.tokens import Token

from dna.utilities_and_language_specific import add_to_dictionary_values, check_name_gender, empty_string, \
    family_members, family_text, objects_string, processed_prepositions, space

family_roles = [key for key in family_members.keys()]

unwanted_tokens = ('INTJ',   # interjection
                   'X')      # other
# Want 'PUNCT' for semantic evaluation and 'SYM' to address statements such as "66.3% of the vote"

def _handle_noun(noun_token: Token) -> (str, str):
    """
    When processing a token (in add_token_details) that is a noun, first handle the special
    cases of a proper noun, pronoun, family member role, etc. Lastly, process a general noun.

    :param noun_token: Token of the noun
    :return: Two strings, the noun text and type
    """
    # Get text with adjectives and first level of preposition only
    noun_text = noun_token.text
    noun_texts = [noun_text]
    for child_token in noun_token.children:
        if child_token.pos_ == 'VERB':
            continue
        if child_token.dep_ == 'prep':
            noun_texts.append(child_token.text)
            for child2 in child_token.children:
                if child2.pos_ not in unwanted_tokens and child2.dep_ != 'prep' and child2.text != '.' and \
                        ((child2.dep_ == 'det' and child2.text == 'no') or child2.dep_ != 'det'):
                    if child2.dep_ == 'poss':
                        noun_texts.append(f'{child2.text}/poss/')
                    else:
                        for child3 in child2.children:
                            if child3.pos_ == 'SYM':
                                noun_texts.append(child3.text)
                        noun_texts.append(child2.text)
        else:
            if child_token.pos_ not in unwanted_tokens and child_token.text != '.' and \
                    ((child_token.dep_ == 'det' and child_token.text == 'no') or child_token.dep_ != 'det'):
                if child_token.dep_ == 'poss':
                    noun_texts.append(f'{child_token.text}/poss/')
                else:
                    noun_texts.append(child_token.text)
    noun_texts_in_order = []
    for token in noun_token.subtree:
        if token.text in noun_texts:
            noun_texts_in_order.append(token.text)
        elif f'{token.text}/poss/' in noun_texts:
            noun_texts_in_order.append(f'{token.text}/poss/')
    full_noun_text = space.join(noun_texts_in_order)
    # Handle proper nouns and pronouns
    if 'PROPN' in noun_token.pos_:      # Proper noun
        return _process_proper_noun(noun_token, full_noun_text)
    elif 'Prs' in noun_token.morph.get('PronType'):    # Personal pronoun
        return _process_personal_pronoun(noun_token)
    # Handle references to family members ('brother', 'sister', ...) or 'family'
    elif noun_text.lower() in family_roles:
        gender = family_members[noun_text.lower()]
        return full_noun_text, f'{gender}SINGPERSON' if gender else f'SINGPERSON'
    elif noun_text.lower()[0:-1] in family_roles:  # Check if plural family members were referenced
        gender = family_members[noun_text.lower()[0:-1]]   # Check if plural family members were referenced
        return full_noun_text, f'{gender}PLURALPERSON' if gender else f'PLURALPERSON'
    elif noun_text.lower() in family_text:
        return full_noun_text, 'PLURALPERSON'
    else:
        return _process_noun(noun_token, full_noun_text)

def _process_noun(token: Token, full_text: str) -> (str, str):
    """
    When processing a token that is a general noun, capture its number, gender, etc.

    :param token: Token of the noun
    :param full_text: Full text of the noun, with adjectives, prepositions, ...
    :return: Two strings, the entity text and type
    """
    symbols = [t.text for t in token.children if t.pos_ == 'SYM']
    ent_type = token.ent_type_ if token.ent_type_ else 'NOUN'
    # Include plurality in ent_type
    if 'Plur' in token.morph.get('Number'):
        ent_type = f'PLURAL{ent_type}'
    elif 'Sing' in token.morph.get('Number'):
        ent_type = f'SING{ent_type}'
    gender = empty_string
    if 'Fem' in token.morph.get('Gender'):
        gender = 'FEMALE'
    elif 'Masc' in token.morph.get('Gender'):
        gender = 'MALE'
    ent_type = f'{gender}{ent_type}' if gender else ent_type
    # Account for a determiner of 'no' - i.e., nothing (for ex, 'no information was found')
    if any([tc for tc in token.children if tc.dep_ == 'det' and tc.text == 'no']):
        ent_type = f'NEG{ent_type}'
    if 'PERCENT' in ent_type or 'MONEY' in ent_type:    # Clean up spaces introduced by the parse
        full_text = full_text.replace(' %', '%')
        for sym in symbols:
            if full_text[full_text.index(sym) + 2].isdigit():
                full_text = full_text.replace(f'{sym} ', sym)
            elif full_text[full_text.index(sym) - 2].isdigit():
                full_text = full_text.replace(f' {sym}', sym)
    return full_text, ent_type

def _process_prep_object(token: Token, dictionary: dict, prep_key: str):
    """
    When processing a token that is a preposition, capture its object details.

    :param token: Token of the preposition
    :param dictionary: Dictionary that the token details should be added to
    :param prep_key: Dictionary key where the details are added
    :return: None (Specified dictionary is updated)
    """
    # Retrieve the text for nouns (including adjectives and compound nouns)
    prep_ent, prep_ent_type = _handle_noun(token)
    prep_ent_detail = dict()
    prep_ent_detail['detail_text'] = prep_ent.split('$&')[0]
    prep_ent_detail['detail_type'] = prep_ent_type
    # Recursively call if there are more objects, related by conjunction
    for child in token.children:
        if 'cc' == child.dep_:
            dictionary[f'{prep_key}_cc'] = child.text
        elif 'conj' == child.dep_:
            _process_prep_object(child, dictionary, prep_key)
    add_to_dictionary_values(dictionary, 'prep_details', prep_ent_detail, dict)

def _process_personal_pronoun(token: Token) -> (str, str):
    """
    When processing a token that is a personal pronoun, return the text that identifies
    the person and their 'type' (gender + single/plural + PERSON).

    :param token: Token of the personal pronoun
    :return: A tuple holding the text that identifies the person and their 'type'
            (gender + single/plural + PERSON).
    """
    entity = token.text
    entity_type = token.ent_type_
    if '1' in token.morph.get('Person'):
        entity_type = 'PERSON'
    if '2' in token.morph.get('Person'):
        entity_type = 'AUDIENCE'
    elif '3' in token.morph.get('Person'):
        if token.text.lower() == 'it':
            entity_type = 'NOUN'
        else:
            entity_type = 'PERSON'
    # Include gender and plurality in ent_type
    if 'Plur' in token.morph.get('Number'):
        entity_type = f'PLURAL{entity_type}'
    elif 'Sing' in token.morph.get('Number'):
        entity_type = f'SING{entity_type}'
    if 'Fem' in token.morph.get('Gender'):
        entity_type = f'FEMALE{entity_type}'
    elif 'Masc' in token.morph.get('Gender'):
        entity_type = f'MALE{entity_type}'
    return entity, entity_type

def _process_proper_noun(token: Token, full_text: str) -> (str, str):
    """
    When processing a token that is a proper noun, return the text of the noun and its
    'type' (gender + single/plural + PERSON).

    :param token: Token of the proper noun
    :param full_text: Full text of the noun, with adjectives, prepositions, ...
    :return: A tuple holding the text of the noun and its 'type' (gender + single/plural + PERSON/
             GPE/LOC/EVENT/...).
    """
    entity_type = token.ent_type_
    if entity_type.endswith('DATE') or entity_type.endswith('TIME'):
        return full_text, entity_type
    if entity_type.endswith('GPE') or entity_type.endswith('LOC') or entity_type.endswith('FAC') \
            or entity_type.endswith('ORG') or entity_type.endswith('NORP') or entity_type.endswith('EVENT') \
            or entity_type.endswith('NOUN'):
        return _process_noun(token, full_text)
    return full_text, check_name_gender(full_text)

def _setup_entity_dictionary(ent_text: str, ent_type: str, ent_key: str, ent_lemma: str) -> dict:
    """
    Set up a dictionary to hold the entity's/token's details.

    :param ent_text: Full token string - For verbs, = lemma; For nouns, compound words
                     and modifiers added
    :param ent_type: The type of the entity - For example, SINGPERSON or NOUN
    :param ent_key: Dictionary key where the details are added
    :param ent_lemma: The lemma of the entity
    :return: A dictionary holding the entity's/token's initial dictionary definition
    """
    ent_dict = dict()
    # Lists of dictionaries have plural names while individual dictionary keys should not
    ent_base_key = 'detail'
    if 'verb' in ent_key:
        ent_base_key = 'verb'
    elif 'object' in ent_key:
        ent_base_key = 'object'
    elif 'subject' in ent_key:
        ent_base_key = 'subject'
    ent_dict[f'{ent_base_key}_text'] = ent_text
    ent_dict[f'{ent_base_key}_lemma'] = ent_lemma
    if ent_key != 'verb':
        ent_dict[f'{ent_base_key}_type'] = ent_type
    return ent_dict

def add_acomp_details(acomp_token: Token, verb_dict: dict):
    """
    Process the token details for an acomp dependency, which is not a noun/verb but an adjective. That
    adjective may be further expanded by a conjunction, an xcomp clause or preposition with a prepositional
    clause (pcomp). Although these are different dependency paths, their resultant semantics are similar.

    :param acomp_token: Token from spacy parse for an acomp sentence dependency
    :param verb_dict: Dictionary to which the acomp details are added
    :return: None (verb_dict is updated)
    """
    token_key = 'acomp'
    ent_dict = _setup_entity_dictionary(acomp_token.text, 'ADJ', token_key, acomp_token.lemma_)
    for child in acomp_token.children:
        if 'conj' == child.dep_:
            add_acomp_details(child, verb_dict)
        elif 'cc' == child.dep_:
            verb_dict[f'{token_key}_cc'] = child.text   # Future: What about and/or combinations?
        elif child.dep_ in ('xcomp', 'prep'):
            # acomp -> xcomp; e.g., 'he is able to swim' (acomp = able, xcomp = swim)
            # acomp -> prep + pcomp; e.g., 'he is tired of running' (acomp = tired, pcomp = running)
            token_words = [ww for ww in acomp_token.subtree]   # Get the full text of the acomp
            token_text = ' '.join([ww.text for ww in token_words]).strip()
            ent_dict[f'full_text'] = token_text
            # TODO: Expand to extract details of the xcomp verb or if there is a prepositional clause (pcomp)
    add_to_dictionary_values(verb_dict, token_key, ent_dict, dict)

def add_token_details(token: Token, dictionary: dict, token_key: str):
    """
    Expand/clarify the token text, get the token's entity type (or define one if blank), and
    add the token details to the specified dictionary key.

    :param token: Token from spacy parse
    :param dictionary: Dictionary that the token details should be added to
    :param token_key: Dictionary key where the details are added
    :return: None (Input dictionary is updated)
    """
    ent_text = token.text.split('$&')[0]
    if ent_text in ('.', ','):   # Erroneous parse sometimes returns punctuation
        return
    if 'verb' in token_key:
        ent_text = token.lemma_
        ent_type = 'VERB'
    else:
        ent_text, ent_type = _handle_noun(token)
    extraneous_words = []
    # Set up the entity's dictionary and add basic details
    ent_dict = _setup_entity_dictionary(ent_text, ent_type, token_key, token.lemma_)
    # Process specific children (conjunctions and prepositions)
    # Note that the conjunction and conjunctive words are included in the original text and are removed
    #    with the 'extraneous_words' processing
    for child in token.children:
        if ent_type == 'VERB' and ('obj' in child.dep_ or 'attr' in child.dep_):
            add_token_details(child, ent_dict, objects_string)
        elif 'conj' == child.dep_:
            extraneous_words.append(child.text)
            add_token_details(child, dictionary, token_key)
        elif 'cc' == child.dep_:
            extraneous_words.append(f' {child.text} ')
            dictionary[f'{token_key}_cc'] = child.text
        elif 'prep' in child.dep_:
            prep_dict = process_prep_token(child, token_key)
            if prep_dict:
                add_to_dictionary_values(ent_dict, 'preps', prep_dict, dict)
    if extraneous_words and 'object' in token_key:
        curr_text = ent_dict['object_text']
        for extraneous_word in extraneous_words:
            curr_text = curr_text.replace(extraneous_word, empty_string)
        ent_dict['object_text'] = curr_text.strip()
    add_to_dictionary_values(dictionary, token_key, ent_dict, dict)

def process_prep_token(prep_token: Token, token_key: str) -> dict:
    prep_dict = dict()
    if prep_token.text.lower() not in processed_prepositions:
        return prep_dict
    prep_dict['prep_text'] = prep_token.text
    for prep_child in prep_token.children:
        if 'obj' in prep_child.dep_ or 'attr' in prep_child.dep_:
            # Object/attr is part of the prepositional clause, so it adds to the current prep_dict
            _process_prep_object(prep_child, prep_dict, (token_key[0:-1] if token_key.endswith("s") else token_key))
            # TODO: Add processing for pcomp
    return prep_dict


import json
import shlex
import sys
from pprint import pprint
from typing import Any, Callable

from slack import WebClient as SlackClient

from exoskelton.plugin_loader import PluginLoader
from exoskelton.slack_argparser import SlackArgumentParserException
from exoskelton.slack_event_context import SlackEventContextFactory

class SlackDummyClient:
    mock_method = ["chat_postMessage", "chat_postEphemeral"]

    def __init__(self, *args: Any, **kwarg: Any):
        self.slack_client = SlackClient(*args, **kwarg)

    def __getattr__(self, key: str) -> Callable[..., str]:
        if key in self.mock_method:

            def dummy_function(*args: Any, **kwarg: Any) -> str:
                print(key)
                pprint(args)
                pprint(kwarg)
                return json.loads(
                    """
                    {
                        "ok": true,
                        "channel": "C1H9RESGL",
                        "ts": "1503435956.000247",
                        "message": {
                            "text": "Here's a message for you",
                            "username": "ecto1",
                            "bot_id": "B19LU7CSY",
                            "attachments": [
                                {
                                    "text": "This is an attachment",
                                    "id": 1,
                                    "fallback": "This is an attachment's fallback"
                                }
                            ],
                            "type": "message",
                            "subtype": "bot_message",
                            "ts": "1503435956.000247"
                        }
                    }"""
                )

            return dummy_function
        else:
            return getattr(self.slack_client, key)

with open("slack_credential.json") as f:
    slack_tokens = json.load(f)

slack_bot_token = slack_tokens["slack_bot_token"]
slack_dummy_client = SlackDummyClient(slack_bot_token)

pl = PluginLoader(slack_dummy_client, "exoskelton")
parser = pl.create_parser()
context_factory = SlackEventContextFactory(slack_dummy_client)

def handle_message(text: str) -> None:
    event_data = {
        "token": "one-long-verification-token",
        "team_id": "TXXXXXXXX",
        "api_app_id": "AXXXXXXXXX",
        "event": {
            "type": "message",
            "text": text,
            "channel": "dummy_channel",
            "user": "UXXXXXXX1",
            "ts": "1549692377.007700",
        },
        "type": "event_callback",
        "authed_users": ["UXXXXXXX1"],
        "event_id": "Ev08MFMKH6",
        "event_time": 1234567890,
    }

    context = context_factory.create(event_data=event_data)
    if context is None:
        # TODO: logging
        print("Failed to parse event_data")
        return

    if context.text.startswith(parser.prog):
        command_str = shlex.split(context.text)[1:]
        try:
            args = parser.parse_args(command_str)
            args.context = context
            args.handler(**vars(args))
        except SlackArgumentParserException as e:
            # TODO: logging
            context.reply_ephemeral(str(e))
        except Exception:
            # TODO: logging
            err = sys.exc_info()
            context.reply_ephemeral(f"Failure on executing command: {err}")

if __name__ == "__main__":
    while True:
        text = input("> ")
        handle_message(text)


import unittest
import numpy as np
from datetime import datetime, timedelta
from hagelslag.data.ModelOutput import ModelOutput
from hagelslag.processing.tracker import label_storm_objects, extract_storm_objects, extract_storm_patches
from hagelslag.processing.TrackProcessing import TrackProcessor
from hagelslag.processing.ObjectMatcher import shifted_centroid_distance, centroid_distance, time_distance
import os

class TestTracking(unittest.TestCase):
    def setUp(self):
        current_dir_files = os.listdir("./")
        if "testdata" not in current_dir_files:
            start_path = "../"
        else:
            start_path = "./"
        self.model_path = start_path + "testdata/spring2015_unidata/"
        self.ensemble_name = "SSEF"
        self.member ="wrf-s3cn_arw"
        self.run_date = datetime(2015, 6, 4)
        self.variable = "uh_max"
        self.start_hour = 18
        self.end_hour = 24
        self.start_date = self.run_date + timedelta(hours=self.start_hour)
        self.end_date = self.run_date + timedelta(hours=self.end_hour)
        self.map_file = start_path + "mapfiles/ssef2015.map"
        self.model_grid = ModelOutput(self.ensemble_name,
                                 self.member,
                                 self.run_date,
                                 self.variable,
                                 self.start_date,
                                 self.end_date,
                                 self.model_path,
                                 self.map_file,
                                 single_step=False)
        self.model_grid.load_data()
        self.model_grid.load_map_info(self.map_file)

    def test_model_grid_loading(self):
        print(self.model_grid.lat.shape, self.model_grid.lon.shape)
        self.assertEqual(self.model_grid.lat.shape[0], self.model_grid.lon.shape[0],
                          "Lat and Long grids are not equal shaped. {")
        self.assertEqual(self.model_grid.data.shape[1:], self.model_grid.lat.shape,
                          "Data shape does not match map shape.")
        self.assertEqual(self.end_hour - self.start_hour + 1, self.model_grid.data.shape[0],
                          "Number of hours loaded does not match specified hours.")

    def test_object_identification(self):
        min_thresh = 10
        max_thresh = 50
        label_grid = label_storm_objects(self.model_grid.data[0], "hyst",
                                         min_thresh, max_thresh, min_area=2, max_area=100)
        label_points = self.model_grid.data[0][label_grid > 0]
        self.assertGreaterEqual(label_points.min(), min_thresh, "Labeled points include those below minimum threshold")
        storm_objs = extract_storm_objects(label_grid, self.model_grid.data[0], self.model_grid.x,
                                           self.model_grid.y, np.array([0]))
        self.assertEqual(len(storm_objs[0]), label_grid.max(), "Storm objects do not match number of labeled objects")
        storm_patch_objs = extract_storm_patches(label_grid, self.model_grid.data[0], self.model_grid.x,
                                                 self.model_grid.y, np.array([0]))
        self.assertEqual(len(storm_patch_objs[0]), len(storm_objs[0]), "Full object and patch extraction counts don't match.")

    def test_track_processing(self):
        ws_params = (25, 50)
        object_matcher_params = ([shifted_centroid_distance], np.array([1.0]),
                             np.array([24000]))
        track_matcher_params = ([centroid_distance, time_distance],
                                     np.array([80000, 2]))
        size_filter = 10
        gaussian_window = 1
        tp = TrackProcessor(self.run_date, self.start_date, self.end_date, self.ensemble_name,
                            self.member, self.variable, self.model_path, self.map_file, ws_params,
                            object_matcher_params, track_matcher_params, size_filter, gaussian_window,
                            segmentation_approach="ws", single_step=False
                            )
        track_model_patch_objects = tp.find_model_patch_tracks()
        self.assertGreater(len(track_model_patch_objects), 0, "No patch objects found")
        #track_model_objects = tp.find_model_tracks()
        #self.assertGreater(len(track_model_objects), 0, "No objects found")


import pandas as pd
from meteostat import Point, Daily
from datetime import datetime
import json
import csv

with open('/Users/akhilesh/WildfireDB_Resources/Weather/us_data_full.json') as f:
    jsondata = json.load(f)
stations = list()
for item in jsondata:
    stations.append(item.get('id'))
#stations = ['70133','70000']

# now we will open a file for writing
# us_csv_file = open('/Users/akhilesh/WildfireDB_Resources/Weather/us_csv_file.csv', 'w')

# # create the csv writer object
# csv_writer = csv.writer(us_csv_file)

# # Counter variable used for writing
# # headers to the CSV file
# count = 0

# for j in jsondata:
#     if count == 0:

#         # Writing headers of CSV file
#         header = j.keys()
#         csv_writer.writerow(header)
#         count += 1

#     # Writing data of CSV file
#     csv_writer.writerow(j.values())

# us_csv_file.close()
stationsdf = pd.read_csv(r'/Users/akhilesh/WildfireDB_Resources/Weather/us_csv_file.csv')
#print(stationsdf.dtypes)
# List of station IDs in the US
#print(stations)
start = datetime(2012, 1, 1)
end = datetime(2012, 12, 31)
# Create an empty DataFrame to store the data
df = pd.DataFrame()

# Loop over the stations and download the data
for station in stations:
    # Create a Point object for the station

    # Download the daily temperature data for the station
    data = Daily(station,start,end)
    data = data.fetch()
    data['id']=station
    dates = pd.date_range(start, periods=len(data), freq='D')
    data['date'] = dates
    #data = data.assign(id = [station])
    #print(data.head())

    # Add the data to the DataFrame
    df = pd.concat([df, data])
    #print(df.dtypes)

# Save the data to a CSV file

joined=df.merge(stationsdf,on='id',how='left')
#to remove snow,wpgt,tsun
#to remove elevation, daily,timezone,region,country,name
joined.drop(['snow', 'wpgt','tsun','elevation','daily','timezone','region','country','name'], axis=1, inplace=True)
joined = joined.rename(columns={'id': 'station_id'})
joined = joined.rename(columns={'latitude': 'station_latitude'})
joined = joined.rename(columns={'longitude': 'station_longitude'})
joined = joined.drop(df.columns[0], axis=1)
joined=joined[['station_latitude','station_longitude','tavg', 'tmin', 'tmax', 'prcp', 'wdir', 'wspd', 'pres', 'station_id','date']]

#print(joined)
joined.to_csv('/Users/akhilesh/WildfireDB_Resources/Weather/bulk_weather_2012.csv',index=False)


from kubernetes import client

def template(context):
    name = context["name"]
    labels={"name": name}

    ports = context["ports"]
    for port in ports:
        port.update(name="tcp-{}-{}".format(port["port"],port["targetPort"]))

    type = "NodePort" if ports[0].get("nodePort") else "ClusterIP"

    return client.V1Service(
        api_version="v1",
        kind="Service",
        metadata=client.V1ObjectMeta(name = name,labels = labels),
        spec=client.V1ServiceSpec(
            type=type,
            ports=ports,
            selector={"name": name},
        ),
    )


from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect, get_object_or_404
from .models import Topic, Entry
from .forms import EntryForm, TopicForm
from django.http import Http404

'''A view function takes in information from a request, prepares the data needed to generate a page and then sends
the data back to the browser, often by using a template that defines what the page will look like. A context is a
dictionary in which the keys are names we'll use in the template to access the data and the values are the data we
need to send to the template '''

def check_topic_owner(topic, request):
    return topic.owner == request.user

def index(request):
    """The home page for Learning Log"""
    return render(request, 'learning_logs/index.html')

@login_required
def topics(request):
    """Show all topics"""
    topics = Topic.objects.filter(owner=request.user).order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)

@login_required()
def topic(request, topic_id):
    """Show a single topic and all its entries"""
    topic = get_object_or_404(Topic, id=topic_id)
    # make sure the topic belongs to the current user
    if topic.owner != request.user:
        raise Http404
    entries = topic.entry_set.order_by('-date_added')
    context = {'topic': topic, 'entries': entries}
    return render(request, 'learning_logs/topic.html', context)

@login_required()
def new_topic(request):
    """Add a new topic"""
    if request.method != 'POST':
        # No data submitted; create a blank form
        form = TopicForm()
    else:
        # POST data submitted; process the data
        form = TopicForm(data=request.POST)
        if form.is_valid():
            new_topic = form.save(commit=False)
            new_topic.owner = request.user
            new_topic.save()
            return redirect('learning_logs:topics')

    # Display a blank or invalid form
    context = {'form': form}
    return render(request, 'learning_logs/new_topic.html', context)

@login_required()
def new_entry(request, topic_id):
    """Add a new entry for a particular topic"""
    topic = Topic.objects.get(id=topic_id)
    if request.method != 'POST':
        # no data submitted, create a blank form
        form = EntryForm()
    else:
        form = EntryForm(data=request.POST)
        if form.is_valid():
            new_entry = form.save(commit=False)
            new_entry.topic = topic
            new_entry.save()
            return redirect('learning_logs:topic', topic_id=topic_id)

    # Display a blank or invalid form
    context = {'topic': topic, 'form': form}
    return render(request, 'learning_logs/new_entry.html', context)

@login_required()
def edit_entry(request, entry_id):
    """Editing an existing entry"""
    entry = Entry.objects.get(id=entry_id)
    topic = entry.topic
    if topic.owner != request.user:
        raise Http404

    if request.method != 'POST':
        # initial request, pre-fill form with current entry
        form = EntryForm(instance=entry)
    else:
        # POST data submitted; process data
        form = EntryForm(instance=entry, data=request.POST)
        if form.is_valid():
            form.save()
            return redirect('learning_logs:topic', topic_id=topic.id)

    context = {'entry': entry, 'topic': topic, 'form': form}
    return render(request, 'learning_logs/edit_entry.html', context)


from django import forms
from .models import Cliente

class FormularioCliente(forms.ModelForm):

    class Meta:

        model = Cliente

        fields = (

            'empresa',
            'nit',
            'nom_cliente',
            'nom_comercial_cliente',
            'direccion',
            'telefono',
            'correo',
            'web',
            'pais',
            'image'

        )

        labels = {

            'nom_empresa' : 'nombre cliente empresa',
            'nom_comercial' : 'nombre comercial cliente',

        }


from django.shortcuts import render
from .models import *

# Create your views here.

def home(request):
    if request.method=='POST':
        name=request.POST['name']
        skill=request.POST['skill']
        description=request.POST['description']
        obj=Details(name=name,skill=skill,description=description)
        obj.save()
    return render(request,'home.html')

def detail(request):
    obj=Details.objects.all()
    return render(request,'details.html',{'obj':obj})


"""
`{{cookiecutter.import_name}} help` will show detailed help information.
"""

import argparse

DESCRIPTION = __doc__

def build_arg_parser(argparser=None):
    if argparser is None:
        argparser = argparse.ArgumentParser()

    argparser.description = DESCRIPTION
    argparser.formatter_class = argparse.RawTextHelpFormatter

    argparser.add_argument(
        'argument_name',
        type=str,
        help='Get help on this.',
    )

    return argparser

def main(argument_name):
    print(f"This should show help for {argument_name!r}.")

{{ cookiecutter.folder_name }}/{{ cookiecutter.import_name }}/bin/help.py


"""empty message

Revision ID: 4f851adcf334
Revises: dd672c449325
Create Date: 2016-04-06 12:07:09.791911

"""

# revision identifiers, used by Alembic.
revision = '4f851adcf334'
down_revision = 'dd672c449325'

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.create_table('feed',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('user_id', sa.Integer(), nullable=True),
    sa.Column('feedType', sa.String(length=3), nullable=True),
    sa.Column('title', sa.String(length=80), nullable=True),
    sa.Column('detail', sa.Text(), nullable=True),
    sa.Column('badge', sa.String(length=256), nullable=True),
    sa.Column('publish_date', sa.DateTime(), nullable=True),
    sa.Column('update_date', sa.DateTime(), nullable=True),
    sa.Column('public', sa.Boolean(), nullable=True),
    sa.Column('active', sa.Boolean(), nullable=True),
    sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    op.drop_table('diary')
    op.alter_column('following', 'active',
               existing_type=mysql.TINYINT(display_width=1),
               type_=sa.Boolean(),
               existing_nullable=True)
    op.alter_column('planting', 'active',
               existing_type=mysql.TINYINT(display_width=1),
               type_=sa.Boolean(),
               existing_nullable=True)
    op.alter_column('reminder', 'active',
               existing_type=mysql.TINYINT(display_width=1),
               type_=sa.Boolean(),
               existing_nullable=True)
    op.alter_column('user', 'active',
               existing_type=mysql.TINYINT(display_width=1),
               type_=sa.Boolean(),
               existing_nullable=True)
    ### end Alembic commands ###

def downgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.alter_column('user', 'active',
               existing_type=sa.Boolean(),
               type_=mysql.TINYINT(display_width=1),
               existing_nullable=True)
    op.alter_column('reminder', 'active',
               existing_type=sa.Boolean(),
               type_=mysql.TINYINT(display_width=1),
               existing_nullable=True)
    op.alter_column('planting', 'active',
               existing_type=sa.Boolean(),
               type_=mysql.TINYINT(display_width=1),
               existing_nullable=True)
    op.alter_column('following', 'active',
               existing_type=sa.Boolean(),
               type_=mysql.TINYINT(display_width=1),
               existing_nullable=True)
    op.create_table('diary',
    sa.Column('id', mysql.INTEGER(display_width=11), nullable=False),
    sa.Column('user_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True),
    sa.Column('title', mysql.VARCHAR(length=80), nullable=True),
    sa.Column('publish_date', mysql.DATETIME(), nullable=True),
    sa.Column('update_date', mysql.DATETIME(), nullable=True),
    sa.Column('active', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True),
    sa.Column('badge', mysql.VARCHAR(length=256), nullable=True),
    sa.Column('detail', mysql.TEXT(), nullable=True),
    sa.Column('public', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True),
    sa.ForeignKeyConstraint(['user_id'], [u'user.id'], name=u'diary_ibfk_1'),
    sa.PrimaryKeyConstraint('id'),
    mysql_default_charset=u'latin1',
    mysql_engine=u'InnoDB'
    )
    op.drop_table('feed')
    ### end Alembic commands ###


from Crypto.Cipher import AES
from Crypto import Random
import base64

padding_string = b'}'

def encrypt(plaintext, key):
    """
    Encrypt the plaintext with AES method.

    Parameters:
        plaintext -- String to be encrypted.
        key       -- Key for encryption.
    """

    iv = Random.new().read(AES.block_size)
    cipher = AES.new(pad(key), AES.MODE_CFB, iv)
    excrypted = base64.b64encode(iv + cipher.encrypt(plaintext))

    return excrypted

def decrypt(ciphertext, key):
    """
    Decrypt the AES encrypted string.

    Parameters:
        ciphertext -- Encrypted string with AES method.
        key        -- key to decrypt the encrypted string.
    """

    global padding_string

    ciphertext = base64.b64decode(ciphertext)
    iv = ciphertext[:AES.block_size]
    cipher = AES.new(pad(key), AES.MODE_CFB, iv)
    decrypted = cipher.decrypt(ciphertext[AES.block_size:])
    l = decrypted.count(padding_string)

    return decrypted[:len(decrypted)-l]

def pad(str):
    """Add padding to the key."""

    global padding_string
    str_len = len(str)

    # Key must be maximum 32 bytes long, so take first 32 bytes
    if str_len > 32:
        return str[:32]

    # If key size id 16, 24 or 32 bytes then padding not require
    if str_len == 16 or str_len == 24 or str_len == 32:
        return str

    # Add padding to make key 32 bytes long
    return str + ((32 - len(str) % 32) * padding_string)


import os
import pickle
import numpy as np
import torch
import torch.nn as nn
import torch.autograd as autograd
from datetime import datetime

def get_constraint(bits, obj):
    if bits == 0:
        return None
    if 'activation' in obj:
        lower = 0
        upper = 2 ** bits
    else:
        lower = -2 ** (bits - 1) + 1
        upper = 2 ** (bits - 1)
    constraint = np.arange(lower, upper)
    return constraint

def train_one_epoch(net, train_loader, optimizer, criterion, log_fun=None):
    print('\n ... Training Model For One Epoch ...')
    net.train()
    msg_print = MessagePrinter(len(train_loader), freq=10)
    for index, (inputs, targets) in enumerate(train_loader):
        optimizer.zero_grad()
        inputs, targets = inputs.cuda(), targets.long().cuda()
        outputs = net(inputs)
        loss_classify = criterion(outputs, targets)
        loss_classify.backward()
        optimizer.step()
        # print msg and write log
        msg = msg_print(loss_classify, targets, outputs)
        if log_fun is not None:
            log_fun(index, net, msg)
    return msg_print.get_performance()

@autograd.no_grad()
def eval_performance(net, test_loader, criterion, log_fun=None):
    print('\n ... Evaluate Model Performance ...')
    net.eval()
    msg_print = MessagePrinter(len(test_loader), freq=10)
    for index, (inputs, targets) in enumerate(test_loader):
        inputs, targets = inputs.cuda(), targets.long().cuda()
        outputs = net(inputs)
        loss_classify = criterion(outputs, targets)
        msg = msg_print(loss_classify, targets, outputs)
        if log_fun is not None:
            log_fun(index, net, msg)
    return msg_print.get_performance()

class MessagePrinter:
    def __init__(self, total_iter, freq=10):
        self.iter_total = total_iter
        self.iter_curr = 0
        self.loss_cls = 0
        self.num_total = 0
        self.num_top1_correct = 0
        self.num_top5_correct = 0
        self.freq = freq

    def get_performance(self):
        loss_cls = round(self.loss_cls, 4)
        top1_acc = round(100. * self.num_top1_correct / self.num_total, 4)
        top5_acc = round(100. * self.num_top5_correct / self.num_total, 4)
        return [loss_cls, top1_acc, top5_acc]

    def __call__(self, loss_cls, targets, outputs):
        loss_cls = loss_cls.item()
        self.loss_cls = (self.loss_cls * self.iter_curr + loss_cls) / (self.iter_curr + 1)
        self.iter_curr += 1

        top1_correct, top5_correct = self.top1_top5_correct(targets, outputs)
        self.num_total += targets.shape[0]
        self.num_top1_correct += top1_correct
        self.num_top5_correct += top5_correct

        time_stamp = str(datetime.now())[:19]
        msg = '{}, {}/{}, Cls={:.3f}, Top1={:.3f}, Top5={:.3f}'
        top1_acc = 100. * self.num_top1_correct / self.num_total
        top5_acc = 100. * self.num_top5_correct / self.num_total
        show_msg = msg.format(time_stamp,
                              self.iter_curr,
                              self.iter_total,
                              self.loss_cls,
                              top1_acc,
                              top5_acc)
        if self.iter_curr % self.freq == 0 or self.iter_curr == self.iter_total:
            print(show_msg)
        return show_msg

    @staticmethod
    @autograd.no_grad()
    def top1_top5_correct(target, output, topk=(1, 5)):
        maxk = max(topk)
        _, pred = output.topk(k=maxk, dim=1, largest=True, sorted=True)
        pred = pred.t()
        correct = pred.eq(target.view(1, -1).expand_as(pred))
        res = []
        for k in topk:
            correct_k = correct[:k].view(-1).sum().item()
            res.append(correct_k)
        return res

class Trainer:
    def __init__(self, net, train_loader, test_loader, optimizer, lr_scheduler, model_name, train_loger=None):
        self.net = net
        self.train_loader = train_loader
        self.test_loader = test_loader
        self.optimizer = optimizer
        self.lr_scheduler = lr_scheduler
        self.model_name = model_name
        self.train_loger = train_loger
        self.criterion = nn.CrossEntropyLoss()

    def save_model(self):
        print('\n ... Save Model ...')
        model_path = os.path.join(self.train_loger.model_cache, self.model_name + '.pth')
        try:
            state_dict = self.net.module.state_dict()
        except AttributeError:
            state_dict = self.net.state_dict()
        torch.save(state_dict, model_path)

    def load_check_point(self):
        ckpt_cache = self.train_loger.ckpt_cache
        net_path = os.path.join(ckpt_cache, self.model_name + '_ckpt_net.pth')
        opt_path = os.path.join(ckpt_cache, self.model_name + '_ckpt_opt.pth')
        lrs_path = os.path.join(ckpt_cache, self.model_name + '_ckpt_lrs.pth')
        self.net.load_state_dict(torch.load(net_path))
        self.optimizer.load_state_dict(torch.load(opt_path))
        self.lr_scheduler.load_state_dict(torch.load(lrs_path))

        epoch_acc_path = os.path.join(ckpt_cache, self.model_name + '_ckpt_epoch_acc.pkl')
        with open(epoch_acc_path, 'rb') as fd:
            check_point = pickle.load(fd)
            start_epoch = check_point['start_epoch']
            best_quan_acc = check_point['best_quan_acc']
        return start_epoch, best_quan_acc

    def save_check_point(self, epoch, best_quan_acc):
        ckpt_cache = self.train_loger.ckpt_cache
        net_path = os.path.join(ckpt_cache, self.model_name + '_ckpt_net.pth')
        opt_path = os.path.join(ckpt_cache, self.model_name + '_ckpt_opt.pth')
        lrs_path = os.path.join(ckpt_cache, self.model_name + '_ckpt_lrs.pth')
        torch.save(self.net.state_dict(), net_path)
        torch.save(self.optimizer.state_dict(), opt_path)
        torch.save(self.lr_scheduler.state_dict(), lrs_path)

        epoch_acc_path = os.path.join(ckpt_cache, self.model_name + '_ckpt_epoch_acc.pkl')
        with open(epoch_acc_path, 'wb') as fd:
            check_point = {'start_epoch': epoch + 1, 'best_quan_acc': best_quan_acc}
            pickle.dump(check_point, fd)

    def __call__(self, total_epoch, save_check_point=True, resume=False):
        start_epoch = 0
        best_quan_acc = 0
        if resume:
            start_epoch, best_quan_acc = self.load_check_point()
            print('\n Resume, start_epoch=%d, best_quan_acc=%.3f' % (start_epoch, best_quan_acc))

        for epoch in range(start_epoch, total_epoch):
            print("\n %s | Current: %d | Total: %d" % (datetime.now(), epoch + 1, total_epoch))
            train_perf_epoch = train_one_epoch(net=self.net,
                                               train_loader=self.train_loader,
                                               optimizer=self.optimizer,
                                               criterion=self.criterion,
                                               log_fun=self.train_loger.print_log)
            quan_perf_epoch = eval_performance(net=self.net,
                                               test_loader=self.test_loader,
                                               criterion=self.criterion,
                                               log_fun=self.train_loger.print_log)
            self.lr_scheduler.step()

            self.train_loger.print_perf(train_perf_epoch + quan_perf_epoch)
            if quan_perf_epoch[1] > best_quan_acc:
                best_quan_acc = quan_perf_epoch[1]
                self.save_model()

            if save_check_point:
                self.save_check_point(epoch, best_quan_acc)


import numpy as np
import os
import torch
from torch.utils.tensorboard import SummaryWriter
from torch.utils.data import TensorDataset, DataLoader
from Network import VAE_Network

class Beta_VAE_Agent():
    def __init__(self, input_dim, latent_dim, output_dim, beta, learning_rate, seed, log_dir):
        self.input_dim = input_dim
        torch.manual_seed(seed)
        np.random.seed(seed)
        self.network = VAE_Network(input_dim, latent_dim, output_dim)
        self.network.train()
        self.optimizer = torch.optim.Adam(self.network.parameters(), lr = learning_rate)
        self.beta = beta
        self.log_dir = log_dir
        if not os.path.exists(self.log_dir):
            os.mkdir(self.log_dir)
        self.writer = SummaryWriter(log_dir = self.log_dir)
        self.best_score = 1e10
        self.early_stopping_count = 0

    def update_params(self, optim, loss, networks, retain_graph = False, grad_cliping = None):
        optim.zero_grad()
        loss.backward(retain_graph=retain_graph)
        if grad_cliping:
            for net in networks:
                torch.nn.utils.clip_grad_norm_(net.parameters(), grad_cliping)
        optim.step()

    def loss_function(self, x, x_hat, mean, log_var):
        reproduction_loss = torch.nn.MSELoss(reduction = 'sum')(x_hat, x)
        KLD = - 0.5 * torch.sum(1 + log_var - mean.pow(2) - log_var.exp())
        return reproduction_loss + self.beta * KLD

    def load_data(self, feature, label, valid_size, test_size, num_cpu, batch_size):
        print('Begining loading...')
        self.batch_size = batch_size
        T = feature.shape[0]
        index_1 = round(T * (1 - valid_size - test_size))
        index_2 = round(T * (1 - test_size))
        feature_train = feature[:index_1, :]
        label_train = label[:index_1, :]
        feature_valid = feature[index_1: index_2, :]
        label_valid = label[index_1: index_2, :]
        dataset_train = TensorDataset(feature_train, label_train)
        self.train_loader = DataLoader(dataset_train, batch_size = batch_size, shuffle = True, num_workers = num_cpu)
        dataset_valid = TensorDataset(feature_valid, label_valid)
        self.valid_loader = DataLoader(dataset_valid, batch_size = batch_size, shuffle = True, num_workers = num_cpu)
        self.sample_num_train = feature_train.shape[0]
        self.sample_num_valid = feature_valid.shape[0]
        print(f'The data contains {self.sample_num_train} training samples and {self.sample_num_valid} valid samples')
        print("Complete!")

    def train(self, epoch_num, lam):
        # print("Start training VAE...")
        self.network.train()
        for i in range(epoch_num):
            train_losses = list()
            for j, (x, _) in enumerate(self.train_loader):
                x = x.to(self.network.device)
                x_hat, mean, log_var = self.network.forward(x)
                L1_loss = 0
                for param in self.network.parameters():
                    L1_loss += torch.sum(torch.abs(param))
                loss = self.loss_function(x, x_hat, mean, log_var) + L1_loss * self.batch_size * lam
                train_losses.append(loss.item())
                self.update_params(self.optimizer, loss, networks=[self.network])
            train_loss = torch.tensor(train_losses).mean()
            # print(f'Epoch {i}/{epoch_num-1} Training Loss {train_loss:.2f} Time Consume {dtime:.3f}')
            self.writer.add_scalar('loss/train', train_loss, i)
            if i % 10 == 0:
                self.evaluate(i, epoch_num)
                torch.save(self.network.state_dict(), os.path.join(self.log_dir, 'Beta_VAE_final.pth'))
            if self.early_stopping_count >= 10:
                break
        print(f'best score:{self.best_score}')

    def evaluate(self, i, epoch_num):
        self.network.eval()
        with torch.no_grad():
            valid_losses = list()
            for j, (x, y) in enumerate(self.valid_loader):
                x = x.to(self.network.device)
                x_hat, mean, log_var = self.network.forward(x)
                loss = torch.nn.MSELoss(reduction = 'sum')(x_hat, x)
                valid_losses.append(loss.item())
            valid_loss = torch.tensor(valid_losses).mean()
        self.writer.add_scalar('loss/valid', valid_loss.item(), i)
        # print("-"*60)
        # print(f'Evaluation {i}/{epoch_num-1} Loss {test_loss.item():.2f}')
        # print("-"*60)
        if valid_loss.item() < self.best_score:
            # print('Update Model')
            self.early_stopping_count = 0
            self.best_score = valid_loss.item()
            torch.save(self.network.state_dict(), os.path.join(self.log_dir, 'Beta_VAE_best.pth'))
        else:
            self.early_stopping_count += 1

        self.network.train()


"""Remove keyvalue properties table

Revision ID: d5dcee6894fa
Revises: 66971a97040e
Create Date: 2017-12-29 17:39:24.192424

"""

# revision identifiers, used by Alembic.
revision = 'd5dcee6894fa'
down_revision = '66971a97040e'
branch_labels = None
depends_on = None

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('keyvalue_properties')
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('keyvalue_properties',
    sa.Column('id', sa.INTEGER(), nullable=False),
    sa.Column('key', sa.VARCHAR(length=255), server_default=sa.text("''::character varying"), autoincrement=False, nullable=False),
    sa.Column('value', sa.TEXT(), autoincrement=False, nullable=True),
    sa.Column('expiration', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
    sa.PrimaryKeyConstraint('id', name='idx_25223_primary')
    )
    # ### end Alembic commands ###


# ニュートラルネットワーク(python neutral.py)
# パーセプトロン：複数の信号を受け取った時に0, 1どちらかを出力する
import numpy as np
import matplotlib.pyplot as plt

def confirm_execution():
  confirmation = input("実行するためにはyを押してください:")
  return confirmation.lower() == "y"

def simple_perceptron(x1, x2):
    w1 = 0.2 # x1の重み
    w2 = 0.8 # x2の重み
    b = -0.7 # 発火しやすさを調整するパラメータ
    y = x1 * w1 + x2 * w2 + b
    if y <= 0:
      return 0
    elif y > 0:
      return 1

def sigmoid(x): # シグモイド間数の定義
    return 1 / (1 + np.exp(-x))

def nn_1(x): # 簡易のニュートラルネットワーク, xを入力として受け取りyを返す
  w1 = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]])
  b1 = np.array([[0.1, 0.2, 0.3]])

  w2 = np.array([[0.1, 0.3], [0.2, 0.4], [0.5, 0.7]])
  b2 = np.array([[0.1, 0.3]])

  # 行列計算:線形変換してシグモイド間数に代入している
  m = np.dot(x, w1) + b1
  n = sigmoid(m)
  y = np.dot(n, w2) + b2
  return y

x = np.array([0.3, 0.6])
y1 = nn_1(x)

# シグモイド関数を描画
x = np.linspace(-6, 6, 1000)
y = sigmoid(x) # 0~1の値を返す

def execute_matrix_calculation():
  print(simple_perceptron(0, 0))
  print(simple_perceptron(0, 1))
  print(y1)

  fig = plt.figure()
  ax = fig.add_subplot(111)
  ax.plot(x, y)
  plt.show()

if confirm_execution():
   execute_matrix_calculation()
else:
   print("プログラムを終了します。")