from jinja2 import Environment, FileSystemLoader
from netaddr import IPAddress, IPNetwork
from yamlreader import yaml_load
from nameko.rpc import rpc, RpcProxy
import ast
from nameko.standalone.rpc import ClusterRpcProxy

CONFIG = {'AMQP_URI': "amqp://guest:guest@localhost:5672"}

def check_ip_network(ip, network):
    if ip == 'all':
        return False
    if IPAddress(ip) in IPNetwork(network):
        return True
    else:
        return False

def check_values(dict_intent):
    if dict_intent['intent_type'] == 'acl':
        parameters = ['from', 'to', 'rule', 'traffic', 'apply']
    elif dict_intent['intent_type'] == 'nat_1to1':
        parameters = ['from', 'to', 'protocol', 'apply']
    elif dict_intent['intent_type'] == 'traffic_shaping':
        parameters = ['from', 'to', 'with', 'traffic', 'apply']
    elif dict_intent['intent_type'] == 'dst_route':
        parameters = ['from', 'to', 'apply']
    elif dict_intent['intent_type'] == 'nat_nto1':
        parameters = ['from', 'to', 'apply']
    elif dict_intent['intent_type'] == 'url_filter':
        parameters = ['name', 'from', 'to', 'rule', 'apply']
    else:
        return "OPENFLOW TRANSLATOR: Intent type not supported"
    for parameter in parameters:
        if parameter not in dict_intent:
            return 'OPENFLOW TRANSLATOR: ' + parameter.upper() + ' parameter is missing'
    return True

def define_order(dict_intent):
    line = 0
    response = 0
    if dict_intent['intent_type'] == 'acl':
        file = 'rules/openflow_acls'
    elif dict_intent['intent_type'] == 'traffic_shaping':
        file = 'rules/openflow_ts'
    else:
        return "OPENFLOW MODULE: Order not found"
    with open(file) as archive:
        if dict_intent['apply'] == 'insert':
            if 'after' in dict_intent:
                if dict_intent['after'] == 'all-intents':
                    for line_num, l in enumerate(archive, 0):
                        line = line_num
                    response = 65535 - line
                    line = line + 1

                else:
                    for line_num, l in enumerate(archive, 0):
                        if "'name': '" + dict_intent['after'] + "'" in l:
                            line = line_num + 1
                            response = 65535 - line_num
            elif 'before' in dict_intent:
                if dict_intent['before'] == 'all-intents':
                    line = 1
                    response = 65535
                else:
                    for line_num, l in enumerate(archive, 0):
                        if "'name': '" + dict_intent['before'] + "'" in l:
                            line = line_num
                            response = 65535 - (line_num - 1)
        else:
            for line_num, l in enumerate(archive, 0):
                if "'name': '" + dict_intent['name'] + "'" in l:
                    line = line_num
                    response = 65535 - line_num
    archive.close()
    if line != 0:
        archive = open(file)
        lines = archive.readlines()
        archive.close()
        dict_intent['order'] = response
        if dict_intent['apply'] == 'insert':
            lines.insert(line, str(dict_intent) + "\n")
            if dict_intent['intent_type'] == 'traffic_shaping':
                lines.insert(line + 1, str(dict_intent) + "\n")
        else:
            lines.pop(line)
            if dict_intent['intent_type'] == 'traffic_shaping':
                lines.pop(line - 1)
        archive = open(file, 'w')
        archive.writelines(lines)
        archive.close()

    return response

def process_acl(dict_intent):
    # loading YAML with openflow settings
    config = yaml_load('openflow_config.yml')
    dict_intent['hostname'] = config['hostname']
    dict_intent['args'] = ''
    # from/to
    if 'from_mask' in dict_intent:
        dict_intent['from'] = dict_intent['from'] + '/' + dict_intent['from_mask']
    if 'to_mask' in dict_intent:
        dict_intent['to'] = dict_intent['to'] + '/' + dict_intent['to_mask']
    if dict_intent['from'] != 'all':
        dict_intent['args'] = dict_intent['args'] + 'nw_src=' + dict_intent['from'] + ','
    else:
        dict_intent['args'] = dict_intent['args'] + 'nw_src=0.0.0.0/0.0.0.0,'
    if dict_intent['to'] != 'all':
        dict_intent['args'] = dict_intent['args'] + 'nw_dst=' + dict_intent['to'] + ','
    else:
        dict_intent['args'] = dict_intent['args'] + 'nw_dst=0.0.0.0/0.0.0.0,'

    # translate protocol/port
    if dict_intent['traffic'] == 'icmp':
        dict_intent['args'] = dict_intent['args'] + 'nw_proto=1,icmp_type=8,'
    elif dict_intent['traffic'] != 'all':
        protocol, port = dict_intent['traffic'].split('/')
        dict_intent['traffic'] = protocol
        dict_intent['traffic_port'] = 'eq ' + port
        if protocol == 'tcp':
            dict_intent['args'] = dict_intent['args'] + 'nw_proto=6,tcp_dst=' + port + ','
        elif protocol == 'udp':
            dict_intent['args'] = dict_intent['args'] + 'nw_proto=17,udp_dst=' + port + ','

    if dict_intent['rule'] == 'block':
        dict_intent['rule'] = 'drop'
    else:
        dict_intent['rule'] = 'normal'

    order = define_order(dict_intent)
    if order != 0:
        dict_intent['order'] = order
    else:
        return 'OPENFLOW TRANSLATOR - ERROR ORDER: It was not possible to determine the order by name in order parameter'

    file_loader = FileSystemLoader('.')
    env = Environment(loader=file_loader)
    template = env.get_template('openflow_template.j2')
    response = template.render(dict_intent)
    output = 'ovs-ofctl del-flows ' + dict_intent['hostname'] + '\n'
    output = output + response + '\n'
    lines = ['# log acl rules \n']
    file = 'rules/openflow_acls'
    with open(file) as archive:
        for line in archive:
            if '#' not in line[0:5] and line[0:1] != "\n":
                dict_rule = ast.literal_eval(line)
                if int(dict_rule['order']) > int(dict_intent['order']):
                    tmp = template.render(dict_rule)
                    output = output + tmp + '\n'
                    lines.append(str(dict_rule) + '\n')
                elif int(dict_rule['order']) <= int(dict_intent['order']):
                    if dict_rule['name'] == dict_intent['name']:
                        lines.append(str(dict_rule) + '\n')
                    else:
                        if dict_intent['apply'] == 'insert':
                            dict_rule['order'] = int(dict_rule['order']) - 1
                        else:
                            dict_rule['order'] = int(dict_rule['order']) + 1
                        tmp = template.render(dict_rule)
                        output = output + tmp + '\n'
                        lines.append(str(dict_rule) + '\n')
    archive.close()
    archive = open(file, 'w')
    archive.writelines(lines)
    archive.close()
    output = output + '\novs-ofctl add-flow ' + dict_intent['hostname'] + ' priority=0,action=normal'

    #with ClusterRpcProxy(CONFIG) as rpc_connect:
    #   rpc_connect.linux_connector.apply_config(config['ip_manage'], config['ssh_port'], config['username'], config['password'],
    #                                           config['device_type'], output, 'openflow')
    return response

def process_nat11(dict_intent):
    # loading YAML file with firewall settings
    config = yaml_load('openflow_config.yml')
    dict_intent['hostname'] = config['hostname']
    # loading and render template jinja2
    file_loader = FileSystemLoader('.')
    env = Environment(loader=file_loader)
    template = env.get_template('openflow_template.j2')
    output = template.render(dict_intent)
    #with ClusterRpcProxy(CONFIG) as rpc_connect:
    #   rpc_connect.linux_connector.apply_config(config['ip_manage'], config['ssh_port'], config['username'], config['password'],
    #                                           config['device_type'], output, 'openflow')
    return output

def process_traffic_shaping(dict_intent):
    # converting throughput and rate
    dict_intent['with'] = dict_intent['with'] * 1000
    dict_intent['burst'] = int(dict_intent['with'] / 10)
    # loading YAML file with firewall settings
    config = yaml_load('openflow_config.yml')
    dict_intent['hostname'] = config['hostname']
    # loading and render template jinja2
    file_loader = FileSystemLoader('.')
    env = Environment(loader=file_loader)
    template = env.get_template('openflow_template.j2')
    output = template.render(dict_intent)
    #with ClusterRpcProxy(CONFIG) as rpc_connect:
    #    rpc_connect.linux_connector.apply_config(config['ip_manage'], config['ssh_port'], config['username'],
    #                                             config['password'],
    #                                             config['device_type'], output, 'openflow')
    return output

def process_dst_route(dict_intent):
    return 'OPENFLOW TRANSLATOR: Route is not yet supported'

def process_natn1(dict_intent):
    return 'OPENFLOW TRANSLATOR: NAT Nto1 is not yet supported'

def process_url_filter(dict_intent):
    return 'OPENFLOW TRANSLATOR: URL Filter is not yet supported'

class OpenflowService:
    """
        Openflow Service
        Microservice that translates the information sent by the api to commands applicable in Openflow devices
        Receive: this function receives a python dictionary, with at least the following information for each processing
        Return:
            - The microservice activates the application module via ssh and returns the result. If any incorrect
            information in the dictionary, the error message is returned
        Translations for NAT1toN and Route have not yet been implemented
        """
    name = "openflow_translator"
    zipcode_rpc = RpcProxy('openflow_service_translator')

    @rpc
    def translate_intent(self, dict_intent):
        if 'intent_type' in dict_intent:
            output = check_values(dict_intent)
            if output is True:
                if dict_intent['intent_type'] == 'acl':
                    return process_acl(dict_intent)
                elif dict_intent['intent_type'] == 'nat_1to1':
                    return process_nat11(dict_intent)
                elif dict_intent['intent_type'] == 'traffic_shaping':
                    return process_traffic_shaping(dict_intent)
                elif dict_intent['intent_type'] == 'dst_route':
                    return process_dst_route(dict_intent)
                elif dict_intent['intent_type'] == 'nat_nto1':
                    return process_natn1(dict_intent)
                elif dict_intent['intent_type'] == 'url_filter':
                    return process_url_filter(dict_intent)
            else:
                return output
        else:
            return 'OPENFLOW TRANSLATOR: the key "intent_type" is unavailable in the dictionary'


#!pipenv run python3
from pprint import pprint
from PyInquirer import prompt, Separator
from git import Repo
import os
import sys

def get_git_root():
    try:
        git_repo = Repo(os.getcwd(), search_parent_directories=True)
        git_root = git_repo.git.rev_parse("--show-toplevel")
        return git_root
    except:
        print("Not currently in a git repo")
        sys.exit()

repo = Repo(get_git_root())

assert not repo.bare

questions = [
    {
        'type': 'checkbox',
        # 'qmark': 'ðŸ˜ƒ',
        'message': 'Select Branches to Delete',
        'name': 'branches',
        'choices': [
            {
                'name': f"{b} -> {b.tracking_branch()}",
                'value': b
            }
            for b in repo.branches],
        'validate': lambda answer: 'You must choose at least one topping.' \
            if len(answer) == 0 else True
    }
]

answers = prompt(questions)
print("Deleted branches:")
try:
    for b in answers['branches']:
        b.delete(repo, b)
        print(b)
except KeyError:
    pass


from direct.gui.OnscreenImage import OnscreenImage
from pandac.PandaModules import TransparencyAttrib, VBase3
from direct.showbase.DirectObject import DirectObject
from direct.gui.DirectGui import DirectButton, DGG
from direct.interval.IntervalGlobal import Sequence, LerpHprInterval

from gui.Popup import Popup
from gui.AlertPopup import AlertPopup
from gui.FullscreenPopup import FullscreenPopup
from event.ClientEvent import ServerJoinResponseEvent
import Settings
import SettingsController
import Globals

"""
egg-texture-cards -o Button_Offline.egg -p 500,75 offline.png offline_over.png
egg-texture-cards -o Button_Multiplayer.egg -p 500,75 multiplayer.png multiplayer_over.png
egg-texture-cards -o Button_Options.egg -p 500,75 options.png options_over.png
egg-texture-cards -o Button_Exit.egg -p 500,75 exit.png exit_over.png

"""

class MainMenu(DirectObject):

    def __init__(self):
        self.node = aspect2d.attachNewNode('MainMenu')
        self.buttons = []
        self.LoadContent()
        self.SetupEventHandlers()

    def SetupEventHandlers(self):
        self.accept(ServerJoinResponseEvent.EventName, self.OnServerJoinResponseEvent)

    def LoadContent(self):
        bg = OnscreenImage(image = 'Assets/Images/Menus/MainMenu/background.png', scale = (2, 1, 1))
        bg.setTransparency(TransparencyAttrib.MAlpha)
        bg.reparentTo(self.node)
        bg.setBin('fixed', 1)

        title = OnscreenImage(image = 'Assets/Images/Menus/MainMenu/title.png')
        title.setTransparency(TransparencyAttrib.MAlpha)
        title.reparentTo(self.node)

        self.spinner = OnscreenImage(image = 'Assets/Images/Menus/MainMenu/loadingSpinner.png', pos = (-0.15, 1, 0.15), scale = 128.0/1024.0)
        self.spinner.setTransparency(TransparencyAttrib.MAlpha)
        self.spinner.reparentTo(base.a2dBottomRight)
        self.spinner.setBin('gui-popup', 0)
        self.spinner.hide()

        self.LoadButton('Button_Offline', 'offline', 'offline_over', 0, 0.2, self.OnButtonClicked, ['offline'])
        self.LoadButton('Button_Multiplayer', 'multiplayer', 'multiplayer_over', 0, 0, self.OnButtonClicked, ['multiplayer'])
        self.LoadButton('Button_Options', 'options', 'options_over', 0, -0.2, self.OnButtonClicked, ['options'])
        self.LoadButton('Button_Exit', 'exit', 'exit_over', 0, -0.4, self.OnButtonClicked, ['exit'])

    def LoadButton(self, egg, up, over, x, y, cmd, args):
        maps = loader.loadModel("Assets/Images/Menus/MainMenu/%s" % (egg))
        b = DirectButton(geom = (maps.find('**/%s' % (up)),
                         maps.find('**/%s' % (over)),
                         maps.find('**/%s' % (over)),
                         maps.find('**/%s' % (up))),
                         command = cmd,
                         extraArgs = args,
                         pressEffect = 0,
                         relief = None,
                         rolloverSound = None,
                         clickSound = None,
                         pos = (x, 1, y),
                         scale = (1, 1, 75.0/500.0))
        b.reparentTo(self.node)
        self.buttons.append(b)

    def OnButtonClicked(self, buttonText):
        if(buttonText == 'multiplayer'):
            Globals.ROCKET_CONTEXT.LoadDocument('Assets/libRocket/multiplayer.rml').Show()
            self.DisableButtons()
            self.acceptOnce('multiplayerPopupClose', self.OnMultiplayerClose)

        elif(buttonText == 'options'):
            Globals.ROCKET_CONTEXT.LoadDocument('Assets/libRocket/options.rml').Show()
            self.DisableButtons()
            self.acceptOnce('optionsPopupClose', self.OnOptionsClose)

        elif(buttonText == 'exit'):
            self.CreateAlertPopup('Exit Game', 'Do you really want to exit?', self.OnExitPopupOkay, self.OnExitPopupCancel)

        elif(buttonText == 'offline'):
            Globals.ROCKET_CONTEXT.LoadDocument('Assets/libRocket/offline.rml').Show()
            self.DisableButtons()
            self.acceptOnce('offlinePopupClose', self.OnOfflineClose)

    def CreatePopup(self, title, fields, values, onOkay, onCancel):
        p = Popup(title, fields, values, onOkay, onCancel)
        self.OnPopupCreated(p)

    def CreateAlertPopup(self, title, text, onOkay, onCancel):
        p = AlertPopup(title, text, onOkay, onCancel)
        self.OnPopupCreated(p)

    def CreateFullScreenPopup(self, title, fields, values, onOkay, onCancel):
        p = FullscreenPopup(title, fields, values, onOkay, onCancel)
        self.OnPopupCreated(p)

    def OnOfflineClose(self, accept):
        if(accept):
            SettingsController.SaveClientSettings()
            taskMgr.doMethodLater(0.1, messenger.send, 'as', ['startOffline'])
        self.EnableButtons()

    def OnOptionsClose(self, accept):
        if(accept):
            SettingsController.SaveClientSettings()
        self.EnableButtons()

    def OnMultiplayerClose(self, accept):
        if(accept):
            SettingsController.SaveClientSettings()
            self.StartLoadingSpinner()
            self.DisableButtons()
            taskMgr.doMethodLater(0.1, messenger.send, 'as1', ['mainMenuMulti'])
        else:
            self.EnableButtons()

    def DisableButtons(self):
        for b in self.buttons:
            b['state'] = DGG.DISABLED

    def EnableButtons(self):
        for b in self.buttons:
            b['state'] = DGG.NORMAL

    def OnAlertPopupClose(self, popup):
        self.DestroyPopup(popup)

    def OnExitPopupOkay(self, popup):
        self.DestroyPopup(popup)
        messenger.send('mainMenuExit')

    def OnExitPopupCancel(self, popup):
        self.DestroyPopup(popup)

    def OnPopupCreated(self, popup):
        self.DisableButtons()

    def DestroyPopup(self, popup):
        popup.Destroy()
        del popup

        self.EnableButtons()

    def Hide(self):
        self.node.hide()

    def Show(self):
        self.node.show()

    def StartLoadingSpinner(self):
        self.spinner.show()
        self.spinSequence = Sequence(LerpHprInterval(self.spinner, 2, VBase3(0, 0, 180), VBase3(0, 0, 0)),
                                     LerpHprInterval(self.spinner, 2, VBase3(0, 0, 360), VBase3(0, 0, 180)))
        self.spinSequence.loop()

    def StopLoadingSpinner(self):
        self.spinSequence.finish()
        self.spinner.hide()

    def OnServerJoinResponseEvent(self, event):
        self.StopLoadingSpinner()
        self.EnableButtons()
        if(not event.GetResponse()):
            p = AlertPopup('Join Game Failed', event.GetReason(), self.ClosePopup, self.ClosePopup)
            self.OnPopupCreated(p)

    def ClosePopup(self, popup):
        self.DestroyPopup(popup)


import os
from dotenv import load_dotenv
from dataclasses import dataclass, asdict
import os
import openai
from metaphor_python import Metaphor
import html2text
from gtts import gTTS, lang

# set up environment
load_dotenv()

# Class represents information about the articles collected (title, author, published date, content, url)
@dataclass
class Article:
    title: str
    author: str
    published_date: str
    content: str
    summary : str
    url: str

    # Represents instance as dictionary
    def dict(self) -> dict:
        return asdict(self)

# Class represents the text to speech service (find articles, translation, SSML conversion, audio generation)
class Audicle:

    # Message codes used
    generate_query_message = "You are a helpful assistant that generates search queries based on user questions. Only generate one search query. The query should help find articles related to the users query. If the user uses abbreviations, expand these to full phrases. If the user inputs something you don’t understand, look for the articles closest to their search."
    summarize_text_message = "You are a helpful assistant that summarizes the content of a webpage. Summarize the user's input. Only output the summary."
    text_to_ssml_message = "You are a helpful assistant that converts extracted HTML text to SSML using Google Cloud's SSML reference. Convert the user's input while retaining the semantic meaning. Only output the SSML text."
    clean_message = "You are a helpful assistant that makes sure that sentences make sense logically and grammatically based on user input. Make sure the sentence flows correctly and is grammatically correct. Wherever it says null, ignore these fields and continue."

    # Supported language conversions
    supported_lang =  {k: v.lower() for k, v in lang.tts_langs().items()}

    def __init__(self):

        self.openai_key = os.getenv('OPENAI_API_KEY')
        openai.api_key = self.openai_key
        self.metaphor = Metaphor(os.getenv('METAPHOR_API_KEY'))

    def main(self) -> None:

        # Ask user prelim questions for inputs
        topic = input("Hello! Thanks for using Audicle. What kind of article do you want to read today?\n")

        # Make sure requested language is supported
        listen_lang = input("Great! Is there a specific language you'd like to listen in? Please write out the full name of the language.\n")
        while listen_lang.lower() not in self.supported_lang.values():
            listen_lang = input("Sorry, that language is not supported. Please select a language from this list: https://cloud.google.com/text-to-speech/docs/voices\n")

        # Generate query
        query = self.generate(self.generate_query_message, topic)

        print("Loading your article...")

        # Seach for primary article and extract contents
        article = self.metaphor.search(query, num_results = 1, use_autoprompt=True)
        article_list = []
        contents = article.get_contents().contents
        result = article.results
        for i in range(len(contents)):
            article_list.append(self.scrape(result[i].title, result[i].author, result[i].published_date, contents[i].extract, contents[i].url, contents[i].id))

        # Find similar articles and extract contents
        similar_articles = self.metaphor.find_similar(url = article_list[0].url, num_results = 3)
        contents = similar_articles.get_contents().contents
        result = similar_articles.results
        for i in range(len(contents)):
            article_list.append(self.scrape(result[i].title, result[i].author, result[i].published_date, contents[i].extract, contents[i].url, contents[i].id))

        # Assign name to each article
        main, similar1, similar2, similar3 = [i for i in article_list]

        # Give audio file + thanks for listening message
        self.audio_file(main, similar1, similar2, similar3, listen_lang)
        print(self.thanks_for_listening(main, similar1, similar2, similar3))

    # Generate appropriate text output based on user input
    def generate(self, message: str, query: str) -> str:
        completion = openai.ChatCompletion.create(
            model = "gpt-3.5-turbo",
            messages = [
                {"role": "system", "content": message},
                {"role": "user", "content": query},
            ]
        )
        return completion.choices[0].message.content

    # Extract content from query output to create list of Articles
    def scrape(self, title: str, author: str, date: str, content: str, url: str, id_num: str) -> Article:
        article_temp = Article(title = title, author = author, published_date = date, content = content, summary = content, url = url)
        self.get_contents(article_temp, id_num)
        self.html_to_plain(article_temp)
        self.summarize(article_temp)
        return article_temp

    # Retrieve contents
    def get_contents(self, article: Article, id_num = str) -> None:
        content = self.metaphor.get_contents(id_num)
        for content in content.contents:
            article.content = content.extract

    # Convert extracted HTML text to plain text
    def html_to_plain(self, article: Article) -> None:
        convert = html2text.html2text(article.content)
        article.content = convert

    # Update content summary
    def summarize(self, article: Article) -> None:
        summary = self.generate(self.summarize_text_message, article.content)
        article.summary = summary

    # Generate standard reading format ("script") for audio generation
    def text_to_script(self, main: Article, first: Article, second: Article, third: Article) -> str:
        script = f"Here is {main.title} written by {main.author} on {main.published_date}. {main.content}. For further reading, check out {first.title} by {first.author} which discusses {first.summary}. Additionally, you can check out {second.title} by {second.author} or {third.title} by {third.author}."
        script = self.generate(self.clean_message, script)
        return script
        #return self.generate(self.text_to_ssml_message, script)

    # Generate audio delivery message
    def thanks_for_listening(self, main: Article, first: Article, second: Article, third: Article) -> str:
        message = f"Happy listening! This article discusses {main.summary}. For further reads, check out {first.title} by {first.author} at {first.url}, {second.title} by {second.author} at {second.url}, or {third.title} by {third.author} at {third.url}. If you’d like to listen to any of these articles, run the tool again and type the title into search!"
        return self.generate(self.clean_message, message)

    # Create and deliver audio
    def audio_file(self, main: Article, first: Article, second: Article, third: Article, language: str) -> None:
        audio = gTTS(self.text_to_script(main, first, second, third), lang = "".join([key for key, value in self.supported_lang.items() if value == language]))
        title = f"{main.title}.mp3"
        audio.save(title)

if __name__ == '__main__':
    audicle = Audicle()
    audicle.main()


import os
import copy
import logging
import numpy as np
import json_tricks
from gym import spaces

import nni
from nni.tuner import Tuner
from nni.utils import OptimizeMode, extract_scalar_reward

from .model import Model
from .util import set_global_seeds
from .policy import build_lstm_policy

logger = logging.getLogger('ppo_tuner_AutoML')

def constfn(val):
    """wrap as function"""
    def f(_):
        return val
    return f

class ModelConfig:
    """
    Configurations of the PPO model
    """
    def __init__(self):
        self.observation_space = None
        self.action_space = None
        self.num_envs = 0
        self.nsteps = 0

        self.ent_coef = 0.0
        self.lr = 3e-4
        self.vf_coef = 0.5
        self.max_grad_norm = 0.5
        self.gamma = 0.99
        self.lam = 0.95
        self.cliprange = 0.2
        self.embedding_size = None  # the embedding is for each action

        self.noptepochs = 4         # number of training epochs per update
        self.total_timesteps = 5000 # number of timesteps (i.e. number of actions taken in the environment)
        self.nminibatches = 4       # number of training minibatches per update. For recurrent policies,
                                    # should be smaller or equal than number of environments run in parallel.

class TrialsInfo:
    """
    Informations of each trial from one model inference
    """
    def __init__(self, obs, actions, values, neglogpacs, dones, last_value, inf_batch_size):
        self.iter = 0
        self.obs = obs
        self.actions = actions
        self.values = values
        self.neglogpacs = neglogpacs
        self.dones = dones
        self.last_value = last_value

        self.rewards = None
        self.returns = None

        self.inf_batch_size = inf_batch_size
        #self.states = None

    def get_next(self):
        """
        get actions of the next trial
        """
        if self.iter >= self.inf_batch_size:
            return None, None
        actions = []
        for step in self.actions:
            actions.append(step[self.iter])
        self.iter += 1
        return self.iter - 1, actions

    def update_rewards(self, rewards, returns):
        """
        after the trial is finished, reward and return of this trial is updated
        """
        self.rewards = rewards
        self.returns = returns

    def convert_shape(self):
        """
        convert shape
        """
        def sf01(arr):
            """
            swap and then flatten axes 0 and 1
            """
            s = arr.shape
            return arr.swapaxes(0, 1).reshape(s[0] * s[1], *s[2:])
        self.obs = sf01(self.obs)
        self.returns = sf01(self.returns)
        self.dones = sf01(self.dones)
        self.actions = sf01(self.actions)
        self.values = sf01(self.values)
        self.neglogpacs = sf01(self.neglogpacs)

class PPOModel:
    """
    PPO Model
    """
    def __init__(self, model_config, mask):
        self.model_config = model_config
        self.states = None    # initial state of lstm in policy/value network
        self.nupdates = None  # the number of func train is invoked, used to tune lr and cliprange
        self.cur_update = 1   # record the current update
        self.np_mask = mask   # record the mask of each action within one trial

        set_global_seeds(None)
        assert isinstance(self.model_config.lr, float)
        self.lr = constfn(self.model_config.lr)
        assert isinstance(self.model_config.cliprange, float)
        self.cliprange = constfn(self.model_config.cliprange)

        # build lstm policy network, value share the same network
        policy = build_lstm_policy(model_config)

        # Get the nb of env
        nenvs = model_config.num_envs

        # Calculate the batch_size
        self.nbatch = nbatch = nenvs * model_config.nsteps # num of record per update
        nbatch_train = nbatch // model_config.nminibatches # get batch size
        # self.nupdates is used to tune lr and cliprange
        self.nupdates = self.model_config.total_timesteps // self.nbatch

        # Instantiate the model object (that creates act_model and train_model)
        self.model = Model(policy=policy, nbatch_act=nenvs, nbatch_train=nbatch_train,
                           nsteps=model_config.nsteps, ent_coef=model_config.ent_coef, vf_coef=model_config.vf_coef,
                           max_grad_norm=model_config.max_grad_norm, np_mask=self.np_mask)

        self.states = self.model.initial_state

        logger.info('=== finished PPOModel initialization')

    def inference(self, num):
        """
        generate actions along with related info from policy network.
        observation is the action of the last step.

        Parameters:
        ----------
        num:             the number of trials to generate
        """
        # Here, we init the lists that will contain the mb of experiences
        mb_obs, mb_actions, mb_values, mb_dones, mb_neglogpacs = [], [], [], [], []
        # initial observation
        # use the (n+1)th embedding to represent the first step action
        first_step_ob = self.model_config.action_space.n
        obs = [first_step_ob for _ in range(num)]
        dones = [True for _ in range(num)]
        states = self.states
        # For n in range number of steps
        for cur_step in range(self.model_config.nsteps):
            # Given observations, get action value and neglopacs
            # We already have self.obs because Runner superclass run self.obs[:] = env.reset() on init
            actions, values, states, neglogpacs = self.model.step(cur_step, obs, S=states, M=dones)
            mb_obs.append(obs.copy())
            mb_actions.append(actions)
            mb_values.append(values)
            mb_neglogpacs.append(neglogpacs)
            mb_dones.append(dones)

            # Take actions in env and look the results
            # Infos contains a ton of useful informations
            obs[:] = actions
            if cur_step == self.model_config.nsteps - 1:
                dones = [True for _ in range(num)]
            else:
                dones = [False for _ in range(num)]

        #batch of steps to batch of rollouts
        np_obs = np.asarray(obs)
        mb_obs = np.asarray(mb_obs, dtype=np_obs.dtype)
        mb_actions = np.asarray(mb_actions)
        mb_values = np.asarray(mb_values, dtype=np.float32)
        mb_neglogpacs = np.asarray(mb_neglogpacs, dtype=np.float32)
        mb_dones = np.asarray(mb_dones, dtype=np.bool)
        last_values = self.model.value(np_obs, S=states, M=dones)

        return mb_obs, mb_actions, mb_values, mb_neglogpacs, mb_dones, last_values

    def compute_rewards(self, trials_info, trials_result):
        """
        compute the rewards of the trials in trials_info based on trials_result,
        and update the rewards in trials_info

        Parameters:
        ----------
        trials_info:             info of the generated trials
        trials_result:           final results (e.g., acc) of the generated trials
        """
        mb_rewards = np.asarray([trials_result for _ in trials_info.actions], dtype=np.float32)
        # discount/bootstrap off value fn
        mb_returns = np.zeros_like(mb_rewards)
        mb_advs = np.zeros_like(mb_rewards)
        lastgaelam = 0
        last_dones = np.asarray([True for _ in trials_result], dtype=np.bool) # ugly
        for t in reversed(range(self.model_config.nsteps)):
            if t == self.model_config.nsteps - 1:
                nextnonterminal = 1.0 - last_dones
                nextvalues = trials_info.last_value
            else:
                nextnonterminal = 1.0 - trials_info.dones[t+1]
                nextvalues = trials_info.values[t+1]
            delta = mb_rewards[t] + self.model_config.gamma * nextvalues * nextnonterminal - trials_info.values[t]
            mb_advs[t] = lastgaelam = delta + self.model_config.gamma * self.model_config.lam * nextnonterminal * lastgaelam
        mb_returns = mb_advs + trials_info.values

        trials_info.update_rewards(mb_rewards, mb_returns)
        trials_info.convert_shape()

    def train(self, trials_info, nenvs):
        """
        train the policy/value network using trials_info

        Parameters:
        ----------
        trials_info:             complete info of the generated trials from the previous inference
        nenvs:                   the batch size of the (previous) inference
        """
        # keep frac decay for future optimization
        if self.cur_update <= self.nupdates:
            frac = 1.0 - (self.cur_update - 1.0) / self.nupdates
        else:
            logger.warning('current update (self.cur_update) %d has exceeded total updates (self.nupdates) %d',
                           self.cur_update, self.nupdates)
            frac = 1.0 - (self.nupdates - 1.0) / self.nupdates
        lrnow = self.lr(frac)
        cliprangenow = self.cliprange(frac)
        self.cur_update += 1

        states = self.states

        assert states is not None # recurrent version
        assert nenvs % self.model_config.nminibatches == 0
        envsperbatch = nenvs // self.model_config.nminibatches
        envinds = np.arange(nenvs)
        flatinds = np.arange(nenvs * self.model_config.nsteps).reshape(nenvs, self.model_config.nsteps)
        for _ in range(self.model_config.noptepochs):
            np.random.shuffle(envinds)
            for start in range(0, nenvs, envsperbatch):
                end = start + envsperbatch
                mbenvinds = envinds[start:end]
                mbflatinds = flatinds[mbenvinds].ravel()
                slices = (arr[mbflatinds] for arr in (trials_info.obs, trials_info.returns, trials_info.dones,
                                                      trials_info.actions, trials_info.values, trials_info.neglogpacs))
                mbstates = states[mbenvinds]
                self.model.train(lrnow, cliprangenow, *slices, mbstates)

class PPOTuner(Tuner):
    """
    PPOTuner
    """

    def __init__(self, optimize_mode, trials_per_update=20, epochs_per_update=4, minibatch_size=4,
                 ent_coef=0.0, lr=3e-4, vf_coef=0.5, max_grad_norm=0.5, gamma=0.99, lam=0.95, cliprange=0.2):
        """
        initialization, PPO model is not initialized here as search space is not received yet.

        Parameters:
        ----------
        optimize_mode:         maximize or minimize
        trials_per_update:     number of trials to have for each model update
        epochs_per_update:     number of epochs to run for each model update
        minibatch_size:        minibatch size (number of trials) for the update
        ent_coef:              policy entropy coefficient in the optimization objective
        lr:                    learning rate of the model (lstm network), constant
        vf_coef:               value function loss coefficient in the optimization objective
        max_grad_norm:         gradient norm clipping coefficient
        gamma:                 discounting factor
        lam:                   advantage estimation discounting factor (lambda in the paper)
        cliprange:             cliprange in the PPO algorithm, constant
        """
        self.optimize_mode = OptimizeMode(optimize_mode)
        self.model_config = ModelConfig()
        self.model = None
        self.search_space = None
        self.running_trials = {}                  # key: parameter_id, value: actions/states/etc.
        self.inf_batch_size = trials_per_update   # number of trials to generate in one inference
        self.first_inf = True                     # indicate whether it is the first time to inference new trials
        self.trials_result = [None for _ in range(self.inf_batch_size)] # results of finished trials

        self.credit = 0 # record the unsatisfied trial requests
        self.param_ids = []
        self.finished_trials = 0
        self.chosen_arch_template = {}

        self.actions_spaces = None
        self.actions_to_config = None
        self.full_act_space = None
        self.trials_info = None

        self.all_trials = {} # used to dedup the same trial, key: config, value: final result

        self.model_config.num_envs = self.inf_batch_size
        self.model_config.noptepochs = epochs_per_update
        self.model_config.nminibatches = minibatch_size

        self.send_trial_callback = None
        logger.info('=== finished PPOTuner initialization')

    def _process_one_nas_space(self, block_name, block_space):
        """
        process nas space to determine observation space and action space

        Parameters:
        ----------
        block_name:              the name of the mutable block
        block_space:             search space of this mutable block

        Returns:
        ----------
        actions_spaces:          list of the space of each action
        actions_to_config:       the mapping from action to generated configuration
        """
        actions_spaces = []
        actions_to_config = []

        block_arch_temp = {}
        for l_name, layer in block_space.items():
            chosen_layer_temp = {}

            if len(layer['layer_choice']) > 1:
                actions_spaces.append(layer['layer_choice'])
                actions_to_config.append((block_name, l_name, 'chosen_layer'))
                chosen_layer_temp['chosen_layer'] = None
            else:
                assert len(layer['layer_choice']) == 1
                chosen_layer_temp['chosen_layer'] = layer['layer_choice'][0]

            if layer['optional_input_size'] not in [0, 1, [0, 1]]:
                raise ValueError('Optional_input_size can only be 0, 1, or [0, 1], but the pecified one is %s'
                                 % (layer['optional_input_size']))
            if isinstance(layer['optional_input_size'], list):
                actions_spaces.append(["None", *layer['optional_inputs']])
                actions_to_config.append((block_name, l_name, 'chosen_inputs'))
                chosen_layer_temp['chosen_inputs'] = None
            elif layer['optional_input_size'] == 1:
                actions_spaces.append(layer['optional_inputs'])
                actions_to_config.append((block_name, l_name, 'chosen_inputs'))
                chosen_layer_temp['chosen_inputs'] = None
            elif layer['optional_input_size'] == 0:
                chosen_layer_temp['chosen_inputs'] = []
            else:
                raise ValueError('invalid type and value of optional_input_size')

            block_arch_temp[l_name] = chosen_layer_temp

        self.chosen_arch_template[block_name] = block_arch_temp

        return actions_spaces, actions_to_config

    def _process_nas_space(self, search_space):
        """
        process nas search space to get action/observation space
        """
        actions_spaces = []
        actions_to_config = []
        for b_name, block in search_space.items():
            if block['_type'] != 'mutable_layer':
                raise ValueError('PPOTuner only accept mutable_layer type in search space, but the current one is %s'%(block['_type']))
            block = block['_value']
            act, act_map = self._process_one_nas_space(b_name, block)
            actions_spaces.extend(act)
            actions_to_config.extend(act_map)

        # calculate observation space
        dedup = {}
        for step in actions_spaces:
            for action in step:
                dedup[action] = 1
        full_act_space = [act for act, _ in dedup.items()]
        assert len(full_act_space) == len(dedup)
        observation_space = len(full_act_space)

        nsteps = len(actions_spaces)

        return actions_spaces, actions_to_config, full_act_space, observation_space, nsteps

    def _generate_action_mask(self):
        """
        different step could have different action space. to deal with this case, we merge all the
        possible actions into one action space, and use mask to indicate available actions for each step
        """
        two_masks = []

        mask = []
        for acts in self.actions_spaces:
            one_mask = [0 for _ in range(len(self.full_act_space))]
            for act in acts:
                idx = self.full_act_space.index(act)
                one_mask[idx] = 1
            mask.append(one_mask)
        two_masks.append(mask)

        mask = []
        for acts in self.actions_spaces:
            one_mask = [-np.inf for _ in range(len(self.full_act_space))]
            for act in acts:
                idx = self.full_act_space.index(act)
                one_mask[idx] = 0
            mask.append(one_mask)
        two_masks.append(mask)

        return np.asarray(two_masks, dtype=np.float32)

    def update_search_space(self, search_space):
        """
        get search space, currently the space only includes that for NAS

        Parameters:
        ----------
        search_space:                  search space for NAS

        Returns:
        -------
        no return
        """
        logger.info('=== update search space %s', search_space)
        assert self.search_space is None
        self.search_space = search_space

        assert self.model_config.observation_space is None
        assert self.model_config.action_space is None

        self.actions_spaces, self.actions_to_config, self.full_act_space, obs_space, nsteps = self._process_nas_space(search_space)

        self.model_config.observation_space = spaces.Discrete(obs_space)
        self.model_config.action_space = spaces.Discrete(obs_space)
        self.model_config.nsteps = nsteps

        # generate mask in numpy
        mask = self._generate_action_mask()

        assert self.model is None
        self.model = PPOModel(self.model_config, mask)

    def _actions_to_config(self, actions):
        """
        given actions, to generate the corresponding trial configuration
        """
        chosen_arch = copy.deepcopy(self.chosen_arch_template)
        for cnt, act in enumerate(actions):
            act_name = self.full_act_space[act]
            (block_name, layer_name, key) = self.actions_to_config[cnt]
            if key == 'chosen_inputs':
                if act_name == 'None':
                    chosen_arch[block_name][layer_name][key] = []
                else:
                    chosen_arch[block_name][layer_name][key] = [act_name]
            elif key == 'chosen_layer':
                chosen_arch[block_name][layer_name][key] = act_name
            else:
                raise ValueError('unrecognized key: {0}'.format(key))
        return chosen_arch

    def generate_multiple_parameters(self, parameter_id_list, **kwargs):
        """
        Returns multiple sets of trial (hyper-)parameters, as iterable of serializable objects.
        """
        result = []
        self.send_trial_callback = kwargs['st_callback']
        for parameter_id in parameter_id_list:
            had_exception = False
            try:
                logger.debug("generating param for %s", parameter_id)
                res = self.generate_parameters(parameter_id, **kwargs)
            except nni.NoMoreTrialError:
                had_exception = True
            if not had_exception:
                result.append(res)
        return result

    def generate_parameters(self, parameter_id, **kwargs):
        """
        generate parameters, if no trial configration for now, self.credit plus 1 to send the config later
        """
        if self.first_inf:
            self.trials_result = [None for _ in range(self.inf_batch_size)]
            mb_obs, mb_actions, mb_values, mb_neglogpacs, mb_dones, last_values = self.model.inference(self.inf_batch_size)
            self.trials_info = TrialsInfo(mb_obs, mb_actions, mb_values, mb_neglogpacs,
                                          mb_dones, last_values, self.inf_batch_size)
            self.first_inf = False

        trial_info_idx, actions = self.trials_info.get_next()
        if trial_info_idx is None:
            self.credit += 1
            self.param_ids.append(parameter_id)
            raise nni.NoMoreTrialError('no more parameters now.')

        self.running_trials[parameter_id] = trial_info_idx
        new_config = self._actions_to_config(actions)
        return new_config

    def _next_round_inference(self):
        """
        """
        self.finished_trials = 0
        self.model.compute_rewards(self.trials_info, self.trials_result)
        self.model.train(self.trials_info, self.inf_batch_size)
        self.running_trials = {}
        # generate new trials
        self.trials_result = [None for _ in range(self.inf_batch_size)]
        mb_obs, mb_actions, mb_values, mb_neglogpacs, mb_dones, last_values = self.model.inference(self.inf_batch_size)
        self.trials_info = TrialsInfo(mb_obs, mb_actions, mb_values, mb_neglogpacs,
                                        mb_dones, last_values, self.inf_batch_size)
        # check credit and submit new trials
        for _ in range(self.credit):
            trial_info_idx, actions = self.trials_info.get_next()
            if trial_info_idx is None:
                logger.warning('No enough trial config, trials_per_update is suggested to be larger than trialConcurrency')
                break
            assert self.param_ids
            param_id = self.param_ids.pop()
            self.running_trials[param_id] = trial_info_idx
            new_config = self._actions_to_config(actions)
            self.send_trial_callback(param_id, new_config)
            self.credit -= 1

    def receive_trial_result(self, parameter_id, parameters, value, **kwargs):
        """
        receive trial's result. if the number of finished trials equals self.inf_batch_size, start the next update to
        train the model
        """
        trial_info_idx = self.running_trials.pop(parameter_id, None)
        assert trial_info_idx is not None

        value = extract_scalar_reward(value)
        if self.optimize_mode == OptimizeMode.Minimize:
            value = -value

        self.trials_result[trial_info_idx] = value
        self.finished_trials += 1

        if self.finished_trials == self.inf_batch_size:
            self._next_round_inference()

    def trial_end(self, parameter_id, success, **kwargs):
        """
        to deal with trial failure
        """
        if not success:
            if parameter_id not in self.running_trials:
                logger.warning('The trial is failed, but self.running_trial does not have this trial')
                return
            trial_info_idx = self.running_trials.pop(parameter_id, None)
            assert trial_info_idx is not None
            # use mean of finished trials as the result of this failed trial
            values = [val for val in self.trials_result if val is not None]
            logger.warning('zql values: {0}'.format(values))
            self.trials_result[trial_info_idx] = (sum(values) / len(values)) if len(values) > 0 else 0
            self.finished_trials += 1
            if self.finished_trials == self.inf_batch_size:
                self._next_round_inference()

    def import_data(self, data):
        """
        Import additional data for tuning

        Parameters
        ----------
        data:               a list of dictionarys, each of which has at least two keys, 'parameter' and 'value'
        """
        logger.warning('PPOTuner cannot leverage imported data.')


import json

from django.http import JsonResponse
from django.views import View
from owners.models import Owner, Dogs

class OwnerRegister(View):
    def get(self, request):
        result = []
        owner = Owner.objects.all()
        # 쿼리문이기 때문에 바로 response를 하지못한다 그래서 반복문을 통해 딕셔너리형테로 만들어준뒤에 response를 해야한다.
        for owners in owner:
            result.append(
                {
                    'id':owners.id,
                    'owner_name': owners.owner_name,
                    'owner_email': owners.owner_email,
                    'owner_age': owners.owner_age,
                }
            )
        return JsonResponse({'owner':result}, status=200)

    def post(self, request):
        data = json.loads(request.body)

        owner = Owner(
            owner_name = data['owner_name'],
            owner_email = data['owner_email'],
            owner_age = data['owner_age'])
        owner.save()

        return JsonResponse({'message': '등록되었습니다'}, status=201)

class DogRegister(View):
    def get(self, request):
        result = []
        dog = Dogs.objects.all()

        # 쿼리문이기 때문에 바로 response를 하지못한다 그래서 반복문을 통해 딕셔너리형테로 만들어준뒤에 response를 해야한다.
        for dogs in dog:
            result.append(
                {
                    'id':dogs.id,
                    'dog_name': dogs.dog_name,
                    'dog_age': dogs.dog_age,
                    'owner_id': dogs.owner.owner_name
                    # 정참조의경우 .id, .owner_name, .owner_email, .owner_age 다 가능하다.
                }
            )
        return JsonResponse({'dogs':result}, status=200)

    def post(self, request):
        data = json.loads(request.body)
        owner_name = Owner.objects.get(owner_name=data['owner_name'])

        dogs = Dogs(
            dog_name = data['dog_name'],
            dog_age = data['dog_age'],
            owner_id = owner_name.id
            )

        dogs.save()

        return JsonResponse({'message': '등록되었습니다'}, status=201)

class OwnerList(View):
    def get(self, request):
        result = []
        owner = Owner.objects.all()
        for owners in owner:
            dog = owners.dogs_set.all()
            # 역참조를 할때 _set 사용할땐 models에서 related_name= 속성을 사용할 수 없다.
            # 역참조는 정참조와 다르게 여러개일수 있음으로 모든내역을 다 출력하려면 반복문으로 리스트화시켜서 넣어주고,
            # 하나만 찝어서 넣고싶다면 범위를 정확하게 지정해줘야한다.
            result1 = []
            for dogs in dog:
                result1.append(
                        {
                            'dog_name': dogs.dog_name,
                            'dog_age': dogs.dog_age,
                        }
                    )
            result.append(
                {
                    'id':owners.id,
                    'owner_name': owners.owner_name,
                    'owner_email': owners.owner_email,
                    'owner_age': owners.owner_age,
                    'owner_id':result1,
                }
            )

        return JsonResponse({'owner_doglist':result}, status=200)


from pathlib import Path
import numpy as np
import re
from bisect import bisect_left
from copy import deepcopy
from itertools import compress
reg = re.compile(r"(y|x)=(\d+), (y|x)=(\d+)\.\.(\d+)")

def main():
    data_folder = Path(".").resolve()
    data = data_folder.joinpath("input.txt").read_text()

    res = Reservoir(data)
    res.flow()
    res.print_ground()
    print(f"The water can reach {res.count_water()} tiles")
    print(f"There are {res.count_rest_water()} tiles left after the spring stops.")

class Reservoir():

    def __init__(self,data):
        data = data.split("\n")

        ax = {'x':1,'y':0}
        sep = '..'
        for i,line in enumerate(data):
            m = reg.match(line).group(1,2,3,4,5)
            data[i] = [[],[]]
            data[i][ax[m[0]]] = [int(m[1])]
            data[i][ax[m[2]]] = [int(m[3]),int(m[4])]

        self.lims = [[0,0],[500,500]]
        self.lowest_y = None
        for line in data:
            for j,dim in enumerate(line):
                for point in dim:
                    if point > self.lims[j][1]:
                        self.lims[j][1] = point

                    if point < self.lims[j][0]:
                        self.lims[j][0] = point

                    if (j == 0) and ((self.lowest_y is None) or (point < self.lowest_y)) :
                            self.lowest_y = point

        self.lims[1][0] -= 1
        self.lims[1][1] += 1

        self.size = (self.lims[0][1]+1,self.lims[1][1]-self.lims[1][0]+1)

        self.ground = np.full(self.size,ord('.'),dtype=int)
        for line in data:
            if len(line[1]) == 2:
                self.ground[line[0][0],(line[1][0]-self.lims[1][0]):(line[1][1]+1-self.lims[1][0])] = ord('#')
            else:
                self.ground[line[0][0]:(line[0][1]+1),line[1][0]-self.lims[1][0]] = ord('#')

        self.spring = (0,500-self.lims[1][0])
        self.ground[self.spring[0],self.spring[1]] = ord('+')
        self.flowing = [self.spring]

    def print_ground(self):
        s = ""
        col_size = self.lims[1][1]
        col_digits = len(str(col_size))
        row_size = self.lims[0][1]
        row_digits = len(str(row_size))

        form_str = "{:" + str(col_digits) + "d}"
        for j in range(col_digits):
            s += " "*row_digits
            for loc in range(self.lims[1][0],self.lims[1][1]+1):
                loc_str = form_str.format(loc)
                s += loc_str[j]
            s += "\n"

        form_str = "{:" + str(row_digits) + "d}"
        for y in range(row_size+1):
            s += form_str.format(y)
            for k in self.ground[y]:
                s += chr(k)
            s += "\n"
        data_folder = Path(".").resolve()
        data_folder.joinpath("output.txt").write_text(s)

    def flow_step(self):
        active = [True]*len(self.flowing)
        active_len = len(active)
        for j,tile in enumerate(self.flowing):
            if active[j]:
                below = (tile[0]+1,tile[1])
                left = (tile[0],tile[1]-1)
                right = (tile[0],tile[1]+1)
                if below[0] >= self.size[0]:
                    active[j] = False
                elif self.ground[below] == ord('.'):
                    self.ground[below] = ord('|')
                    self.flowing.append(below)
                    active.append(True)
                elif self.ground[below] in [ord('~'),ord('#')]:
                    i_l = 1
                    left_below = (tile[0]+1,tile[1]-i_l)
                    while (self.ground[left] in [ord('.'),ord('|')]) and (self.ground[left_below] in [ord('~'),ord('#')]):
                        i_l += 1
                        left = (tile[0],tile[1]-i_l)
                        left_below = (tile[0]+1,tile[1]-i_l)

                    i_r = 1
                    right_below = (tile[0]+1,tile[1]+i_r)
                    while (self.ground[right] in [ord('.'),ord('|')]) and (self.ground[right_below] in [ord('~'),ord('#')]):
                        i_r += 1
                        right = (tile[0],tile[1]+i_r)
                        right_below = (tile[0]+1,tile[1]+i_r)

                    if (self.ground[right] == ord('#')) and (self.ground[left] == ord('#')):
                        self.ground[tile[0],(left[1]+1):right[1]] = ord('~')
                        for k,_ in enumerate(active):
                            if (self.flowing[k][0] == tile[0]) and (left[1] < self.flowing[k][1] < right[1]):
                                active[k] = False
                    else:
                        closed_left = int(self.ground[left] not in [ord('.'),ord('|')])
                        open_right = int(self.ground[right] in [ord('.'),ord('|')])
                        self.ground[tile[0],(left[1]+closed_left):(right[1]+open_right)] = ord('|')
                        if not closed_left:
                            self.flowing.append(left)
                            active.append(True)
                        if open_right:
                            self.flowing.append(right)
                            active.append(True)
                        active[j] = False

        self.flowing = list(compress(self.flowing,active))

        return (len(active) != active_len) or (sum(active) != active_len)

    def flow(self):
        while self.flow_step():
            pass

    def count_water(self):
        return np.sum(np.isin(self.ground[self.lowest_y:],[ord('|'),ord('~')]))

    def count_rest_water(self):
        return np.sum(self.ground[self.lowest_y:] == ord('~'))

if __name__ == "__main__":
    main()


from scipy.io import wavfile as wav
from os import walk
import os, glob, wave
import csv

path = 'C:/Users/Muhammad/Desktop/REU19/Talker_Speaker.csv'
audio_location = 'C:/Users/Muhammad/Desktop/REU19/lombardgrid/audio'

with open(path, 'a') as csv_file:
    csv_reader = csv.reader(csv_file, delimiter = ',')
    line_count = 0

    names = []
    duration = []

    # adds file name to list names
    for (dirpath, dirnames, filenames) in walk(audio_location):
        names.extend(filenames)

    # gets rid of file extension
    names = [os.path.splitext(x)[0] for x in names]

    # for each file in directory, add duration to list
    for soundfiles in glob.glob(os.path.join(audio_location, '*.wav')):
        (freq, sig) = wav.read(soundfiles)
        nframes = sig.size
        length_of_file = nframes/freq
        duration.append(length_of_file)

    print(names)
    print(duration)
    print('Length of f:       ', len(names))
    print('Length of duration:', len(duration))

    # clears csv
    clear_csv = open(path, 'w+')
    clear_csv.close()

    # names the first row
    fieldnames = ['File_Name',
                  'Speaker_1',
                  'Speaker_2',
                  'Speaker_3',
                  'Speaker_4',
                  'Speaker_5',
                  'Speaker_6',
                  'File_Name_Out_S1',
                  'File_Name_Out_S2',
                  'File_Name_Out_S3',
                  'File_Name_Out_S4',
                  'File_Name_Out_S5',
                  'File_Name_Out_S6',
                  'Duration']
    writer = csv.DictWriter(csv_file, fieldnames = fieldnames, lineterminator = '\n')
    writer.writeheader()

    for i in range(len(names)):
        writer.writerow({'File_Name' : names[i],
                         'Speaker_1' : 'N',
                         'Speaker_2' : 'N',
                         'Speaker_3' : 'N',
                         'Speaker_4' : 'N',
                         'Speaker_5' : 'N',
                         'Speaker_6' : 'N',
                         'File_Name_Out_S1' : names[i] + '_speaker_1',
                         'File_Name_Out_S2' : names[i] + '_speaker_2',
                         'File_Name_Out_S3' : names[i] + '_speaker_3',
                         'File_Name_Out_S4' : names[i] + '_speaker_4',
                         'File_Name_Out_S5' : names[i] + '_speaker_5',
                         'File_Name_Out_S6' : names[i] + '_speaker_6',
                         'Duration' : duration[i]})


from django.shortcuts import render, get_object_or_404, redirect
from blog.models import Post
from .models import Reply
from .forms import CommentForm, PostReplyForm

def post_comment(request, post_pk):
    post = get_object_or_404(Post, pk=post_pk)
    user = request.user
    if request.method == 'POST':
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = form.save(commit=False)
            comment.post = post
            comment.user = user
            comment.save()
            return redirect(post)
        else:
            comment_list = post.comment_set.all()
            context = {
                'post': post,
                'form': form,
                'comment_list': comment_list
            }
            return render(request, 'blog/detail.html', context=context)
    return redirect(post)

def post_reply(request, post_pk):
    post = get_object_or_404(Post, pk=post_pk)
    # post_comment = get_object_or_404(PostComent, pk= post_comment_pk)
    user = request.user
    if request.method == "POST":
        reply_form = PostReplyForm(request.POST)
        if reply_form.is_valid():
            reply_form = reply_form.save(commit=False)
            if request.POST['comment_reply']:
                reply_form.comment_reply = Reply.objects.filter(pk=request.POST['comment_reply'])[0]
            reply_form.user = user
            reply_form.save()

            return redirect(post)
        else:
            comment_list = post.comment_set.all()
            context = {
                'post': post,
                'form': CommentForm(),
                'reply_form': reply_form,
                'comment_list': comment_list
            }
        return render(request, 'blog/detail.html', context=context)
    return redirect(post)


"""Name hold a name choice for a Request
"""
# from . import db, ma
from marshmallow import fields
from sqlalchemy import event
from sqlalchemy.orm import backref
from sqlalchemy.orm.attributes import get_history

from namex.models import db, ma

class Name(db.Model):
    __tablename__ = 'names'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(1024), index=True)
    state = db.Column(db.String(15), default='NE')  # NE=Not Examined; R=Rejected; A=Accepted; C=Cond. Accepted
    choice = db.Column(db.Integer)
    designation = db.Column(db.String(50), default=None)
    consumptionDate = db.Column('consumption_date', db.DateTime(timezone=True))
    corpNum = db.Column('corp_num', db.String(10), default=None)
    remoteNameId = db.Column('remote_name_id', db.BigInteger)

    # Decision info
    conflict1 = db.Column(db.String(250), default='')  # optional conflict name
    conflict2 = db.Column(db.String(250), default='')  # optional conflict name
    conflict3 = db.Column(db.String(250), default='')  # optional conflict name
    conflict1_num = db.Column(db.String(250), default='')  # optional conflict name - corp or NR number
    conflict2_num = db.Column(db.String(250), default='')  # optional conflict name - corp or NR number
    conflict3_num = db.Column(db.String(250), default='')  # optional conflict name - corp or NR number
    decision_text = db.Column(db.String(1000), default='')

    nrId = db.Column('nr_id', db.Integer, db.ForeignKey('requests.id'), index=True)
    commentId = db.Column('comment_id', db.Integer, db.ForeignKey('comments.id'))
    # nameRequest = db.relationship('Request')

    # if a comment is added during decision, link it to the name record to be sent back to NRO
    comment = db.relationship("Comment", backref=backref("related_name", uselist=False), foreign_keys=[commentId])

    # Required for name request name analysis
    _name_type_cd = db.Column('name_type_cd', db.String(10))

    NOT_EXAMINED = 'NE'
    APPROVED = 'APPROVED'
    REJECTED = 'REJECTED'
    CONDITION = 'CONDITION'
    # Needed for name request reservation before completing the nr
    RESERVED = 'RESERVED'
    COND_RESERVE = 'COND-RESERVE'

    # Properties added for Name Request
    @property
    def name_type_cd(self):
        """Property containing the name type which is used by name Request."""
        return self._name_type_cd

    @name_type_cd.setter
    def name_type_cd(self, value: str):
        self._name_type_cd = value

    def as_dict(self):
        return {
            'id': self.id,
            'name': self.name,
            'name_type_cd': self.name_type_cd,
            'designation': self.designation,
            'choice': self.choice,
            'state': self.state,
            'conflict1': self.conflict1,
            'conflict2': self.conflict2,
            'conflict3': self.conflict3,
            'conflict1_num': self.conflict1_num,
            'conflict2_num': self.conflict2_num,
            'conflict3_num': self.conflict3_num,
            'decision_text': self.decision_text,
            'consumptionDate': self.consumptionDate.isoformat() if self.consumptionDate else None,
            'corpNum': self.corpNum,
            'comment': None if self.comment is None else self.comment.as_dict()
        }

    @classmethod
    def find_by_name(cls, name):
        return cls.query.filter_by(name=name).first()

    def save_to_db(self):
        # force uppercase names
        self.name = self.name.upper()

        db.session.add(self)
        db.session.commit()

    def add_to_db(self):
        db.session.add(self)

    def delete_from_db(self):
        db.session.delete(self)
        db.session.commit()

@event.listens_for(Name, 'after_insert')
@event.listens_for(Name, 'after_update')
def update_nr_name_search(mapper, connection, target):
    """Add any changes to the name to the request.nameSearch column and publish name state changes where applicable."""
    from flask.globals import current_app

    from namex.models import Event, Request, State
    from namex.services.audit_trail.event_recorder import EventRecorder

    name = target
    nr = Request.find_by_id(name.nrId)
    if nr:
        # get the names associated with the NR
        names_q = connection.execute(
            f"""
            SELECT names.name from names
            JOIN requests on requests.id = names.nr_id
            WHERE requests.id={nr.id}
            """
        )
        # format the names into a string like: |1<name1>|2<name2>|3<name3>
        names = [x[0] for x in names_q.all()]
        name_search = ''
        for item, index in zip(names, range(len(names))):
            name_search += f'|{index + 1}{item}{index + 1}|'
        # update the name_search field of the nr with the formatted string
        connection.execute(
            """
            UPDATE requests
            SET name_search=%s
            WHERE id=%s
            """,
            ('(' + name_search + ')', nr.id)
        )

        # set nr state to consumed
        name_consume_history = get_history(name, 'consumptionDate')
        current_app.logger\
            .debug('name_consume_history check - nrNum: {}, consumptionDate: {}, corpNum: {}, state: {}'
            .format(nr.nrNum, name.consumptionDate, name.corpNum, name.state))
        # Note: we cannot just check for a corpNum addition due to some Society change of name NRs coming over from
        #  NRO extractor providing a value for the corpNum field.
        if len(name_consume_history.added) \
                and name.consumptionDate \
                and name.corpNum \
                and name.state in ['APPROVED', 'CONDITION']:
            # Adding an after_flush_postexec to avoid connection and transaction closed issue's
            # Creating one time execution event when ever corpNum is added to a name
            # corpNum sets from nro-extractor job
            @event.listens_for(db.session, 'after_flush_postexec', once=True)
            def receive_after_flush_postexec(session, flush_context):
                nr = Request.find_by_id(name.nrId)
                nr.stateCd = State.CONSUMED
                nr.add_to_db()
                current_app.logger.debug('moved to CONSUMED state {}'.format(name.corpNum))
                EventRecorder.record_as_system(Event.UPDATE_FROM_NRO, nr, {
                    'id': nr.id,
                    'nrNum': nr.nrNum,
                    'stateCd': nr.stateCd
                }, True)
                current_app.logger.debug('moved to CONSUMED state event logged {}'.format(nr.nrNum))

class NameSchema(ma.SQLAlchemySchema):
    class Meta:
        model = Name
        fields = (
            'choice',
            'comment',
            'conflict1',
            'conflict2',
            'conflict3',
            'conflict1_num',
            'conflict2_num',
            'conflict3_num',
            'consumptionDate',
            'corpNum',
            'decision_text',
            'designation',
            'id',
            'name_type_cd',
            'name',
            'state'
        )

    conflict1 = fields.String(required=False, allow_none=True)
    conflict2 = fields.String(required=False, allow_none=True)
    conflict3 = fields.String(required=False, allow_none=True)
    conflict1_num = fields.Field(required=False, allow_none=True)
    conflict2_num = fields.Field(required=False, allow_none=True)
    conflict3_num = fields.Field(required=False, allow_none=True)
    decision_text = fields.String(required=False, allow_none=True)
    comment = fields.String(required=False, allow_none=True)
    consumptionDate = fields.DateTime(required=False, allow_none=True)
    corpNum = fields.String(required=False, allow_none=True)
    designation = fields.String(required=False, allow_none=True)
    name = fields.String(
        required=True,
        error_messages={'required': {'message': 'name is a required field'}}
    )
    name_type_cd = fields.String(required=False, allow_none=True)


import requests
import json
import jsonpath
import openpyxl
from DataDriven import Library

def test_add_multiple_students():

    # API
    API_URL = "http://thetestingworldapi.com/api/studentsDetails"
    file = open("/home/afzhal-ahmed-s/PycharmProjects/AddNewStudent.json")
    json_request = json.loads(file.read())

    obj = Library.Common("/home/afzhal-ahmed-s/Downloads/Noduco_Udemy_C-PPA.xlsx", 'Sheet1')
    col = obj.fetch_column_count()
    row = obj.fetch_row_count()
    keyList = obj.fetch_key_names()

    for i in range(2, row + 1):
        updated_json_request = obj.update_request_with_data(i, json_request, keyList)
        response = requests.post(API_URL, updated_json_request)
        print(response)