import sys, os, urllib.request, urllib.error, urllib.parse, logging, pwd
import subprocess, site, cgi, datetime, threading, copy, json
import uuid, time, re
from html import escape  # ***MUST COME before `from lxml import html`!***
from collections import defaultdict, OrderedDict
from lxml import html
from lxml.html import builder
from http.cookies import SimpleCookie
logging.basicConfig(
    level=logging.DEBUG if __debug__ else logging.INFO,
    format='%(asctime)s:%(levelname)s:%(name)s:%(message)s')
LOCK = threading.Lock()
try:  # command-line testing won't have module available
    import uwsgi
    #logging.debug('uwsgi: %s', dir(uwsgi))
except ImportError:
    uwsgi = type('uwsgi', (), {'opt': {}})  # object with empty opt attribute
    uwsgi.lock = LOCK.acquire
    uwsgi.unlock = LOCK.release
#logging.debug('uwsgi.opt: %s', repr(uwsgi.opt))
#logging.debug('sys.argv: %s', sys.argv)  # only shows [uwsgi]
# 2017-12-28 set `chdir` option in pyturn.uwsgi so now PWD should be correct
#logging.debug('current working directory: %s', os.path.abspath('.'))  # was '/'
# so we can see that sys.argv is useless for uwsgi operation
THISDIR = os.path.dirname(uwsgi.opt.get('wsgi-file', b'').decode())
if THISDIR and os.getcwd() != THISDIR:
    logging.warning('having to chdir from %s to %s', os.getcwd(), THISDIR)
    os.chdir(THISDIR)
else:
    logging.warning('THISDIR: %s, os.getcwd(): %s', THISDIR, os.getcwd())
APPDIR = (uwsgi.opt.get('check_static', b'').decode() or
          os.path.join(THISDIR, 'html'))
MIMETYPES = {'png': 'image/png', 'ico': 'image/x-icon', 'jpg': 'image/jpeg',
             'jpeg': 'image/jpeg',}
DATA = {
    'groups': {},  # active groups
    'finished': {},  # inactive groups (for "Report" page)
}
HTTPSESSIONS = {}  # data like username, linked with session keys, goes here
EXPECTED_ERRORS = (
    NotImplementedError,
    ValueError,
    KeyError,
    IndexError,
    SystemError,
)
PARSED = html.parse(os.path.join(APPDIR, 'index.html')).getroot()
PAGE = html.tostring(PARSED.getroottree())
DEBUG = ['all']  # populate from querystring
# create translation table of illegal characters for groupnames
# ":" is used in this program for internal purposes, so disallow that
# "/" cannot be allowed because we create a filename from groupname
# otherwise, mostly being permissive
ILLEGAL = str.maketrans(dict.fromkeys('''([{:/'"}])'''))

def debug(category, *args):
    '''
    log debug code only for given category

    reduces log size and allows for granular approach to debugging
    '''
    if not __debug__:
        return
    elif category in DEBUG:
        logging.debug(*args)

def findpath(env):
    '''
    locate directory where files are stored, and requested file

    side effect: splits off querystring and stores its debug values in DEBUG

    NOTE: DEBUG is a global and as such will be affected by any client adding
    `debug=` args to his querystring. so the net result in debugging will be
    the union of what all the clients request.
    '''
    start = APPDIR
    parsed = urllib.parse.urlparse(
        urllib.parse.unquote(env.get('REQUEST_URI', '')))
    if parsed.query:
        query = urllib.parse.parse_qs(parsed.query or '')
        DEBUG[:] = list(set(DEBUG) | set(query.get('debug', [])))
    debug('all', 'findpath: start: %s' % start)
    path = urllib.parse.unquote(env.get('HTTP_PATH', ''))
    #debug('all', 'path, attempt 1: %s', path)
    path = path or parsed.path
    #debug('all', 'path, attempt 2: %s', path)
    path = (path or '/').lstrip('/')
    debug('all', 'findpath: should not be None at this point: "%s"', path)
    return start, path

def loadpage(path, data=None):
    '''
    input template and populate the HTML with data array

    eventually client-side JavaScript will perform many of these functions.
    '''
    data = data or DATA
    parsed = html.fromstring(PAGE)
    postdict = data.get('postdict', {})
    debug('load', 'loadpage: postdict: %s', postdict)
    set_values(parsed, postdict,
               ['username', 'groupname', 'httpsession_key', 'joined'])
    if 'groups' in data:
        groups = populate_grouplist(parsed, data)
    else:
        groups = []
    debug('load', 'loadpage: groups: %s', groups)
    # only show load indicator if no path specified;
    # get rid of meta refresh if path has already been chosen
    if path == '':
        debug('load', 'showing load indicator')
        hide_except('loading', parsed)
        return html.tostring(parsed).decode()
    else:
        for tag in parsed.xpath('//meta[@http-equiv="refresh"]'):
            tag.getparent().remove(tag)
    if 'text' in postdict:
        message = builder.PRE(postdict['text'])
        parsed.xpath('//div[@id="error-text"]')[0].append(message)
        debug('load', 'showing error page')
        hide_except('error', parsed)
    elif postdict.get('joined'):
        debug('join', 'found "joined": %s', data['postdict'])
        group = sanitize(postdict['groupname'])
        if not group in groups:
            if not group in data['finished']:
                debug('all', 'nonexistent group, showing joinform again')
                hide_except('joinform', parsed)
            else:
                create_report(parsed, group, data)
                debug('all', 'showing report page')
                hide_except('report', parsed)
        else:
            groupdata = data['groups'][group]
            speaker = select_speaker(group, data)
            userdata = groupdata['participants'][postdict['username']]
            remaining = groupdata['talksession']['remaining']
            set_text(parsed, ['talksession-speaker'],
                     ['Current speaker is %s' % speaker if speaker else
                      'Waiting for next speaker'])
            set_text(parsed, ['talksession-time'], [formatseconds(remaining)])
            debug('talk', 'userdata[request]: %.6f', userdata['request'])
            buttonvalue = 'Cancel request' if userdata['request'] else 'My Turn'
            debug('talk', 'setting buttonvalue to %s', buttonvalue)
            set_button(parsed, ['myturn-button'], [buttonvalue])
            debug('talk', 'showing talk page')
            hide_except('talksession', parsed)
    elif (postdict.get('submit') == 'Join' and postdict.get('username') and
          postdict.get('group', '') == ''):
        # some browsers won't return `group` in postdict at all if
        # selected element is empty (as it is by default in this case)
        debug('join', 'showing groupform after joinform')
        hide_except('groupform', parsed)
    else:
        debug('load', 'showing joinform by default')
        hide_except('joinform', parsed)
    return html.tostring(parsed).decode()

def create_report(parsed=None, group=None, data=None, **formatting):
    '''
    show participants with the amount of time each spoke

    >>> parsed = html.fromstring("""
    ...

    ...

    ...

    ...

    ...
Name	Elapsed Time

    ...
(none)	00:00:00

    ...

    ...

    ...

    ...
""")
    >>> data = json.loads("""{"finished": {"test": {"groupname": "test",
    ...  "participants": {"jc": {"spoke": 48.5}, "Ed": {"spoke": 3.25}}}}}""")
    >>> formatting = {'pretty_print': True, 'with_tail': False}
    >>> print(create_report(parsed, 'test', data, **formatting).decode('utf8'))

    Name
    Elapsed Time

    jc
    00:00:48

    Ed
    00:00:03

    <BLANKLINE>
    '''
    parsed = parsed if parsed is not None else copy.deepcopy(PARSED)
    data = data or DATA
    body_div = parsed.xpath('//*[@id="report-body"]')[0]
    rows = body_div.xpath('.//table/tr')
    debug('report', 'create_report: rows: %s', rows)
    template = rows[1]
    table = template.getparent()
    table.remove(template)
    try:
        participants = data['finished'][group]['participants']
    except KeyError as nosuchgroup:
        logging.warning('No such group %s', nosuchgroup)
        participants = {}
    speakers = sorted(participants, key=lambda u: -participants[u]['spoke'])
    columns = template.xpath('./td')
    debug('report', 'create_report: speakers: %s', speakers)
    for speaker in speakers:
        debug('report', 'adding speaker "%s" to report', speaker)
        columns[0].text = speaker
        columns[1].text = formatseconds(participants[speaker]['spoke'])
        debug('report', 'template now: %s', html.tostring(template))
        table.append(html.fromstring(html.tostring(template)))
        debug('report', 'table now: %s', html.tostring(table))
    return html.tostring(body_div, **formatting)

def set_text(parsed, idlist, values):
    '''
    pre-set page text
    '''
    debug('all', 'setting values of %s from %s', idlist, values)
    for index in range(len(idlist)):
        elementid = idlist[index]
        value = values[index]
        element = parsed.xpath('//*[@id="%s"]' % elementid)[0]
        debug('all', 'before: %s', html.tostring(element))
        element.text = value
        debug('all', 'after: %s', html.tostring(element))

def set_button(parsed, idlist, values):
    '''
    modify button values

    >>> content = html.fromstring('
<input id="test" value="Test">
')
    >>> set_button(content, ['test'], ['new value'])
    >>> content.xpath('//*[@id="test"]')[0].get('value')
    'new value'
    '''
    for index in range(len(idlist)):
        elementid = idlist[index]
        value = values[index]
        element = parsed.xpath('//*[@id="%s"]' % elementid)[0]
        debug('buttons', 'before: %s', html.tostring(element))
        element.set('value', value)
        debug('buttons', 'after: %s', html.tostring(element))

def set_values(parsed, postdict, fieldlist):
    '''
    pre-set form input values from postdict
    '''
    debug('hidden', 'setting values of %s from %s', fieldlist, postdict)
    for fieldname in fieldlist:
        value = postdict.get(fieldname, '')
        if not value:
            debug('hidden', 'skipping %s, no value found', fieldname)
            continue
        elements = parsed.xpath('//input[@name="%s"]' % fieldname)
        for element in elements:
            debug('hidden', 'before: %s', html.tostring(element))
            element.set('value', value)
            debug('hidden', 'after: %s', html.tostring(element))

def populate_grouplist(parsed=None, data=None, formatted='list', **options):
    '''
    fill in 'select' element with options for each available group

    if `formatted` is 'list', just return list of groups, oldest first

    >>> options = {'pretty_print': True, 'with_tail': False}
    >>> data = {'groups': {'test': {'timestamp': 0}, 'again': {'timestamp': 1}}}
    >>> print(populate_grouplist(None, data, 'element', **options))
    <select id="group-select" name="group" data-contents=":test:again">
            <option value="">(Create new group)</option>
           <option value="test">test</option>
    <option value="again" selected>again</option></select>
    <BLANKLINE>
    >>> data['groups']['test']['timestamp'] = 2
    >>> populate_grouplist(None, data)
    ['again', 'test']
    '''
    # sorting a dict gives you a list of keys
    data = data or DATA
    session_key = data.get('httpsession_key', None)
    session = HTTPSESSIONS.get(session_key, {})
    added_group = session.get('added_group', None)
    parsed = parsed if parsed is not None else html.fromstring(PAGE)
    groups = sorted(data['groups'],
                    key=lambda g: data['groups'][g]['timestamp'])
    contents = ':'.join([''] + groups)
    grouplist = parsed.xpath('//select[@name="group"]')[0]
    debug('grouplist', 'populate_grouplist: %s', grouplist)
    for group in groups:
        newgroup = builder.OPTION(group, value=group)
        grouplist.append(newgroup)
    # make newest group the "selected" one
    # except for someone who just created a group, mark *that* one selected
    for group in grouplist.getchildren():
        try:
            del group.attrib['selected']
        except KeyError:
            pass
    try:
        grouplist[grouplist.index(added_group)].set('selected', 'selected')
    except (KeyError, ValueError, IndexError, TypeError):
        grouplist[-1].set('selected', 'selected')
    grouplist.set("data-contents", contents)
    if formatted == 'list':
        return groups
    else:
        return html.tostring(grouplist, **options).decode()

def hide_except(keep, tree):
    '''
    set "display: none" for all sections of the page we don't want to see
    '''
    for page in tree.xpath('//div[@class="body"]'):
        if not page.get('id').startswith(keep):
            page.set('style', 'display: none')
        elif 'style' in page.attrib:
            del page.attrib['style']

def data_merge(data, cookie):
    '''
    anything missing in data['postdict'] gets set from cookie if found
    '''
    if cookie:
        if not data['postdict'].get('username'):
            logging.debug('data_merge: setting username from cookie')
            data['postdict']['username'] = cookie['username'].value
        else:
            logging.debug('data_merge: found username already in postdict')
        if not data['postdict'].get('http_sessionkey'):
            logging.debug('data_merge: setting session key from cookie')
            data['postdict']['http_sessionkey'] = cookie['sessionid'].value
        else:
            logging.debug('data_merge: session key already in postdict')
    else:
        logging.debug('data_merge: cookie: %r, postdict: %s',
                      cookie, data.get('postdict'))

def server(env=None, start_response=None):
    '''
    primary server process, sends page with current groups list
    '''
    status_code, mimetype, page = '500 Server error', 'text/html', '(Unknown)'
    start, path = findpath(env)
    cookie, data = handle_post(env)
    logging.debug('server: cookie: %s', cookie)
    data_merge(data, cookie)  # set any missing data from cookie
    debug('all', 'server: data: %s', data)
    if path in ('groups',):
        page = populate_grouplist(None, data, formatted='element')
        status_code = '200 OK'
    elif path.startswith('report/'):
        group = path.split('/')[1]
        page = create_report(group=group).decode('utf8')
        status_code = '200 OK'
    elif path.startswith('groups/'):
        group = path.split('/')[1]
        try:
            page = json.dumps(data['groups'][group])
        except KeyError as groupname:
            debug('all', 'group %s does not exist in %s', groupname, data)
            page = '{}'
        status_code = '200 OK'
    elif path in ('', 'noscript', 'app'):
        page = loadpage(path, data)
        status_code = '200 OK'
    elif path == 'status':
        page = escape(json.dumps(data))
        status_code = '200 OK'
    else:
        try:
            page, mimetype = render(os.path.join(start, path))
            status_code = '200 OK'
        except (IOError, OSError) as filenotfound:
            status_code = '404 File not found'
            page = '
No such page: %s
' % str(filenotfound)
    headers = [('Content-type', mimetype)]
    if cookie is not None:
        logging.debug('setting cookie headers %r', cookie.output())
        headers.extend(cookie_headers(cookie))
    start_response(status_code, headers)
    debug('all', 'page: %s', page[:128])
    return [page.encode('utf8')]

def cookie_headers(cookie):
    '''
    make list of tuples for cookie values

    >>> cookie = SimpleCookie()
    >>> cookie['test'] = 'this'
    >>> cookie['test']['path'] = '/'
    >>> cookie_headers(cookie)
    [('Set-Cookie', 'test=this; Path=/')]
    '''
    cookies = cookie.output().split('\r\n')
    return [tuple(re.compile(': ').split(c, 1)) for c in cookies]

def handle_post(env):
    '''
    process the form submission and return data structures

    note what dict(parse_qsl(formdata)) does:

    >>> from urllib.parse import parse_qsl
    >>> parse_qsl('a=b&b=c&a=d&a=e')
    [('a', 'b'), ('b', 'c'), ('a', 'd'), ('a', 'e')]
    >>> OrderedDict(_)
    OrderedDict([('a', 'e'), ('b', 'c')])
    >>>

    so only use it where you know that no key will have more than
    one value.

    parse_qs will instead return a dict of lists.
    '''
    uwsgi.lock()  # lock access to DATA global
    worker = getattr(uwsgi, 'worker_id', lambda *args: None)()
    DATA['handler'] = (worker, env.get('uwsgi.core'))
    timestamp = datetime.datetime.utcnow().timestamp()
    cookie = SimpleCookie(env['HTTP_COOKIE']) if 'HTTP_COOKIE' in env else None
    try:
        if env.get('REQUEST_METHOD') != 'POST':
            DATA['postdict'] = {}
            return cookie, copy.deepcopy(DATA)
        form = cgi.FieldStorage(fp=env['wsgi.input'], environ=env)
        DATA['postdict'] = postdict = {k: form.getfirst(k) for k in form.keys()}
        debug('all', 'handle_post: %s, postdict: %s', form, postdict)
        # [groupname, total, turn] and submit=Submit if group creation
        # [username, group] and submit=Join if joining a group
        postdict['timestamp'] = timestamp
        if not postdict.get('httpsession_key'):
            postdict['httpsession_key'] = uuid.uuid4().hex
            debug('sessions', 'set httpsession_key = %s',
                  postdict['httpsession_key'])
        try:
            buttonvalue = postdict['submit']
        except KeyError:
            raise ValueError('No "submit" button found')
        cookie = update_httpsession(postdict)
        if buttonvalue == 'Join':
            # username being added to group
            # don't allow if name already in group
            groups = DATA['groups']
            debug('join', 'processing Join: %s', postdict)
            username = postdict.get('username', '')
            group = sanitize(postdict.get('group', ''))
            if not username:
                raise ValueError('Name field cannot be empty')
            elif group in groups:
                postdict['groupname'] = group
                if username in groups[group]['participants']:
                    raise ValueError('"%s" is already a member of %s' % (
                        username, group))
                groups[group]['participants'][username] = defaultdict(
                    float,  # for `speaking` and `spoke` times
                    {'timestamp': timestamp, 'requests': []}
                )
                postdict['joined'] = '%s:%s' % (username, group)
                if 'talksession' not in groups[group]:
                    groups[group]['talksession'] = {
                        'start': timestamp,
                        'speaker': None,
                        'tick': 0,
                    }
                    counter = threading.Thread(
                        target=countdown,
                        name=group,
                        args=(group,))
                    counter.daemon = True  # leave no zombies on exit
                    counter.start()
            # else group not in groups, no problem, return to add group form
            return cookie, copy.deepcopy(DATA)
        elif buttonvalue == 'Submit':
            # groupname, total (time), turn (time) being added to groups
            # don't allow if groupname already being used
            groups = DATA['groups']
            group = postdict['groupname'] = sanitize(postdict['groupname'])
            if not group in groups:
                groups[group] = postdict
                groups[group]['participants'] = {}
                return cookie, copy.deepcopy(DATA)
            else:
                raise ValueError((
                    'Group {group[groupname]} already exists with total time '
                    '{group[total]} minutes and turn time '
                    '{group[turn]} seconds').format(group=groups[group]))
        elif buttonvalue == 'OK':
            # affirming receipt of error message or Help screen
            return cookie, copy.deepcopy(DATA)
        elif buttonvalue == 'Help':
            raise UserWarning('Help requested')
        elif buttonvalue == 'My Turn':
            # attempting to speak in ongoing session
            # this can be reached either by normal HTML form submission
            # or by XHR from JavaScript on client side
            debug('button', 'My Turn button pressed, env: %s', env)
            groups = DATA['groups']
            group = sanitize(postdict['groupname'])
            username = postdict['username']
            try:
                userdata = groups[group]['participants'][username]
                if not userdata['request']:
                    debug('button', "userdata: setting %s's request to %.6f",
                          username, timestamp)
                    userdata['request'] = timestamp
                    userdata['requests'].append([timestamp, None])
                else:
                    logging.warning('ignoring newer request %.6f, '
                                    'keeping %.6f', userdata['request'],
                                    timestamp)
            except KeyError:
                raise SystemError('Group %s is no longer active' % group)
            return cookie, copy.deepcopy(DATA)
        elif buttonvalue == 'Cancel request':
            debug('button', 'My Turn button released')
            groups = DATA['groups']
            group = sanitize(postdict['groupname'])
            username = postdict['username']
            try:
                userdata = groups[group]['participants'][username]
                if userdata['request']:
                    userdata['request'] = None
                    userdata['requests'][-1][1] = timestamp
                else:
                    logging.error('no speaking request found for %s', username)
            except KeyError:
                raise SystemError('Group %s is no longer active' % group)
            return cookie, copy.deepcopy(DATA)
        elif buttonvalue == 'Check status':
            return cookie, copy.deepcopy(DATA)
        else:
            raise ValueError('Unknown form submitted')
    except UserWarning as request:
        if str(request) == 'Help requested':
            debug('all', 'displaying help screen')
            DATA['postdict']['text'] = read(os.path.join(THISDIR, 'README.md'))
            return cookie, copy.deepcopy(DATA)
    except EXPECTED_ERRORS as failed:
        debug('all', 'displaying error: "%r"', failed)
        DATA['postdict']['text'] = repr(failed)
        return cookie, copy.deepcopy(DATA)
    finally:
        uwsgi.unlock()

def most_eligible_speaker(group, data=None):
    '''
    participant who first requested to speak who has spoken least

    >>> data = {
    ...  'groups': {
    ...   'test': {
    ...    'participants': {
    ...     'alice': {'spoke': 3, 'request': '2017-10-01T14:21:37.024529'},
    ...     'bob': {'spoke': 2, 'request': '2017-10-01T14:21:37.024531'},
    ...     'chuck': {'spoke': 3, 'request': '2017-10-01T14:21:37.024530'}}}}}
    >>> most_eligible_speaker('test', data)
    'bob'
    >>> data = {
    ...  'groups': {
    ...   'test': {
    ...    'participants': {
    ...     'alice': {'spoke': 2, 'request': '2017-10-01T14:21:37.024531'},
    ...     'bob': {'spoke': 2, 'request': '2017-10-01T14:21:37.024531'},
    ...     'chuck': {'spoke': 2, 'request': '2017-10-01T14:21:37.024530'}}}}}
    >>> most_eligible_speaker('test', data)
    'chuck'
    '''
    data = data or DATA
    groupdata = data['groups'][group]
    people = groupdata['participants']
    waiting = filter(lambda p: people[p]['request'], people)
    speaker_pool = sorted(waiting, key=lambda p:
                          (people[p]['spoke'], people[p]['request']))
    return (speaker_pool or [None])[0]

def select_speaker(group, data=None):
    '''
    let current speaker finish his turn before considering most eligible

    SIDE EFFECTS:
        when `turn` time is up or speaker voluntarily relinquishes turn:
            sets speaker's `speaking` count to zero in data dict
            sets speaker to new speaker

    NOTE: not using uwsgi.lock for this, shouldn't be necessary. no
    possible race conditions are known at time of coding (jc).
    '''
    data = data or DATA
    groupdata = data['groups'][group]
    talksession = groupdata['talksession']
    turntime = float(groupdata['turn'])
    if talksession['speaker']:
        speaker = groupdata['participants'][talksession['speaker']]
        if speaker['speaking'] >= turntime or not speaker['request']:
            speaker['speaking'] = 0
            talksession['speaker'] = most_eligible_speaker(group, data)
    else:
        talksession['speaker'] = most_eligible_speaker(group, data)
    return talksession['speaker']

def sanitize(name):
    '''
    can't count on someone entering, say, '../../../.hidden/evil' as groupname

    in addition to ILLEGAL characters, also strip leading '.' and '-',
    the first hiding the file from normal listing, the second making removal
    difficult because it looks like an option to rm, so one needs to
    `rm -- -evilfile`.
    >>> sanitize('../../../.-hidden/::evil')
    'hiddenevil'

    >>> sanitize(None)
    '''
    return name.translate(ILLEGAL).lstrip('-.') if name is not None else None

def countdown(group, data=None):
    '''
    expire the talksession after `minutes`

    currently only using uwsgi.lock() when moving group to `finished`.
    may need to reevaluate that (jc).

    >>> now = datetime.datetime.utcnow().timestamp()
    >>> data = {'finished': {}, 'groups': {
    ...         'test': {
    ...          'total': '.001',
    ...          'talksession': {'start': now, 'speaker': None},
    ...          'participants': {'nobody': {'requests': [[0.1, 0.2]]}},
    ...         }}}
    >>> countdown('test', data)
    '''
    data = data or DATA
    groups = data['groups']
    sleeptime = .25  # seconds
    try:
        minutes = float(groups[group]['total'])
        groups[group]['talksession']['remaining'] = minutes * 60
        ending = (datetime.datetime.fromtimestamp(
            groups[group]['talksession']['start']) +
                  datetime.timedelta(minutes=minutes)).timestamp()
        debug('countdown', 'countdown ending: %.6f', ending)
        while True:
            time.sleep(sleeptime)
            now = datetime.datetime.utcnow().timestamp()
            debug('countdown', 'countdown now: %.6f', now)
            if now > ending:
                debug('countdown', 'countdown ended at %.6f', now)
                break
            speaker = select_speaker(group, data)
            debug('countdown', 'countdown: speaker: %s', speaker)
            if speaker:
                speakerdata = groups[group]['participants'][speaker]
                speakerdata['speaking'] += sleeptime
                speakerdata['spoke'] += sleeptime
            groups[group]['talksession']['remaining'] -= sleeptime
            groups[group]['talksession']['tick'] += 1
        # should we uwsgi.lock() here in case group is currently being updated?
        # if so, need uwsgi.unlock() in `finally` clause
        data['finished'][group] = data['groups'].pop(group)
        # now save the report of clicks, not same as report of time spoken
        reportdir = os.path.join('statistics', group)
        reportname = os.path.join(reportdir, '%.6f.json' % now)
        try:
            participants = data['finished'][group]['participants']
        except KeyError:
            logging.error("No such key 'participants' in %s",
                          data['finished'][group])
            return
        os.makedirs(reportdir, exist_ok=True)
        report = open(reportname, 'w')
        report.write(json.dumps([{speaker: participants[speaker]['requests']}
                                 for speaker in participants],
                                indent=4))
        report.close()
    except KeyError as error:
        logging.error('countdown: was group "%s" removed? KeyError: %s',
                      group, error, exc_info=True)
        logging.info('data: %s', data)

def update_httpsession(postdict):
    '''
    simple implementation of user (http) sessions

    this is for keeping state between client and server, this is *not*
    the same as discussion (talk) sessions!

    another thread should go through and remove expired httpsessions
    '''
    # FIXME: this session mechanism can only be somewhat secure with https
    # FIXME: a thread needs to remove old httpsessions to save memory
    timestamp = postdict['timestamp']
    cookie = None
    if 'httpsession_key' in postdict and postdict['httpsession_key']:
        session_key = postdict['httpsession_key']
        # only bother storing session once a username has been entered
        if postdict.get('username', None):
            username = postdict['username']
            newgroup = sanitize(postdict.get('group', None))
            if session_key in HTTPSESSIONS:
                if HTTPSESSIONS[session_key]['username'] != username:
                    logging.warning(
                        'changing session username from "%s" to "%s"',
                        HTTPSESSIONS[session_key]['username'],
                        username)
                if newgroup:
                    HTTPSESSIONS[session_key]['added_group'] = newgroup
                HTTPSESSIONS[session_key]['updated'] = timestamp
            else:
                HTTPSESSIONS[session_key] = {
                    'timestamp': timestamp,
                    'updated': timestamp,
                    'added_group': None,
                    'username': username}
            cookie = SimpleCookie()
            cookie['sessionid'] = session_key
            cookie['sessionid']['path'] = '/'
            logging.debug('cookie: %s', cookie)
            cookie['username'] = username
            cookie['username']['path'] = '/'
            logging.debug('cookie: %s', cookie)
        else:
            debug('sessions',
                  'no username yet associated with session %s', session_key)
    else:
        logging.warning('no httpsession_key in POST')
    return cookie

def render(pagename, standalone=True):
    '''
    Return content with Content-type header
    '''
    debug('render', 'render(%s, %s) called', pagename, standalone)
    if pagename.endswith('.html'):
        debug('render', 'rendering static HTML content')
        return (read(pagename), 'text/html')
    elif not pagename.endswith(('.png', '.ico', '.jpg', '.jpeg')):
        # assume plain text
        logging.warning('app is serving %s instead of nginx', pagename)
        return (read(pagename), 'text/plain')
    elif standalone:
        logging.warning('app is serving %s instead of nginx', pagename)
        return (read(pagename),
                MIMETYPES.get(os.path.splitext(pagename)[1], 'text/plain'))
    else:
        logging.error('not standalone, and no match for filetype')
        raise OSError('File not found: %s' % pagename)

def read(filename):
    '''
    Return contents of a file
    '''
    debug('read', 'read: returning contents of %s', filename)
    with open(filename) as infile:
        data = infile.read()
        debug('read', 'data: %s', data[:128])
        return data

def formatseconds(seconds):
    '''
    return rounded-up seconds count as HH:MM:SS

    https://stackoverflow.com/a/31946730/493161

    >>> formatseconds(666.50001)
    '00:11:07'
    '''
    return '{:0>8}'.format(str(datetime.timedelta(seconds=round(seconds))))

if __name__ == '__main__':
    print(server(os.environ, lambda *args: None))


#! /usr/bin/python3

import numpy as np
from matplotlib import pyplot as plt

# Simple Euler forward

# Input variables
Q = 10.
b = [20.]
S = 1E-2
D = 2E-2
h_b = 4
intermittency = 1

# Constants
phi = 3.97
g = 9.805
rho_s = 2700.
rho = 1000.
tau_star_crit = 0.0495

# Derived variables
a1 = 1. / h_b
a2 = S**0.7 / ( 2.9 * (rho_s - rho)/rho * g**0.3 * D**0.9 )
kh = D**.1 / (2.9 * g**.3 * S**.3)

# Starting values
t = [0.]
dt = 10000
nt = 120

# Equilibrium width?
beq = 0.17 / ( g**.5 * ((rho_s - rho)/rho)**(5/3.) * 1.2**(5/3.)
               * tau_star_crit**(5/3.) ) * Q * S**(7/6.) / D**1.5

# Depth?
h = kh * (Q/b[-1])**0.6

# Tau*
tau_star_bed = h * S / ( ((rho_s - rho)/rho) * D)
tau_star_bank = tau_star_bed / 1.2

# Compute through time
for i in range(nt):
    bi = b[-1]
    tau_star_bank = a2 * (Q/bi)**(3/5.) / 1.2
    if tau_star_bank > tau_star_crit:
        bi += a1 * ( tau_star_bank - tau_star_crit )**(3/2.) \
                 * dt * intermittency
    else:
        b = beq
        break
    b.append(bi)
    t.append(t[-1] + dt)

t = np.array(t)
b = np.array(b)

plt.figure()
plt.hlines(beq, t[0] / (24.*60.*60.), t[-1] / (24.*60.*60.),
           '.5', label='Equilibrium width', linewidth=2)
plt.plot(t / (24.*60.*60.), b, 'k-', label='Transient width',
         linewidth=2)
plt.xlabel('Flood duration [days]')
plt.ylabel('Channel width [m]')
plt.legend()
plt.tight_layout()


# 图形画布
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib  # 导入图表模块
import matplotlib.pyplot as plt # 导入绘图模块

class PlotCanvas(FigureCanvas):

    def __init__(self, parent=None, width=0, height=0, dpi=100):
        # 避免中文乱码
        matplotlib.rcParams['font.sans-serif'] = ['SimHei']   #选择字体为SimHei
        matplotlib.rcParams['axes.unicode_minus'] = False     #处理坐标抽轴线的负刻度值情况
        # 创建图形
        fig = plt.figure(figsize=(width, height), dpi=dpi)
        # 初始化图形画布
        FigureCanvas.__init__(self, fig)
        self.setParent(parent)  # 设置父类

    # 折线图
    def broken_line(self,number,train_list):
        '''
        linewidth:折线的宽度
        marker：折点的形状
        markerfacecolor：折点实心颜色
        markersize：折点大小
        number：车票数量
        train_list：车次
        '''
        #enumerate内建序列函数，返回（i,value）索引和值
        day_x = ['第二天', '第三天','第四天','第五天', '第六天']  # X轴折线点
        for index, n in enumerate(number):
            plt.plot(day_x, n, linewidth=1, marker='o',
                     markerfacecolor='blue', markersize=8, label=train_list[index])  # 绘制折线marker:折点，label:图例
        plt.grid(linestyle=":")
        plt.legend(bbox_to_anchor=(-0.03,1))                                         # 让图例生效，并设置图例显示位置
        plt.title('卧铺车票数量走势图')  # 标题名称
    # bbox_to_anchor(num1, num2),  bbox_to_anchor被赋予的二元组中，第一个数值用于控制legend的左右移动，值越大越向右边移动，第二个数值用于控制legend的上下移动，值越大，越向上移动。


import sys
from pathlib import Path

import cv2
import imutils
import numpy as np

FILE = Path(__file__).resolve()
ROOT = FILE.parents[1]

if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))

code_dir_path = ROOT.joinpath("Code")
data_dir_path = ROOT.joinpath("Data")

class BlurTool:
    def __init__(self):

        cfg_path = code_dir_path.joinpath("yolov7.cfg")
        weight_path = code_dir_path.joinpath("yolov7.weights")
        self.net = cv2.dnn.readNetFromDarknet(str(cfg_path), str(weight_path))
        self.layers = [(self.net.getLayerNames()[i - 1]) for i in self.net.getUnconnectedOutLayers()]

    def _check_file(self, file_name):

        self.file_path = data_dir_path.joinpath(file_name)
        assert self.file_path.exists(), "File does not exist!"

        suffix = self.file_path.suffix
        if suffix in [".png", ".jpg"]:
            self.is_video = False
        elif suffix in [".mp4"]:
            self.is_video = True
        else:
            raise ValueError(f"No Support for the Format: {suffix}")

    def process(self, file_name="LP.png"):

        self._check_file(file_name)
        if not self.is_video:
            self.process_image()
        else:
            self.process_video()

    def process_image(self):
        frame = cv2.imread(str(self.file_path))
        vehicles = self.detect_vehicle(frame)
        # for vehicle in vehicles:
        #     xmin, xmax, ymin, ymax = vehicle
        #     self.detect_license_plate(frame[ymin: ymax, xmin: xmax])

        xmin, xmax, ymin, ymax = vehicles[1]
        self.detect_license_plate(frame[ymin: ymax, xmin: xmax])

        # print(vehicles)

    def process_video(self):
        cap = cv2.VideoCapture()

    def detect_vehicle(self, frame):

        frame_height, frame_width = frame.shape[:2]

        vehicle_classes = [2, 3, 5, 7]
        vehicle_boxes, vehicle_scores, vehicles = [], [], []

        vehicle_conf_thr, vehicle_nms_thr = 0.5, 0.2
        # ?: size
        # preprocess -> set input -> forward
        blob = cv2.dnn.blobFromImage(frame, scalefactor=1 / 255, size=(320, 320), mean=[0, 0, 0], swapRB=True)
        self.net.setInput(blob)
        outputs = self.net.forward(self.layers)

        for output in outputs:
            for detection in output:
                # [x, y, w, h, conf, score1, score2, ..., score80]
                scores = detection[5:]
                class_id = np.argmax(scores)
                conf = scores[class_id]
                if (class_id in vehicle_classes) and (conf > vehicle_conf_thr):
                    x, y = detection[0] * frame_width, detection[1] * frame_height
                    w, h = detection[2] * frame_width, detection[3] * frame_height
                    xmin, xmax = x - w / 2, x + w / 2
                    ymin, ymax = y - h / 2, y + h / 2

                    vehicle_boxes.append((xmin, xmax, ymin, ymax))
                    vehicle_scores.append(float(conf))

        #  postprocess: nms -> size filter
        vehicle_indices = cv2.dnn.NMSBoxes(vehicle_boxes, vehicle_scores, vehicle_conf_thr, vehicle_nms_thr)
        for index in vehicle_indices:
            xmin, xmax, ymin, ymax = map(int, vehicle_boxes[index])
            if (xmax - xmin) * (ymax - ymin) >= frame_width * frame_height * 0.03:
                # [(xmin, xmax, ymin, ymax), ...]
                vehicles.append((xmin, xmax, ymin, ymax))

        return vehicles

    def detect_license_plate(self, frame):

        license_plate = None
        # convert to grey scale -> reduce noise -> detect edges
        frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        frame_reduce_noise = cv2.bilateralFilter(frame_gray, d=13, sigmaColor=15, sigmaSpace=15)
        frame_edge = cv2.Canny(frame_reduce_noise, threshold1=30, threshold2=200)

        contours = imutils.grab_contours(cv2.findContours(frame_edge.copy(), mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_SIMPLE))
        # check which one has a rectangle shape (4 sides) and closed figure
        for cnt in sorted(contours, key=cv2.contourArea, reverse=True)[:5]:
            peri = cv2.arcLength(curve=cnt, closed=True)
            approx = cv2.approxPolyDP(curve=cnt, epsilon=0.1 * peri, closed=True)
            if len(approx) == 4:
                x, y, w, h = cv2.boundingRect(approx)
                license_plate = (x, y, x + w, y + h)
                break

        return license_plate
        # # open -> threshold -> edge detection
        # frame_open = cv2.morphologyEx(frame_reduce_noise, op=cv2.MORPH_OPEN, kernel=np.ones((23, 23), np.uint8))
        # frame_add_weight = cv2.addWeighted(src1=frame_reduce_noise, alpha=1, src2=frame_open, beta=-1, gamma=0)
        # _, frame_thresh = cv2.threshold(frame_add_weight, thresh=0, maxval=255, type=cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        # frame_edge = cv2.Canny(frame_thresh, threshold1=100, threshold2=200)

        # frame_edge = cv2.morphologyEx(frame_edge, op=cv2.MORPH_CLOSE, kernel=np.ones((10, 10), np.uint8))
        # frame_edge = cv2.morphologyEx(frame_edge, cv2.MORPH_OPEN, kernel=np.ones((10, 10), np.uint8))

        # contours = imutils.grab_contours(cv2.findContours(frame_edge.copy() , cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE))
        # for cnt in sorted(contours, key=cv2.contourArea, reverse=True)[:5]:
        #     (x, y), (w, h), angle = cv2.minAreaRect(cnt)

        # cv2.imshow("test", frame_edge)
        # cv2.waitKey()
        # exit()

        # cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 255), 2)
        # cv2.imshow("test", frame)
        # cv2.waitKey()
        # cv2.drawContours(frame, [a], -1, (0, 255, 0), 3)
        # cv2.imshow("test", frame)
        # cv2.waitKey()

blur_tool = BlurTool()
blur_tool.process(file_name="LP.png") # LP.png frame.jpg

"""
# face detection
prototxt_path = "Code/prototxt.txt"
model_path = "Code/res10_300x300_ssd_iter_140000_fp16.caffemodel"
model = cv2.dnn.readNetFromCaffe(prototxt_path, model_path)

xmin, xmax, ymin, ymax = map(int, vehicle_boxes[index])
vehicles.append()
cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), (0, 255, 255), 5)
cv2.imshow("test", frame)
cv2.waitKey()
"""


# -*- coding: utf-8 -*-
__author__ = 'Liang Zhao'
__email__ = 'liangz8@vt.edu'
__version__ = '1.0.0'

import hashlib
import unicodedata

def get_hash_key(x):
    return hashlib.sha1(str(x)).hexdigest()

def normalize_str(s):
    if isinstance(s, str):
        s = s.decode("utf8")
    s = unicodedata.normalize("NFKD", s)
    return s.encode('ASCII', 'ignore').lower()

CO_CAPITAL_OSI_WAY = {
    'Ciudad de México': ['Mexico', 'Ciudad de México', 'Ciudad de México'],
    'Brasília'	      : ['Brazil', 'Brasília', 'Brasília'],
    'Buenos Aires'    : ['Argentina', '-', 'Buenos Aires'],
    'Caracas'         : ['Venezuela', 'Caracas', 'Caracas'],
    'Bogotá'          : ['Colombia', 'Bogotá', 'Bogotá'],
    'Quito'           : ['Ecuador', 'Pichincha', 'Quito'],
    'San Salvador'    : ['El Salvador', 'San Salvador', 'San Salvador'],
    'Asunción'        : ['Paraguay', 'Asunción', 'Asunción'],
    'Montevideo'      : ['Uruguay', 'Montevideo', 'Montevideo']
}

CO_CAPITAL_LOOKUP = dict( (normalize_str(k), v) for k, v in CO_CAPITAL_OSI_WAY.iteritems())

def osi_capital_city_province_corrector(loc):
    """Makes the state info correction as per OSI for
       capital city of the country

    :param loc: location tuple (Country, State, City)
    :returns: ( Country, State, City )
    """
    ci = normalize_str(loc[2])
    if ci in CO_CAPITAL_LOOKUP and \
       normalize_str(loc[0]) == normalize_str(CO_CAPITAL_LOOKUP[ci][0]):
        return CO_CAPITAL_LOOKUP[ci]
    else:
        return loc

def formatting_warning(msg0):
    surrogate, possibility, eIdKeyword = msg0
    warning = {}
    warning['derivedFrom'] = {}
    warning['derivedFrom']['source'] = "Raw Data-Sift Twitter Stream"
    warning['derivedFrom']['end'] = surrogate['derivedFrom']['end']
    warning['derivedFrom']['start'] = surrogate['derivedFrom']['start']
    warning['comments'] = "Dynamic Query Expansion model(classifier=classification.randomforest-CU v0.0.1)"
    warning['derivedFrom']['derivedIds'] = (surrogate['embersId'],eIdKeyword) # from tweets
    warning['model'] = 'Dynamic Query Expansion model(classifier=classification.randomforest-CU v0.0.1)'
    warning['confidence'] = possibility
    warning['confidenceIsProbability'] = True
    warning['eventType'] = surrogate['eventType']
    warning['location'] = surrogate['location']
    warning['coordinates'] = surrogate['coordinates']
    warning['population'] = surrogate['population']
    warning['eventDate'] = surrogate['eventDate']
    warning['date'] = surrogate['date']
    warning['embersId'] = get_hash_key(warning)
    return warning
def formatting_surrogate_dict(warning0): #surrogate
    warning = {}
    warning['derivedFrom'] = {}
    warning['derivedFrom']['source'] = "Local Modularity Spatial Scan"
    warning['derivedFrom']['end'] = warning0[3][1]  # last tweet timestamp
    warning['derivedFrom']['start'] = warning0[3][0]  # first tweet tinestamp
    warning['comments'] = "Local Modularity Spatial Scan(classifier=classification.randomforest-CU v0.0.1)"
    warning['derivedFrom']['derivedIds'] = warning0[6]   # raw data: embersID, processed data: parent ID
    warning['model'] = 'Local Modularity Spatial Scan(classifier=classification.randomforest-CU v0.0.1)'
    warning['confidence'] = warning0[5]
    warning['confidenceIsProbability'] = True
    warning['eventType'] = warning0[1]
    warning['location'] = osi_capital_city_province_corrector(warning0[0])
    warning['coordinates'] = warning0[4]
    warning['population'] = warning0[2]
    warning['eventDate'] = warning0[3][2]
    warning['date'] = warning0[3][1]
    warning['embersId'] = get_hash_key(warning)
    return warning

def formatting_surrogate(warnings0,dates):
    surrogates = []
    for dat in warnings0:
        location = dat[0][0][0][0]
        coordinates = tuple(dat[0][0][0][1])
        eventType = dat[0][0][1][0]
        population = dat[0][0][1][1]
        confidence = dat[0][1]
        embersId = dat[1]
        surrogates.append(formatting_surrogate_dict((location,eventType,population,
                                                dates,coordinates,confidence,embersId)))
    return surrogates

def formatting_keywords(country,date,keyspace,embersIds): #eIdKeyword
    temp,keyelements = keyspace
    del temp
    msg = {}
    msg['country'] = country
    msg['keywords'] = keyelements.keys()
    msg['date'] = date[:10]
    msg['derivedFrom'] = embersIds
    msg['embersId'] = get_hash_key(msg)
    return msg

def formalizing(surrogates0,threshold,dates,eIdKeyword):
    warnings = []
    for dat in surrogates0:
        confidence = dat['confidence']
        if(confidence>threshold):
            possibility = float('%.3f'%Possibility_computing(confidence,threshold))
            if(possibility >= 0.2):
                warnings.append(formatting_warning((dat,possibility,eIdKeyword)))
    return warnings

def Possibility_computing(count,threshold):
    if(threshold > 0):
        return (count-threshold)*0.8/float(1.0 - threshold)
    else:
        return count*0.8

def main():
    pass

if __name__ == '__main__':
    main()


#!/usr/bin/env python
# -*- coding:utf-8 -*-
import requests
import json
#药监总局地址：http://scxk.nmpa.gov.cn:81/xk/
if __name__ == "__main__":
    url = 'http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do?method=getXkzsList'
    user_agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1'
    headers = {
        'User-Agent': user_agent
    }
    data = {
        "on": "true",""
        "page": "1",
        "pageSize": "15",""
        "productName":"",
        "conditionType": "1",
        "applyname":"",
        "applysn":"",
    }
    json_ids = requests.post(url=url,headers=headers,data=data).json()
    print(json_ids)
    all_data_list = [] #存储所有企业的详情数据
    id_list = [] #存储企业的ID
    for dic in json_ids['list']:
        id_list.append(dic['ID'])
    print(id_list)#批量获取的ID

    #获取企业详情页信息
    print()
    post_url = 'http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do?method=getXkzsById'
    for id in id_list:
        data = {
            'id':id
        }
        detail_json = requests.post(url=url,headers=headers,data=data).json()
        print(detail_json,"--------")
        all_data_list.append(detail_json)
    #持久化存储
    fp = open('../alldata.json', 'w', encoding='utf-8')
    json.dump(all_data_list,fp=fp,ensure_ascii=False)
    print("over")


import base64
import os

from django.conf import settings
from django.contrib import messages
from django.contrib.auth import login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.core.files.base import ContentFile
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_http_methods

from .forms import DinoImageForm, DinosaurForm
from .models import DinoImage, Dinosaur, Favorite

def register(request):
    if request.method == "POST":
        form = UserCreationForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data.get("username")
            messages.success(request, f"Account created for {username}!")
            return redirect("login")
    else:
        form = UserCreationForm()
    return render(request, "registration/register.html", {"form": form})

def user_login(request):
    if request.method == "POST":
        form = AuthenticationForm(data=request.POST)
        if form.is_valid():
            user = form.get_user()
            login(request, user)
            messages.success(request, f"You are now logged in as {user.username}")
            return redirect("home")
    else:
        form = AuthenticationForm()
    return render(request, "registration/login.html", {"form": form})

def user_logout(request):
    logout(request)
    messages.success(request, "You have been logged out")
    return redirect("home")

@login_required
def home(request):
    dinos = Dinosaur.objects.all()
    return render(request, "home.html", {"dinos": dinos})

@login_required
# def dinosaur_detail(request, pk):
#     dino = get_object_or_404(Dinosaur, pk=pk)
#     images = DinoImage.objects.filter(dinosaur=dino)
#     image_urls = []
#     for image in images:
#         image_urls.append(
#             f"data:{image.content_type};base64,{base64.b64encode(image.image.read()).decode()}"
#         )
#     is_favorited = Favorite.objects.filter(user=request.user, dinosaur=dino).exists()
#     return render(
#         request,
#         "dinosaur_detail.html",
#         {"dino": dino, "image_urls": image_urls, "is_favorited": is_favorited},
#     )
def dinosaur_detail(request, pk):
    dino = get_object_or_404(Dinosaur, pk=pk)
    images = DinoImage.objects.filter(dinosaur=dino)
    image_urls = []
    for image in images:
        image_path = os.path.join(settings.MEDIA_ROOT, str(image.image))
        with open(image_path, "rb") as f:
            image_data = f.read()
            image_base64 = base64.b64encode(image_data).decode()
            image_url = f"data:image/jpeg;base64,{image_base64}"
            image_urls.append(image_url)
    return render(
        request,
        "dinosaur_detail.html",
        {"dino": dino, "images": images, "image_urls": image_urls},
    )

@login_required
def add_dinosaur(request):
    if request.method == "POST":
        form = DinosaurForm(request.POST, request.FILES)
        if form.is_valid():
            dino = form.save()
            messages.success(request, f"{dino.name} has been added to the database!")
            return redirect("dinosaur_detail", pk=dino.pk)
    else:
        form = DinosaurForm()
    return render(request, "add_dinosaur.html", {"form": form})

@login_required
def search_results(request):
    query = request.GET.get("q")
    print(
        query
    )  # Add this line to check that the search query is being retrieved correctly
    if query:
        dinosaurs = Dinosaur.objects.filter(name__icontains=query)
    else:
        dinosaurs = Dinosaur.objects.all()
    return render(request, "search_results.html", {"dinosaurs": dinosaurs})

@login_required
def edit_dinosaur(request, pk):
    dino = get_object_or_404(Dinosaur, pk=pk)
    if request.method == "POST":
        form = DinosaurForm(request.POST, request.FILES, instance=dino)
        if form.is_valid():
            form.save()
            return redirect("dinosaur_detail", pk=dino.pk)
    else:
        form = DinosaurForm(instance=dino)
    return render(request, "dinosaur_edit.html", {"form": form, "dino": dino})

@login_required
def update_dinosaur(request, pk):
    dino = get_object_or_404(Dinosaur, pk=pk)
    if request.method == "POST":
        form = DinosaurForm(request.POST, request.FILES, instance=dino)
        if form.is_valid():
            dino = form.save()
            messages.success(request, f"{dino.name} has been updated!")
            return redirect("dinosaur_detail", pk=dino.pk)
    else:
        form = DinosaurForm(instance=dino)
    return render(request, "add_dinosaur.html", {"form": form})

@login_required
def delete_dinosaur(request, pk):
    dino = get_object_or_404(Dinosaur, pk=pk)
    if request.method == "POST":
        dino.delete()
        messages.success(request, f"{dino.name} has been deleted from the database!")
        return redirect("home")
    return render(request, "delete_dinosaur.html", {"dino": dino})

@login_required
@require_http_methods(["POST"])
def add_image(request, pk):
    dinosaur = get_object_or_404(Dinosaur, pk=pk)
    if request.method == "POST":
        form = DinoImageForm(request.POST, request.FILES)
        if form.is_valid():
            image = form.save(commit=False)
            image.dinosaur = dinosaur
            image.save()
            return redirect("dinosaur_detail", pk=dinosaur.pk)
    else:
        form = DinoImageForm()
    return render(request, "add_image.html", {"form": form, "dinosaur": dinosaur})

@login_required
@require_http_methods(["POST"])
def delete_image(request, pk):
    dino_image = get_object_or_404(DinoImage, pk=pk)
    dinosaur_pk = dino_image.dinosaur.pk
    dino_image.delete()
    return redirect("dinosaur_detail", pk=dinosaur_pk)

@login_required
@require_http_methods(["POST"])
def toggle_favorite(request, pk):
    dino = get_object_or_404(Dinosaur, pk=pk)
    fav, created = Favorite.objects.get_or_create(user=request.user, dinosaur=dino)
    if not created:
        fav.delete()
    return JsonResponse({"success": True, "is_favorited": not created})

@login_required
def add_favorite(request, pk):
    dino = get_object_or_404(Dinosaur, pk=pk)
    fav, created = Favorite.objects.get_or_create(user=request.user, dinosaur=dino)
    if created:
        messages.success(request, "Added to favorites.")
    else:
        messages.error(request, "This dinosaur is already in your favorites.")
    return redirect("dinosaur_detail", pk=dino.pk)

@login_required
def list_favorites(request):
    favorites = Favorite.objects.filter(user=request.user)
    dinos = [fav.dinosaur for fav in favorites]
    return render(request, "list_favorites.html", {"dinos": dinos})


import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

import argopandas as argo

wmos = [4902596, 4902597]

var_names = ['PRES', 'TEMP', 'PSAL', 'DOXY', 'CHLA', 'BBP700']

for wmo in wmos:
    ix   = argo.float(wmo).synthetic_prof
    up   = ix.subset_direction('asc')
    down = ix.subset_direction('desc')

    up['CYCLE']   = [int(f[-6:-3]) for f in up.file]
    down['CYCLE'] = [int(f[-7:-4]) for f in down.file]

    cycles = set(up.CYCLE.unique()).intersection(down.CYCLE.unique())

    for cycle in cycles:
        fig, ax  = plt.subplots()
        up_sub   = up.loc[up.CYCLE == cycle]
        down_sub = down.loc[down.CYCLE == cycle]

        up_data   = up_sub.levels
        down_data = down_sub.levels

        sns.lineplot(data=up_data, x='DOXY', y='PRES', sort=False, estimator=None, ax=ax)
        sns.lineplot(data=down_data, x='DOXY', y='PRES', sort=False, estimator=None, ax=ax)

        ax.set_ylim((200,0))


from parse import parse

import pygame
from pygame.locals import *

from cube import Cube
from const import *
from pygame.math import Vector3
from utils import *
from drawable_chunk import DrawableChunk
from hero import Hero
from level import *

class Level(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.cubes = []
        self.drawables = []
        self.events = []
        self.image_tileset = pygame.image.load("res/tileset.png").convert_alpha()
        self.size = Vector3()

    def add_drawable(self, drawable):
        self.drawables.append(drawable)

    def read(self, filename):
        tileset_width = self.image_tileset.get_width() // TILE_SIZE
        with open(filename) as f:
            lines = [line.rstrip() for line in f]
            for line in lines:
                r = parse("{:d}:{:d}:{:d} {:d}:{:d}:{:d}", line)

                c0 = to_2d_coords(
                    r[3],
                    tileset_width,
                )

                c1 = to_2d_coords(
                    r[4],
                    tileset_width,
                )

                c2 = to_2d_coords(
                    r[5],
                    tileset_width,
                )

                coords = [c0, c1, c2]

                cube = Cube(coords)
                cube.position = Vector3(
                    r[0] * Cube.SIZE, r[1] * Cube.SIZE, r[2] * Cube.SIZE
                )

                cube.indexes = Vector3(r[0], r[1], r[2])
                cube.zindex = sum(cube.indexes)
                self.cubes.append(cube)

            self.update_size()

    def update_size(self):
        for cube in self.cubes:
            if self.size.x < cube.indexes.x + 1:
                self.size.x = cube.indexes.x + 1

            if self.size.y < cube.indexes.y + 1:
                self.size.y = cube.indexes.y + 1

            if self.size.z < cube.indexes.z + 1:
                self.size.z = cube.indexes.z + 1

    def get_drawable(self, x, y, z):
        for i in range(len(self.drawables)):
            drawable = self.drawables[i]
            if drawable.position.x // 16 == x and drawable.position.y // 16 == y and drawable.position.z  // 16 == z:
                return drawable

    def get_cube(self, x, y, z):
        for i in range(len(self.cubes)):
            cube = self.cubes[i]
            if cube.indexes.x == x and cube.indexes.y == y and cube.indexes.z == z:
                return cube

    def get_cube_index(self, x, y, z):
        for i in range(len(self.cubes)):
            cube = self.cubes[i]
            if cube.indexes.x == x and cube.indexes.y == y and cube.indexes.z == z:
                return i

    def draw(self, camera, surface_display):
        drawables_with_chunks = []

        # Work In Progress: split drawables into chunks when needed
        for drawable in self.drawables:
            if isinstance(drawable, Hero):
                # assum that only hero needs chunk display
                # draw in a temporary surface
                surface_tmp = pygame.Surface(
                    (drawable.drawable_width, drawable.drawable_height), pygame.SRCALPHA
                )
                drawable.draw(0, 0, surface_tmp)

                # assum 2 chunks
                nb_chunk = 2  # drawable_height // Cube.SIZE
                for number in range(nb_chunk):
                    drawable_chunk = DrawableChunk(
                        drawable.position.x,
                        drawable.position.y,
                        drawable.position.z + 16,  # Shift
                    )

                    # TODO fix drawing when hero jump
                    drawable_chunk.zindex = (
                        sum(
                            list(
                                map(
                                    (lambda x: x / Cube.SIZE),
                                    drawable_chunk.position,
                                )
                            )
                        )
                        + number
                        - 1
                    )

                    drawable_chunk.number = nb_chunk - number - 1
                    drawable_chunk.surface = surface_tmp
                    drawable_chunk.size = Vector2(
                        drawable.drawable_width, drawable.drawable_height
                    )
                    drawables_with_chunks.append(drawable_chunk)
            else:
                drawables_with_chunks.append(drawable)

        sorted_drawables = sorted(
            self.cubes + drawables_with_chunks, key=lambda drawable: drawable.zindex
        )

        for drawable in sorted_drawables:
            drawable_iso = cartesian_to_isometric(
                (drawable.position.x, drawable.position.y)
            )
            x = camera.x + drawable_iso.x - Cube.SIZE
            y = camera.y + drawable_iso.y - drawable.position.z

            if isinstance(drawable, Cube):
                drawable.draw(x, y, surface_display, self.image_tileset)
            else:
                drawable.draw(x, y, surface_display)

    def clear(self):
        self.drawables.clear()
        self.cubes.clear()
        self.events.clear()


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Simple check list from AllenNLP repo:
https://github.com/allenai/allennlp/blob/master/setup.py

To create the package for pypi.

1.  Change the version in __init__.py, setup.py as well as docs/source/conf.py.

2.  Commit these changes with the message: "Release: VERSION"

3.  Add a tag in git to mark the release:
        git tag VERSION -m 'Adds tag VERSION for pypi'

    Push the tag to git:
        git push --tags origin master

4.  Build both the sources and the wheel.

    Do not change anything in setup.py between creating the wheel and the
    source distribution (obviously).

    For the wheel, run: "python setup.py bdist_wheel" in the top level
    directory.  (this will build a wheel for the python version you use to
    build it).

    For the sources, run: "python setup.py sdist" You should now have a /dist
    directory with both .whl and .tar.gz source versions.

5.  Check that everything looks correct by uploading package to test server:

    twine upload dist/* -r pypitest

    (pypi suggest using twine as other methods upload files via plaintext.)

    You may have to specify the repository url,
    use the following command then:
        twine upload dist/* -r pypitest\
        --repository-url=https://test.pypi.org/legacy/

    Check that you can install it in a virtualenv by running:
        pip install -i https://testpypi.python.org/pypi transformers

6.  Upload the final version to actual pypi:

    twine upload dist/* -r pypi

7.  Copy the release notes from RELEASE.md to the tag in github.

"""
import os
from setuptools import setup

# Utility function to read the README file.
# Used for the long_description.  It's nice, because now 1) we have a top level
# README file and 2) it's easier to type in the README file than to put a raw
# string in below ...
def read(fname: str) -> str:
    """ Read and return README as str. """
    return open(os.path.join(os.path.dirname(__file__), fname)).read()

setup(
    name="asta",
    version="0.0.7",
    author="Brendan Whitaker",
    description=("Shape annotations for homogeneous numpy arrays and pytorch/tensorflow tensors."),
    license="GPLv3",
    packages=["asta"],
    long_description=read("README"),
    long_description_content_type="text/plain",
    install_requires=["toml", "numpy", "sympy", "oxentiel"],
    package_data={"asta": ["defaults/astarc"]},
    include_package_data=True,
    python_requires=">=3.7.0",
    classifiers=[
        "Development Status :: 3 - Alpha",
        "Intended Audience :: Developers",
        "Intended Audience :: Education",
        "Intended Audience :: Science/Research",
        "Topic :: Utilities",
        "License :: OSI Approved :: BSD License",
        "Topic :: Scientific/Engineering",
        "Programming Language :: Python :: 3.7",
    ],
)


import logging
def initLogging():
    # Use simple logging in this file
    # See whether I can seperate logging from this program and my library

    logging.basicConfig(filename='test_logging.log',level=logging.DEBUG,
        format='%(asctime)s %(message)s', datefmt='%Y%m%d-%H%M%S')

    logger = logging.getLogger('root')
    # This will setup the default logger
    # The default level for this logging system is info
    # a = logging
    a = logger
    a.info('Hello world')
    a.error('This is an error')
    a.warning('This is a warning')
    a.debug('Debug information')

# initLogging()

# Make sure the log information from tenon would not contaminate here
tenonpath = '..'
import sys; sys.path.append(tenonpath)
import tenon

tenon.run(__file__, '../demo.blend')
if tenon.inblender():
    tenon.render.write('demo.png')
    tenon.logging.info('Write image to demo.png')

    logging.info('The execution is completed')


from pathlib import Path

from .code_description import CodeDescriptionParams, CodeDescriptionXLSXDocument
from .dataset_description import (
    DatasetDescriptionParams,
    DatasetDescriptionXLSXDocument,
)
from .manifest import ManifestXLSXDocument

def write_xlsx_files(
    base_path: Path,
    dataset_description_params: DatasetDescriptionParams,
    code_description_params: CodeDescriptionParams,
) -> None:
    dataset_description_xlsx = DatasetDescriptionXLSXDocument()
    dataset_description_xlsx.save_document(
        base_path=base_path, template_data=dataset_description_params
    )

    code_description_xlsx = CodeDescriptionXLSXDocument()
    code_description_xlsx.save_document(
        base_path=base_path, template_data=code_description_params
    )
    manifest_xlsx = ManifestXLSXDocument()
    manifest_xlsx.save_document(base_path=base_path, template_data=None)


from django.shortcuts import render, redirect
from .models import Todo
from .forms import TodoForm

def tasks_list(request):
    todos = Todo.objects.all()
    context = {'todos': todos}
    return render(request, 'tasks_list.html', context)

def add_todo(request):
    if request.method == 'POST':
        form = TodoForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('add_todo')
    else:
        form = TodoForm()

    todos = Todo.objects.all()

    context = {
        'form': form,
        'todos': todos,
    }

    return render(request, 'add_todo.html', context)


from functools import reduce

def main():
    # one liner functions
    culc_sum = lambda number_list: sum(number_list)
    check_palindrome = lambda number: str(number) == str(number)[::-1]
    factorial = lambda number: reduce((lambda a, b: a * b), range(1, number + 1))

    # check functions
    print(culc_sum([1, 2, 3, 4, 5, 10]))
    print(check_palindrome(12121))
    print(factorial(6))

if __name__ == '__main__':
    main()


# Sparse Search: Given a sorted array of strings that is interspersed with empty strings, write a
# method to find the location of a given string.

def sparse_search(l, item):
    def search(start, end):
        mid = start + (end - start) // 2
        if l[mid] == "":
            radius = 1
            while l[mid] == "":
                if mid+radius <= end and l[mid+radius] != "":
                    mid=mid+radius
                elif mid-radius >= start and l[mid-radius] != "":
                    mid=mid-radius
                if mid+radius > end and mid-radius < start:
                    return None
                radius += 1
        if l[mid] == item:
            return mid
        elif l[mid] > item:
            return search(start, mid-1)
        else:
            return search(mid+1, end)
    return search(0, len(l)-1)

l = ["a", "", "b", "", "", "", "c", "", ""]
print(sparse_search(l, "a"))
assert sparse_search(l, "a") == 0
print(sparse_search(l, "b"))
assert sparse_search(l, "b") == 2
print(sparse_search(l, "c"))
assert sparse_search(l, "c") == 6


import copy
import time

class Krpsim:
    def __init__(self, agent, delay, verbose, random=False):
        self.inventory = []
        self.agent = agent.copy()
        self.delay = delay
        self.stock = (agent.stock)
        self.verbose = verbose
        self.random = random

    @property
    def stock(self):
        return self._stock

    @stock.setter
    def stock(self, stock):
        if isinstance(stock, dict):
            self._stock = dict(stock)
        else:
            raise ValueError('Stock must be a dictionary')

    def copy(self):
        return copy.copy(self)

    def generate_inventory(self):
        if len(self.agent.get_available_process_lst()) == 0:
            return self
        i = 0

        while True and i < 1000:
            if self.random == False:
                walk = self.agent.generate_inventory(self.inventory, self.delay)
            else:
                walk = self.agent.generate_walk(self.inventory, self.delay)
            if walk == None or len(walk) == 0:
                self.stock = self.agent.stock
                self.agent.stock = self.agent.initial_stock
                i += 1
                continue
            self.inventory.extend(walk)
            if self.verbose:
                print('agent stock')
                self.agent.print_stocks(self.agent.stock)
            self.stock = self.agent.stock
            self.agent.stock = self.agent.initial_stock
            if self.inventory != None and len(self.inventory) != 0:
                break
        if i >= 1000:
            self.inventory.clear()
            self.inventory = list(self.agent.walk)
        return self

    def run(self) -> None:
        if self.verbose:
            print('start')
            print('inventory.stock:', self.stock)
        else:
            print("Evaluating ", end='')
        stock = self.optimize()
        print(" done.")
        if self.agent.finite and 'time' in self.agent.optimize:
            stock = self.optimize_time(stock)
        self.print_trace(stock)

    def optimize(self) -> dict:
        prev_indi = self.agent.copy()
        prev_indi.init_stocks()
        indi = self.copy()

        # for time measurement
        dot_interval = 0.5
        start_time = time.time()
        next_dot_time = start_time + dot_interval

        # for finite checking:
        self.agent.finite = False
        while indi.stock != prev_indi.stock:
            prev_indi = indi
            new_indi = indi.copy()
            new_indi.agent.stock = dict(indi.stock)
            new_indi.agent.initial_stock = dict(indi.stock)
            new_indi.generate_inventory()
            if self.verbose:
                print('new stock:', new_indi.stock)
            indi = new_indi.copy()

            current_time = time.time()
            if current_time >= next_dot_time:
                print('.', end='', flush=True)
                next_dot_time = current_time + dot_interval

            if current_time - start_time >= self.delay:
                break
            elif indi.stock == prev_indi.stock or len(indi.agent.get_available_process_lst()) == 0:
                self.agent.finite = True
                break
        self.inventory = list(new_indi.inventory)
        return dict(new_indi.stock)

    def optimize_time(self, stock_dict: dict) -> dict:
        min_total_cycle = float('inf')
        inventory = []
        time_stock = []
        for i in range(80):
            if len(self.inventory) != 0:
                total_cycle = self.inventory[-1][1]
                total_cycle += int(
                self.agent.process[self.inventory[len(self.inventory) - 1][0]].nb_cycle)
            else:
                total_cycle = float('inf')
            if min_total_cycle > total_cycle:
                min_total_cycle = total_cycle
                inventory = list(self.inventory)
                time_stock = dict(stock_dict)
            elif min_total_cycle == total_cycle:
                for optimize in self.agent.optimize:
                    if optimize != 'time' and len(time_stock) != 0 and time_stock[optimize] < stock[optimize]:
                        inventory = list(self.inventory)
                        time_stock = dict(stock_dict)
            self.agent.cycle = 0
            self.inventory.clear()
            stock = self.optimize()
        self.inventory.clear()
        self.inventory = list(inventory)
        return time_stock

    def print_trace(self, stock_dict: dict) -> None:
        print("Main walk")
        if len(self.inventory) != 0:
            total_cycle = self.inventory[-1][1]
        else:
            total_cycle = 0
        biggest_cycle = 0
        for item in self.inventory:
            if (int(self.agent.process[item[0]].nb_cycle) > biggest_cycle):
                biggest_cycle = int(self.agent.process[item[0]].nb_cycle)
            print(f"{item[1]}:{item[0]}")

        if len(self.inventory) != 0:
            total_cycle += int(
            self.agent.process[self.inventory[len(self.inventory) - 1][0]].nb_cycle)

        if self.agent.finite is True:
            print(f"no more process doable at time {total_cycle + 1}")

        print("Stock :")
        for key, value in stock_dict.items():
            print(f" {key} => {value}")


# -*- coding: utf-8 -*-
import copy

class Population():
    """
    Hold relevant information and bookkeeping functions for a population.
    """
    def __init__(self, pop_name, ea_mu, ea_lambda, dmax_init, dmax_overall,
                 parent_selection, overselection_top, p_m, survival_selection,
                 tournament_size_for_survival_selection, parsimony_technique,
                 pppc, functions, terminals):
        self.pop_name = pop_name
        self.ea_mu = ea_mu
        self.ea_lambda = ea_lambda
        self.dmax_init = dmax_init
        self.dmax_overall = dmax_overall
        self.parent_selection = parent_selection
        self.overselection_top = overselection_top
        self.p_m = p_m
        self.survival_selection = survival_selection
        self.tournament_size_for_survival_selection = tournament_size_for_survival_selection
        self.parsimony_technique = parsimony_technique
        self.pppc = pppc
        self.functions = functions
        self.terminals = terminals

        self.individuals = None  # list of ExprTree instances
        self.best_individuals = [] # list of best individuals of each generation

        # Per-run bookkeeping values
        self.run_high_fitness = float('-inf')
        self.run_high_score = float('-inf')
        self.run_best_world_data = None
        self.run_best_individual = None
        self.evals_with_no_change = 0

        # Per-generation bookkeeping values
        self.gen_high_fitness = float('-inf')
        self.gen_high_score = float('-inf')
        self.gen_fitness_total = 0
        self.gen_score_total = 0
        self.gen_best_world_data = None
        self.gen_best_individual = None
        self.gen_best_solution = None
        self.gen_max_tree_height = -1
        self.gen_tree_height_total = 0
        self.gen_max_tree_size = -1
        self.gen_tree_size_total = 0

    def reset_run_values(self):
        """
        Reset values to prepare for a new run.
        """
        self.individuals = None
        self.best_individuals = []
        self.run_high_fitness = float('-inf')
        self.run_high_score = float('-inf')
        self.run_best_world_data = None
        self.run_best_solution = None
        self.evals_with_no_change = 0

    def calc_run_stats(self):
        """
        Update stats for the current run. To be called immediately
        after a generation has completed including bookkeeping.
        """
        # Check and handle new run high score and fitness
        if (self.gen_high_score > self.run_high_score):
            self.run_high_score = self.gen_high_score
        if (self.gen_high_fitness > self.run_high_fitness):
            self.run_high_fitness = self.gen_high_fitness
            print('New run', self.pop_name, 'high fitness: ', self.run_high_fitness)
            self.run_best_individual = self.gen_best_individual

        # # ALTERNATE METHOD:
        # # We only care about the best from the final generation, so
        # # just blindly copy after each generation and when this method
        # # is no longer called, that's the final generation
        # self.run_high_score = self.gen_high_score
        # self.run_high_fitness = self.gen_high_fitness
        # self.run_best_individual = self.gen_best_individual
        # print('Gen', self.pop_name, 'high fitness: ', self.run_high_fitness)

    def generation_bookkeeping(self):
        """
        Update stats for the current generation. To be called immediately
        after a generation has completed.
        """
        self.gen_high_fitness = float('-inf')
        self.gen_high_score = float('-inf')
        self.gen_fitness_total = 0
        self.gen_score_total = 0
        self.gen_best_individual = None
        self.gen_max_tree_height = -1
        self.gen_tree_height_total = 0
        self.gen_max_tree_size = -1
        self.gen_tree_size_total = 0

        for individual in self.individuals:
            self.gen_fitness_total += individual.fitness
            if (individual.fitness > self.gen_high_fitness):
                self.gen_high_fitness = individual.fitness
                self.gen_best_individual = copy.deepcopy(individual)
            self.gen_score_total += individual.score
            if (individual.score > self.gen_high_score):
                self.gen_high_score = individual.score
            self.gen_tree_height_total += individual.root.height
            if (individual.root.height > self.gen_max_tree_height):
                self.gen_max_tree_height = individual.root.height
            self.gen_tree_size_total += individual.root.size
            if (individual.root.size > self.gen_max_tree_size):
                self.gen_max_tree_size = individual.root.size

        # Save off best individual of the generation
        self.best_individuals.append(copy.deepcopy(self.gen_best_individual))

    def update_logs(self, eval_count, experiment_log, parsimony_log):
        """
        Update the experiment and parsimony logs
        """
        # Update log
        experiment_log.write(str(eval_count) + '\t' \
                             + str(self.gen_fitness_total / len(self.individuals)) + '\t'
                             + str(self.gen_high_fitness) + '\n')

        # Update parsimony log
        parsimony_log.write(str(eval_count) + '\t' \
                            + str(self.gen_tree_height_total / len(self.individuals)) + '\t'
                            + str(self.gen_max_tree_height) + '\t'
                            + str(self.gen_tree_size_total / len(self.individuals)) + '\t'
                            + str(self.gen_max_tree_size) + '\t'
                            + str(self.gen_score_total / len(self.individuals)) + '\t'
                            + str(self.gen_high_score) + '\n')


# Juhusliku valiku valimiseks importige arvutisse juhuslik teek
import random

# Valikute loend
options = ["kivi", "paber", "käärid"]

# Funktsioon mängu mängimiseks
def play_game():
    # Arvuti teeb juhusliku valiku
    computer_choice = random.choice(options)

    # Kasutaja teeb valiku
    user_choice = input("Valige kivi, paber või käärid: ")

    # Kontrollige, kes mängu võidab
    if computer_choice == user_choice:
        print("Viik!")
    elif (computer_choice == "kivi" and user_choice == "käärid") or (computer_choice == "paber" and user_choice == "kivi") or (computer_choice == "käärid" and user_choice == "paber"):
        print("Arvuti võitis!")
    else:
        print("Kasutaja võitis!")

# Mängutsükkel mitme mängu mängimiseks
while True:
    play_game()
    play_again = input("Kas soovid veel mängida (jah/ei)? ")
    if play_again.lower() == "ei":
        break

# Hüvastijätu sõnum
print("Lõpetame mängimise, tänan, et mängisite!")


"""Reads parameters of received http request"""
import http.client as http_client
import logging
from typing import Optional

import azure.functions as func

from .custom_error import DownloadBlobError

log = logging.getLogger(name="log." + __name__)

def main(req: func.HttpRequest, params_list: Optional[list] = None) -> dict[str, str]:
    """
    Reads parameters of received http request.

    Args:
        req (azure.functions.HttpRequest): HTTP request sent to Azure Function's endpoint.
        params_list (Optional[list], optional): list of parameters expected in the request.\
            Defaults to ["invoice_id", "single_file_download", "file_format"].

    Raises:
        DownloadBlobError: if any of the expected parameters is not found in the request.

    Returns:
        dict[str, str]: dictionary of parameters and their values.
    """
    log.debug(msg=f"Reading parameters of the request {req}.")
    if params_list is None:
        params_list = ["invoice_id", "file_format"]

    params = {}
    for param in params_list:
        try:
            params[param] = req.params[param]
        except KeyError as exc:
            message = f"No {param} parameter in the request."
            raise DownloadBlobError(
                exception_type="KeyError",
                details=f"KeyError: {param}",
                message=message,
                status_code=http_client.BAD_REQUEST,
            ) from exc

    log.debug(msg=f"Parameters of the request {req} read successfully.")
    return params


import imp
import discord
from discord.ext import commands
import json
import os
from os import listdir
from os.path import isfile, join
from datetime import datetime
import subprocess
from discordLevelingSystem import DiscordLevelingSystem
import aiosqlite

def micsid(ctx):
    return ctx.author.id == 481377376475938826 or ctx.author.id == 624076054969188363

def log(log):
    now = datetime.now()
    timern = now.strftime("%d/%m/%Y %H:%M:%S")

    with open('./other/log.txt', 'a') as f:
        f.write('\n')
        f.write(f"{timern} | {log}")

cogs = []
for i in os.listdir("cogs/"):
    if i == "__pycache__":
        pass
    else:
        print(i[:-3])

class BotMakerCommands(commands.Cog):
    def __init__(self, client):
        self.client = client

    @commands.command()
    @commands.check(micsid)
    async def logs(self, ctx):
      file = discord.File("./other/log.txt")
      await ctx.author.send(file=file)

    @commands.command()
    @commands.check(micsid)
    async def msgserver(self, ctx, id:int, *, message):
        for guild in self.client.guilds:
            if guild.id == id:
                return await guild.text_channels[0].send(message)
        await ctx.send("guild not found")

    @commands.command()
    @commands.check(micsid)
    async def reloadall(self, ctx):
        lst = [f for f in listdir("cogs/") if isfile(join("cogs/", f))]
        no_py = [s.replace('.py', '') for s in lst]
        startup_extensions = ["cogs." + no_py for no_py in no_py]
        startup_extensions.remove("cogs.Leveling")

        try:
            for cogs in startup_extensions:
                self.client.reload_extension(cogs)

            await ctx.send("All Reloaded")

        except Exception as e:
            print(e)
            log(e)

    @commands.command(hidden = True)
    @commands.check(micsid)
    async def pull(self, ctx):
        gitstuff = subprocess.run(["git", "pull"], capture_output=True).stdout
        await ctx.send(gitstuff.decode())
        log(gitstuff.decode())

    @commands.command(help="Dms all server owners")
    @commands.check(micsid)
    async def dm_owners(self,ctx,*, msg):
        await ctx.send("Sending...")
        log(f"DMing all owners with {msg}")
        mins = 0
        #predicts how long it will take
        mins = len(self.client.guilds) * 0.1
        await ctx.send(f"Estimated time: {mins} minutes")

        owners = []
        for server in self.client.guilds:
            tosend = server.owner
            owners.append(tosend)
        owners = list(set(owners))
        for i in owners:
            try:
                await i.send(msg)
            except:
                await ctx.send(f"Counld not send to {i}")
        await ctx.send("Done")

    @commands.command()
    @commands.check(micsid)
    async def ghoastping(self,ctx,*,member:discord.Member):
        for i in ctx.guild.channels:
            try:
                x = await i.send(f"{member.mention}")
                await x.delete()
            except:
                print(f"Can't send message in {i}")

    @commands.command(hidden = True)
    @commands.is_owner()
    async def clearlog(self,ctx):
        file = discord.File("./other/log.txt")
        await ctx.author.send(file=file)
        dirs = 'other/'
        for f in os.listdir(dirs):
            os.remove(os.path.join(dirs, f))
        dirs = 'tempstorage/'
        for f in os.listdir(dirs):
            os.remove(os.path.join(dirs, f))
        await ctx.send("Cleared")
        await log("Cleared at " + datetime.now().strftime("%d/%m/%Y %H:%M:%S"))

    @commands.command(hidden = True)
    @commands.check(micsid)
    async def status(self, ctx):
        gitstuff = subprocess.run(["git", "status"], capture_output=True).stdout
        await ctx.send(gitstuff.decode())
        log(gitstuff.decode())

    @commands.command()
    @commands.check(micsid)
    async def load(self, ctx, extension):
        self.client.load_extension(f"cogs.{extension}")
        embed = discord.Embed(
            title='Load', description=f'{extension} successfully loaded', color=0xff00c8)
        await ctx.send(embed=embed)

    @commands.command()
    @commands.check(micsid)
    async def unload(self, ctx, extension):
        self.client.unload_extension(f"cogs.{extension}")
        await ctx.send(f"The module '{extension}' has been unloaded successfully!")

    @commands.command()
    @commands.is_owner()
    async def change_status(self, ctx, *, status):
        status = status.replace("[[servers]]", str(len(self.client.guilds)))
        await self.client.change_presence(activity=discord.Game(name=status))
        await ctx.send(f"Status changed to {status}")

    @commands.command()
    @commands.is_owner()
    async def commandlookup(self, ctx, command):
        #check if command exists
        if self.client.get_command(command) == None:
            await ctx.send("Command not found")
            return
        #find the cog
        for i in self.client.cogs:
            if self.client.get_command(command) in self.client.get_cog(i).get_commands():
                cog = i
        await ctx.send(f"Cog: {cog}\nCommand: {command}")

    #when a command is used, it will be logged
    @commands.Cog.listener()
    async def on_command(self, ctx):
        #check if file exists
        if os.path.isfile(f"databases/command_usage.db"):
            async with aiosqlite.connect("databases/command_usage.db") as db:
                #check if command is in database
                async with db.execute("SELECT * FROM command_usage WHERE command = ?", (ctx.command.name,)) as cursor:
                    data = await cursor.fetchall()
                    #if command is not in database
                    if len(data) == 0:
                        await db.execute("INSERT INTO command_usage VALUES (?, ?, ?)", (ctx.command.name, 1, datetime.now().strftime("%d/%m/%Y %H:%M:%S")))
                        await db.commit()
                    #if command is in database
                    else:
                        await db.execute("UPDATE command_usage SET times_used = ?, last_used = ? WHERE command = ?", (data[0][1] + 1, datetime.now().strftime("%d/%m/%Y %H:%M:%S"), ctx.command.name))
                        await db.commit()

        else:
            async with aiosqlite.connect("databases/command_usage.db") as db:
                await db.execute("CREATE TABLE command_usage (command TEXT, times_used INTEGER, last_used TEXT)")
                await db.commit()

    @commands.command()
    @commands.check(micsid)
    async def commandusage(self, ctx, command):
        if os.path.isfile(f"databases/command_usage.db"):
            async with aiosqlite.connect("databases/command_usage.db") as db:
                async with db.execute("SELECT * FROM command_usage WHERE command = ?", (command,)) as cursor:
                    data = await cursor.fetchall()
                    if len(data) == 0:
                        await ctx.send("Command not found")
                    else:
                        embed = discord.Embed(title = "Command Usage", description = f"Command: {data[0][0]}\nTimes used: {data[0][1]}\nLast used: {data[0][2]}", color = 0xff00c8)
                        await ctx.send(embed = embed)
        else:
            await ctx.send("Command not found")

    @commands.command()
    @commands.check(micsid)
    async def commandusagelist(self, ctx):
        if os.path.isfile(f"databases/command_usage.db"):
            async with aiosqlite.connect("databases/command_usage.db") as db:
                async with db.execute("SELECT * FROM command_usage") as cursor:
                    data = await cursor.fetchall()
                    if len(data) == 0:
                        await ctx.send("No commands found")
                    else:
                        embed = discord.Embed(title = "Command Usage", description = "Command: Times used: Last used:", color = 0xff00c8)
                        for i in data:
                            embed.description += f"\n{i[0]}: {i[1]}: {i[2]}"
                        await ctx.send(embed = embed)
        else:
            await ctx.send("No commands found")

    @commands.command()
    @commands.is_owner()
    async def server_invite(self, ctx, *, server):
        guild = self.client.get_guild(int(server))
        if guild == None:
            await ctx.send("Server not found")
            return
        invite = await guild.channels[0].create_invite()
        await ctx.send(invite)

    @commands.command()
    @commands.is_owner()
    async def server_look_up(self, ctx, *, server):
        guild = self.client.get_guild(int(server))
        if guild == None:
            await ctx.send("Server not found")
            return
        embed = discord.Embed(title = guild.name, description = f"ID: {guild.id}", color = 0xff00c8)
        embed.add_field(name = "Owner", value = f"{guild.owner.name}#{guild.owner.discriminator}")
        embed.add_field(name = "Members", value = guild.member_count)
        embed.add_field(name = "Channels", value = len(guild.channels))
        embed.add_field(name = "Roles", value = len(guild.roles))
        embed.add_field(name = "Created at", value = guild.created_at.strftime("%d/%m/%Y %H:%M:%S"))
        embed.add_field(name = "Owner ID", value = guild.owner.id)

        try:
            embed.set_thumbnail(url = guild.icon.url)
        except:
            pass

        await ctx.send(embed = embed)

def setup(client):
    client.add_cog(BotMakerCommands(client))


import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"

from paddleocr import PaddleOCR,draw_ocr
# Paddleocr supports Chinese, English, French, German, Korean and Japanese.
# You can set the parameter `lang` as `ch`, `en`, `fr`, `german`, `korean`, `japan`
# to switch the language model in order.
ocr = PaddleOCR(use_angle_cls=True, lang='ch') # need to run only once to download and load model into memory
img_path = 'Im6.png'
result = ocr.ocr(img_path, cls=True)
#for line in result:
#    print(line)

# Each line consists of a 4 * 2 list and a tuple,
# containing coordinates of a bounding box and ocr result with confidence, respectively.

# draw result
from PIL import Image
image = Image.open(img_path).convert('RGB')
boxes = [line[0] for line in result]
txts = [line[1][0] for line in result]
scores = [line[1][1] for line in result]
im_show = draw_ocr(image, boxes, txts, scores, font_path='./fonts/simfang.ttf')
im_show = Image.fromarray(im_show)
im_show.save('result.jpg')


from tkinter import *
from PIL import Image, ImageDraw
from src.Model import Model

b1 = "up"
xold, yold = None, None
image1, drawimg = None, None
model = Model()

def create_lines(canv):
    canv.create_line(30, 0, 30, 140, smooth=TRUE, fill="red", width="1")
    canv.create_line(110, 0, 110, 140, smooth=TRUE, fill="red", width="1")
    canv.create_line(0, 30, 140, 30, smooth=TRUE, fill="red", width="1")
    canv.create_line(0, 110, 140, 110, smooth=TRUE, fill="red", width="1")

def testCallback(canv):
    global image1, model
    #image1 = image1.resize((28,28))
    image1.save("./valami.png")
    model.testImage(image1)

def clearCallback(canv):
    global image1, drawimg
    canv.delete('all')
    create_lines(canv)
    drawimg.rectangle((0, 0, image1.size[0], image1.size[1]), fill=0)

def main():
    global image1, drawimg
    image1 = Image.new(mode="L", size=(28, 28))
    drawimg = ImageDraw.Draw(image1)

    root = Tk()
    root.title("DRAW")
    root.geometry('200x150')

    drawing_area = Canvas(root)
    drawing_area.grid(row=0, column=0, rowspan=2)
    drawing_area.config(width=140, height=140)
    drawing_area.configure(background='black')
    create_lines(drawing_area)
    drawing_area.bind("<Motion>", motion)
    drawing_area.bind("<ButtonPress-1>", b1down)
    drawing_area.bind("<ButtonRelease-1>", b1up)

    B1 = Button(root, text="Test", command=lambda: testCallback(drawing_area))
    B1.grid(row=0, column=1)
    B2 = Button(root, text="Clear", command=lambda: clearCallback(drawing_area))
    B2.grid(row=1, column=1)
    root.mainloop()

def b1down(event):
    global b1
    b1 = "down"

def b1up(event):
    global b1, xold, yold
    b1 = "up"
    xold = None
    yold = None

def motion(event):
    global drawimg
    if b1 == "down":
        global xold, yold
        if xold is not None and yold is not None:
            event.widget.create_line(xold, yold, event.x, event.y, smooth=TRUE, fill="white", width="10")
            drawimg.line((xold / 5, yold / 5, event.x / 5, event.y / 5), fill=255, width=2)
        xold = event.x
        yold = event.y

if __name__ == "__main__":
    model.gen_data()
    model.train()
    main()