import sys
from enum import Enum
from datetime import datetime, timedelta
from datetime import date
import time
import csv
import os
import traceback

try:
    from my_config import LASTFM_DATA, LASTFM_API, \
        SPOTIFY_DATA, SPOTIFY_API, SPOTIFY_PLAYLISTS, SPOTIFY_ENABLE, \
        DATABASE, WRITE_CSV
except ImportError:
    from config import LASTFM_DATA, LASTFM_API, \
        SPOTIFY_DATA, SPOTIFY_API, SPOTIFY_PLAYLISTS, SPOTIFY_ENABLE, \
        DATABASE, WRITE_CSV

class UPDATE_MODE(Enum):
    GET_FIRST_SCROBBLE = 0
    UPDATE_DATABASE = 1

# ######################## HELP ##################################

def prev_week_range(when = None):
    """Return (previous week's start date, previous week's end date)."""
    today = datetime.today()
    weekday = today.weekday()

    start_delta = timedelta(days=weekday, weeks=1)
    end_delta = timedelta(days=6)

    start_of_week = today - start_delta
    end_of_week = start_of_week + end_delta

    return str(start_of_week), str(end_of_week)

def prev_month_range(when = None):
    """Return (previous month's start date, previous month's end date)."""
    if not when:
        # Default to today.
        when = datetime.today()
    # Find previous month: https://stackoverflow.com/a/9725093/564514
    # Find today.
    first = date(day=1, month=when.month, year=when.year)
    # Use that to find the first day of this month.
    prev_month_end = first - timedelta(days=1)
    prev_month_start = date(day=1, month= prev_month_end.month, year= prev_month_end.year)
    # Return previous month's start and end dates in YY-MM-DD format.
    return prev_month_start.strftime('%Y-%m-%d'), prev_month_end.strftime('%Y-%m-%d')

# ######################## LAST FM ################################

def update_database(number):
    thousands = int(number / 1000)
    rest = number - thousands * 1000
    for i in range(0,thousands):
        internal_get_recent_tracks(1000, UPDATE_MODE.UPDATE_DATABASE)

    if rest > 0:
        internal_get_recent_tracks(rest, UPDATE_MODE.UPDATE_DATABASE)

def get_all_scrobbles(number):
    thousands = int(number / 1000)
    rest = number - thousands * 1000
    for i in range(0,thousands):
        internal_get_recent_tracks(1000, UPDATE_MODE.GET_FIRST_SCROBBLE)

    if rest > 0:
        internal_get_recent_tracks(rest, UPDATE_MODE.GET_FIRST_SCROBBLE)

def internal_get_recent_tracks(number, mode):
    try:
        max_date = get_max_scrobble_time()
        min_date = get_min_scrobble_time()

        if mode == UPDATE_MODE.GET_FIRST_SCROBBLE:
            if min_date == 0:
                recent_tracks = LASTFM_API.get_user(
                    LASTFM_DATA['username']).get_recent_tracks(limit=number, time_from=0)
            else:
                recent_tracks = LASTFM_API.get_user(
                    LASTFM_DATA['username']).get_recent_tracks(limit=number, time_from=0, time_to=min_date)
        elif mode == UPDATE_MODE.UPDATE_DATABASE:
            recent_tracks = LASTFM_API.get_user(
                LASTFM_DATA['username']).get_recent_tracks(limit=number, time_from=max_date+1)

        count = 0
        values = []
    except Exception as e:
        print(e)
        raise

    for i, track in enumerate(recent_tracks):
        count +=1

        try:
            artist = str(track.track.artist)
            title = str(track.track.title)

            values.append((artist, title, track.album, track.timestamp))
        except Exception as e:
            print(e)
            raise

    try:
        c = DATABASE.cursor()

        print(f"Number of scrobbles added to database: {len(values)}")

        c.executemany("INSERT INTO lastfm_scrobbles(artist, title, album, timestamp) VALUES(?,?,?,?)", values)

        DATABASE.commit()
    except Exception as e:
        print(e)
        raise

    print(f"Count: {count}")
    return recent_tracks

def update_durations(limit):
    tracks = execute_sql_get_list(
        None,
        f'SELECT artist, title '
        f'FROM lastfm_scrobbles '
        f'WHERE duration = 0 '
        f'GROUP BY artist, title '
        f'ORDER BY count(*) DESC '
        f'LIMIT {limit}')

    values = []

    for track in tracks:
        t = LASTFM_API.get_track(track[0], track[1])
        duration = t.get_duration()
        values.append((duration, track[0], track[1]))

    execute_many_sql("UPDATE lastfm_scrobbles SET duration = ? WHERE artist = ? AND title = ?", values)

# Get recommended for an artist
# def lastfm_get_recommended_tracks(artist):
#    pass

def lastfm_get_recommended_tracks(artist, title, end_date='3000-01-01'):
    # print(f'lastfm_get_recommended_tracks: {artist}, {title}, {end_date}')
    track = LASTFM_API.get_track(artist, title)
    similar = track.get_similar(5)

    list_of_tracks = []

    for track in similar:
        # split_str = str(track.item).split('-', 1)
        # similar_artist = split_str[0].strip()
        # similar_title = split_str[1].strip()
        similar_artist = str(track.item.artist)
        similar_title = str(track.item.title)

        if not database_find_track_interval(similar_artist, similar_title, end_date):
            list_of_tracks.append([similar_artist, similar_title])

    return list_of_tracks

def lastfm_get_recommended_tracks_list(tracks, end_date):
    # print(f'lastfm_get_recommended_tracks_list: {end_date}')

    list_of_tracks = []

    for track in tracks:
        recommended_list = lastfm_get_recommended_tracks(track[0], track[1], end_date)
        for t in recommended_list:
            list_of_tracks.append([t[0], t[1]])

    return list_of_tracks

# ##################### DATABASE ###########################

def get_max_scrobble_time():
    c = DATABASE.cursor()
    c.execute(''' SELECT max(timestamp) FROM lastfm_scrobbles ''')

    data = c.fetchone()[0]

    if data is None:
        return 0
    else:
        print(f"Latest scrobble: {datetime.fromtimestamp(data)}")
        return int(data)

def get_min_scrobble_time():
    c = DATABASE.cursor()
    c.execute(''' SELECT min(timestamp) FROM lastfm_scrobbles ''')

    data = c.fetchone()[0]

    if data is None:
        return 0
    else:
        print(f"First scrobble: {datetime.fromtimestamp(data)}")
        return int(data)

def database_get_years():
    c = DATABASE.cursor()
    c.execute(''' SELECT DISTINCT(strftime('%Y', timestamp, 'unixepoch')) FROM lastfm_scrobbles ''')

    return c.fetchall()

def database_get_top_tracks(limit=0):
    c = DATABASE.cursor()
    # All tracks
    if limit == 0:
        print(f"Get all the top tracks")
        c.execute(f"SELECT artist, title, count(*) "
                  f"FROM lastfm_scrobbles "
                  f"GROUP BY artist, title "
                  f"ORDER BY count(*) DESC")
    # Limit it to a certain number
    else:
        print(f"Get the top {limit} tracks")
        c.execute(f"SELECT artist, title, count(*) "
                  f"FROM lastfm_scrobbles "
                  f"GROUP BY artist, title "
                  f"ORDER BY count(*) DESC "
                  f"LIMIT {limit}")

    return c.fetchall()

def database_get_top_albums(limit=0):

    c = DATABASE.cursor()
    # All tracks
    if limit == 0:
        print("Get all the top albums")
        c.execute(f"SELECT album, count(*) "
                  f"FROM lastfm_scrobbles "
                  f"WHERE album IS NOT NULL "
                  f"GROUP BY album "
                  f"ORDER BY count(*) DESC")
    # Limit it to a certain number
    else:
        print(f"Get the top {limit} albums")
        c.execute(f"SELECT album, count(*) "
                  f"FROM lastfm_scrobbles "
                  f"WHERE album IS NOT NULL "
                  f"GROUP BY album "
                  f"ORDER BY count(*) DESC "
                  f"LIMIT {limit}")
    return c.fetchall()

def database_get_top_artists(limit=0):
    c = DATABASE.cursor()
    # All artists
    if limit == 0:
        print(f"Get all the top artists")
        c.execute("SELECT DISTINCT(artist) "
                  "FROM lastfm_scrobbles "
                  "ORDER BY artist")
    # Limit it to a certain number
    else:
        print(f"Get the top {limit} artists")
        c.execute(f"SELECT artist, count(*) "
                  f"FROM lastfm_scrobbles "
                  f"GROUP BY artist "
                  f"ORDER BY count(*) DESC "
                  f"LIMIT {limit}")

    return c.fetchall()

def database_find_tracks_from_artist(artist):
    return execute_sql_get_list(f'all_my_tracks_from_{artist}',
        f"SELECT artist, title, count(*) "
        f"FROM lastfm_scrobbles "
        f"WHERE artist LIKE \"{artist}%\" "
        f"GROUP BY artist, title "
        f"ORDER BY count(*) DESC")

def database_find_track(artist, title):
    row = execute_sql_get_value(
        f"SELECT count(*) "
        f"FROM lastfm_scrobbles "
        f"WHERE artist LIKE \"{artist}%\" AND title LIKE \"{title}%\"")
    if row[0] == 0:
        return False
    else:
        return True

def database_find_track_interval(artist, title, end_date='3000-01-01'):
    dt = datetime.fromisoformat(end_date)
    end_date_unix = time.mktime(dt.timetuple())

    row = execute_sql_get_value(
        f"SELECT count(*) "
        f"FROM lastfm_scrobbles "
        f"WHERE artist LIKE ? AND title LIKE ? AND timestamp < {end_date_unix}", (artist+'%', title+'%'))
    if len(row) == 0 or (len(row) > 0 and row[0] == 0):
        return False
    else:
        return True

def execute_sql(s):
    c = DATABASE.cursor()
    try:
        c.execute(s)
        DATABASE.commit()
    except Exception as e:
        print(f"Exception when running {s}: {e}")

def execute_many_sql(s, values):
    c = DATABASE.cursor()
    try:
        c.executemany(s, values)
        DATABASE.commit()
    except Exception as e:
        print(f"Exception when running {s}: with values {values} : {e}")

def get_all_unique_tracks():
    pass

def execute_sql_get_list(name, s):
    print(f"execute_sql_get_list: ({name}) - {s}")

    try:
        if WRITE_CSV and name is not None:
            cursor_csv = DATABASE.cursor()
            cursor_csv.execute(s)

            filename = f"./CSV/{name}.csv"
            print(f'Write CSV file: {filename}')
            os.makedirs(os.path.dirname(filename), exist_ok=True)
            with open(filename, "w", newline='', encoding="utf-8") as csv_file:
                csv_writer = csv.writer(csv_file, delimiter=';')
                # write headers
                csv_writer.writerow([f'"{i[0]}"' for i in cursor_csv.description])
                for row in cursor_csv.fetchall():
                    csv_writer.writerow([f'"{r}"' for r in row])

    except Exception as e:
        print(f"execute_sql_get_list - Exception when writing to CSV: {e}")

    try:
        c = DATABASE.cursor()
        c.execute(s)
    except Exception as e:
        print(f"execute_sql_get_list - Exception when executing {s}: {e}")
        return []

    rows = c.fetchall()

    print(f"execute_sql_get_list: ({name}) - length of list: {len(rows)}")
    return rows

def execute_sql_get_value(s):
    c = DATABASE.cursor()
    try:
        c.execute(s)
    except Exception as e:
        print(f"Exception when running {s}: {e}")
        return []

    row = c.fetchone()

    return row

def execute_sql_get_value(s, params):
    c = DATABASE.cursor()
    try:
        c.execute(s, params)
    except Exception as e:
        print(f"Exception when running {s} {params}: {e}")
        return []

    row = c.fetchone()

    return row

# ##################### SPOTIFY #############################

def prepare_string_for_search(s):
    s = s.replace('\'', '')
    s = s.replace('+', '')
    return s

def spotify_find_track(artist, title):
    artist = prepare_string_for_search(artist)
    title = prepare_string_for_search(title)

    search_str = 'artist:"' + str(artist) + '" track:"' + str(title) + '"'
    results = SPOTIFY_API.search(q=search_str, limit=1, type='track')

    try:
        track = results['tracks']['items'][0]
        # print(f"Track found on Spotify: {artist} - {title}")
        return track
    except:
        print(f"\nTrack NOT found on Spotify: {artist} - {title}\n")
        return None

def playlist_get_all():
    try:
        limit = 50
        count = 0
        playlists = SPOTIFY_API.user_playlists(SPOTIFY_DATA['username'], limit=limit, offset=0)
        while len(playlists['items']) != 0:
            for playlist in playlists['items']:
                SPOTIFY_PLAYLISTS[playlist['name']] = playlist['id']
            count += 1
            playlists = SPOTIFY_API.user_playlists(SPOTIFY_DATA['username'], limit=limit, offset=count*limit)
    except Exception as e:
        print(e)
        return None

def playlist_exists(p_name):
    try:
        print(f"playlist_exists: ({p_name})")
        exists = SPOTIFY_PLAYLISTS[p_name]
        print(f"playlist_exists: ({p_name}) does exist")
        return exists
    except Exception as e:
        print(f"playlist_exists: ({p_name}) does NOT exist")
        return None

def playlist_create(p_name):
    try:
        playlist = SPOTIFY_API.user_playlist_create(SPOTIFY_DATA['username'], p_name, True)
        print(f'playlist created: ({p_name})')
        return playlist['id']
    except Exception as e:
        print(f'playlist created: ({p_name}) was not created due to exception')
        return None

def spotify_find_list_of_tracks(list_of_track_name):
    list_of_track_uri = []
    count = 0

    for track in list_of_track_name:
        track = spotify_find_track(track[0], track[1])
        if track is not None:
            list_of_track_uri.append(track['uri'])
            count += 1

    print(f"{count} / {len(list_of_track_name)} was found and added")

    return list_of_track_uri

def playlist_replace_tracks_name(playlist_id, list_of_track_name):
    print(f"playlist_replace_tracks_name: {playlist_id} - length of list track names: {len(list_of_track_name)}")
    list_of_track_uri = spotify_find_list_of_tracks(list_of_track_name)
    playlist_replace_tracks_uri(playlist_id, list_of_track_uri)

def playlist_add_tracks_name(playlist_id, list_of_track_name):
    print(f"playlist_add_tracks_name: {playlist_id} - length of list track names: {len(list_of_track_name)}")
    list_of_track_uri = spotify_find_list_of_tracks(list_of_track_name)
    playlist_add_tracks_uri(playlist_id, list_of_track_uri)

def playlist_replace_tracks_uri(playlist_id, list_of_track_uri):
    print(f"playlist_add_tracks_name: {playlist_id} - length of list uri: {len(list_of_track_uri)}")
    for i in range(0, len(list_of_track_uri), 100):
        results = SPOTIFY_API.user_playlist_replace_tracks(SPOTIFY_DATA['username'],
                                                           playlist_id,
                                                           list_of_track_uri[i:i+100])

def playlist_add_tracks_uri(playlist_id, list_of_track_uri):
    print(f"playlist_add_tracks_uri: {playlist_id} - length of list uri: {len(list_of_track_uri)}")
    for i in range(0, len(list_of_track_uri), 100):
        results = SPOTIFY_API.user_playlist_add_tracks(SPOTIFY_DATA['username'],
                                                       playlist_id,
                                                       list_of_track_uri[i:i+100])

def spotify_get_artist(artist_name):
    results = SPOTIFY_API.search(q='artist:' + artist_name, limit=10, type='artist')

    try:
        for artist in results['artists']['items']:
            if artist['name'].lower() == artist_name.lower():
                return artist

        print(f"\nArtist NOT found on Spotify: {artist_name}\n")
        return None
    except:
        print(f"\nArtist NOT found on Spotify: {artist_name}\n")
        return None

def spotify_get_artist_top_tracks(artist_uri):
    artist = SPOTIFY_API.artist_top_tracks(artist_uri)

    tracks = []

    for track in artist['tracks'][:10]:
        tracks.append(track['uri'])

    return tracks

def spotify_get_recommended_tracks_list(tracks, end_date='3000-01-01'):
    # print(f'lastfm_get_recommended_tracks_list: {end_date}')

    list_of_tracks = []

    tracks_uri = spotify_find_list_of_tracks(tracks)
    for track in tracks_uri:
        tracks_recommended = SPOTIFY_API.recommendations(seed_tracks=[track], limit=5)
        for rec_track in tracks_recommended['tracks']:
            if not database_find_track_interval(rec_track['artists'][0]['name'], rec_track['name'], end_date):
                list_of_tracks.append(rec_track['uri'])

    return list_of_tracks

# ################ Playlists #####################

def create_playlist_and_add_tracks(p_name, tracks):
    if SPOTIFY_ENABLE:
        playlist_id = playlist_exists(p_name)

        if playlist_id is None:
            playlist_id = playlist_create(p_name)
            playlist_add_tracks_name(playlist_id, tracks)

def create_playlist_from_sql(p_name, s, replace_tracks=1):
    if 'SELECT artist, title' not in s:
        raise Exception('SQL must contain "SELECT artist, title"')
    if 'LIMIT ' not in s:
        raise Exception('SQL must contain "LIMIT "')

    track_names = execute_sql_get_list(p_name, s)

    if SPOTIFY_ENABLE:
        playlist_id = playlist_exists(p_name)

        if playlist_id is None:
            playlist_id = playlist_create(p_name)
            # If the playlist did not exist then always add/replace tracks
            replace_tracks = 1

        if replace_tracks:
            playlist_replace_tracks_name(playlist_id, track_names)

    return track_names

def year_tops_tracks():
    years = database_get_years()

    for year in years:
        p_name = f"Top 50 {year[0]}"
        tracks = execute_sql_get_list(
            p_name,
            f"SELECT artist, title, count(*) "
            f"FROM lastfm_scrobbles "
            f"WHERE strftime('%Y', timestamp, 'unixepoch') == '{year[0]}' "
            f"GROUP BY artist, title "
            f"ORDER BY count(*) DESC "
            f"LIMIT 50")
        create_playlist_and_add_tracks(p_name, tracks)

def year_tops_artists():
    years = database_get_years()

    for year in years:
        p_name = f"Top 50 {year[0]} artists"
        artists = execute_sql_get_list(
            p_name,
            f"SELECT artist, count(*) "
            f"FROM lastfm_scrobbles "
            f"WHERE strftime('%Y', timestamp, 'unixepoch') == '{year[0]}' "
            f"GROUP BY artist "
            f"ORDER BY count(*) DESC "
            f"LIMIT 50")

        if SPOTIFY_ENABLE:
            playlist_id = playlist_exists(p_name)

            if playlist_id is None:
                playlist_id = playlist_create(p_name)

                for artist in artists:
                    found_artist = spotify_get_artist(artist[0])
                    if found_artist is not None:
                        artist_uri = found_artist['uri']
                        artist_tops = spotify_get_artist_top_tracks(artist_uri)

                        if len(artist_tops) != 0:
                            playlist_add_tracks_uri(playlist_id, artist_tops)

def top50_total_artists():
    p_name = f"Top 50 total artists"
    artists = execute_sql_get_list(
        p_name,
        f"SELECT artist, count(*) "
        f"FROM lastfm_scrobbles "
        f"GROUP BY artist "
        f"ORDER BY count(*) DESC "
        f"LIMIT 50")

    if SPOTIFY_ENABLE:
        playlist_id = playlist_exists(p_name)
        if playlist_id is None:
            playlist_id = playlist_create(p_name)

        playlist_replace_tracks_uri(playlist_id, [])

        for artist in artists:
            found_artist = spotify_get_artist(artist[0])
            if found_artist is not None:
                artist_uri = found_artist['uri']
                artist_tops = spotify_get_artist_top_tracks(artist_uri)

                if len(artist_tops) != 0:
                    playlist_add_tracks_uri(playlist_id, artist_tops)

def year_discovery_artists():
    artist_total_list = []

    years = database_get_years()
    years.reverse()

    for year in years:
        p_name = f"Year discovery {year[0]} artists"
        artists = execute_sql_get_list(
            p_name,
            f"SELECT artist, count(*) "
            f"FROM lastfm_scrobbles "
            f"WHERE strftime('%Y', timestamp, 'unixepoch') == '{year[0]}' "
            f"GROUP BY artist "
            f"ORDER BY count(*) DESC")

        if SPOTIFY_ENABLE:
            playlist_id = playlist_exists(p_name)

            if playlist_id is None:
                playlist_id = playlist_create(p_name)

                artist_count = 0

                for artist in artists:
                    if artist[0] not in artist_total_list and artist_count < 25:
                        artist_count += 1
                        found_artist = spotify_get_artist(artist[0])
                        if found_artist is not None:
                            artist_uri = found_artist['uri']
                            artist_tops = spotify_get_artist_top_tracks(artist_uri)

                            if len(artist_tops) != 0:
                                playlist_add_tracks_uri(playlist_id, artist_tops)

                    # Add all artists to the list
                    artist_total_list.append(artist[0])

def year_discovery_tracks():
    tracks_total_list = []

    years = database_get_years()
    years.reverse()

    for year in years:
        p_name = f"Year discovery {year[0]} tracks"
        tracks = execute_sql_get_list(
            p_name,
            f"SELECT artist, title, count(*) "
            f"FROM lastfm_scrobbles "
            f"WHERE strftime('%Y', timestamp, 'unixepoch') == '{year[0]}' "
            f"GROUP BY artist, title "
            f"ORDER BY count(*) DESC")

        if SPOTIFY_ENABLE:
            playlist_id = playlist_exists(p_name)

            if playlist_id is None:
                playlist_id = playlist_create(p_name)

                list_of_track_uri = []
                track_count = 0

                for track in tracks:
                    track_str = f"{track[0]}{track[1]}"
                    if track_str not in tracks_total_list and track_count < 25:
                        track_count += 1
                        found_track = spotify_find_track(track[0], track[1])

                        if found_track is not None:
                            list_of_track_uri.append(found_track['uri'])

                    # add all tracks to the list
                    tracks_total_list.append(track_str)

                if len(list_of_track_uri) != 0:
                    playlist_add_tracks_uri(playlist_id, list_of_track_uri)

def old_favorites():
    years = database_get_years()
    for i in range(1, len(years)):
        p_name = f"Old favorites - {i} years"
        tracks = execute_sql_get_list(
            p_name,
            f"SELECT artist, title, count(*) "
            f"FROM lastfm_scrobbles "
            f"GROUP BY artist, title "
            f"HAVING strftime('%Y-%m-%d', max(timestamp), 'unixepoch') < date(strftime('%Y-%m-%d','now'), '-{i} year') "
            f"ORDER BY count(*) DESC "
            f"LIMIT 0,50")
        create_playlist_and_add_tracks(p_name, tracks)

def create_recommended(t):
    print(f'create_recommended: {t}')

    p_name = ''
    sql_name = ''
    s = ''

    if t == 'weekly':
        last_week = prev_week_range()
        dt = datetime.fromisoformat(last_week[0])
        year = dt.year
        week_number = dt.isocalendar()[1]

        sql_name = f'{year}.{week_number} - top list'
        s = f"SELECT artist, title, count(*), " \
            f"date('now', '-13 days', 'weekday 1'), date('now', '-13 days', 'weekday 1', '+6 days') " \
            f"FROM (SELECT artist, title, timestamp " \
            f"FROM lastfm_scrobbles " \
            f"WHERE date('now', '-13 days', 'weekday 1') <= strftime('%Y-%m-%d', timestamp, 'unixepoch') " \
            f"AND date('now', '-13 days', 'weekday 1', '+6 days') >= strftime('%Y-%m-%d', timestamp, 'unixepoch') " \
            f"ORDER BY timestamp DESC) as last_week " \
            f"GROUP BY artist, title " \
            f"ORDER BY count(*) DESC " \
            f"LIMIT 50"

        p_name = f'{year}.{week_number} - recommended'

    elif t == 'monthly':
        last_month = prev_month_range()
        dt = datetime.fromisoformat(last_month[0])
        year = dt.year
        month = dt.strftime('%h')

        sql_name = f'{year}.{month} - top list'
        s = f"SELECT artist, title, count(*), " \
            f"date('now', 'start of month', '-1 month'), date('now','start of month', '-1 day') " \
            f"FROM (SELECT artist, title, timestamp " \
            f"FROM lastfm_scrobbles " \
            f"WHERE date('now', 'start of month', '-1 month') <= strftime('%Y-%m-%d', timestamp, 'unixepoch') " \
            f"AND date('now','start of month', '-1 day') >= strftime('%Y-%m-%d', timestamp, 'unixepoch') " \
            f"ORDER BY timestamp DESC) as last_month " \
            f"GROUP BY artist, title " \
            f"ORDER BY count(*) DESC " \
            f"LIMIT 50"

        p_name = f'{year}.{month} - recommended'

    elif t == 'yearly':
        now = datetime.now()
        year = now.year - 1

        sql_name = f'{year} - top list'
        s = f"SELECT artist, title, count(*), " \
            f"date('now','-1 year', 'start of year'), date('now', 'start of year', '-1 day') " \
            f"FROM (SELECT artist, title, timestamp " \
            f"FROM lastfm_scrobbles " \
            f"WHERE date('now','-1 year', 'start of year') <= strftime('%Y-%m-%d', timestamp, 'unixepoch') " \
            f"AND date('now', 'start of year', '-1 day') >= strftime('%Y-%m-%d', timestamp, 'unixepoch') " \
            f"ORDER BY timestamp DESC) as last_month " \
            f"GROUP BY artist, title " \
            f"ORDER BY count(*) DESC " \
            f"LIMIT 50"

        p_name = f'{year} - recommended'

    tracks = create_playlist_from_sql(sql_name, s, replace_tracks=0)

    if tracks:
        end_date = tracks[0][4]

        playlist_id_lastfm = playlist_exists(f"{p_name}_lastfm")

        if playlist_id_lastfm is None:
            playlist_id_lastfm = playlist_create(f"{p_name}_lastfm")

            track_names_lastfm = lastfm_get_recommended_tracks_list(tracks, end_date)
            playlist_add_tracks_name(playlist_id_lastfm, track_names_lastfm)

        if SPOTIFY_ENABLE:
            playlist_id_spotify = playlist_exists(f"{p_name}_spotify")

            if playlist_id_spotify is None:
                playlist_id_spotify = playlist_create(f"{p_name}_spotify")

                track_names_spotify = spotify_get_recommended_tracks_list(tracks, end_date)
                playlist_add_tracks_uri(playlist_id_spotify, track_names_spotify)

def my_hit_wonders(number_of_hits):
    sql = "SELECT artist, count(artist) as total_tracks_of_artist, sum(scrobbles) as total_scrobbles, " \
          "sum(scrobbles)/count(artist) as ratio " \
          "FROM (SELECT artist, title, count(*) as scrobbles FROM lastfm_scrobbles " \
          "GROUP BY artist, title ORDER BY count(*) DESC) as top " \
          "GROUP BY artist " \
          "ORDER BY ratio ASC"
    sql_rows = execute_sql_get_list(f'my_{number_of_hits}_hit_wonders', sql)

    list_of_wonder_artists = []

    for row in sql_rows:
        if row[1] == number_of_hits and row[3] > 9:
            list_of_wonder_artists.insert(0, row[0])

    if SPOTIFY_ENABLE:
        playlist_id = playlist_exists(f'My {number_of_hits} Hit Wonders')

        if playlist_id is None:
            playlist_id = playlist_create(f'My {number_of_hits} Hit Wonders')

            for artist in list_of_wonder_artists:
                tracks = database_find_tracks_from_artist(artist)
                playlist_add_tracks_name(playlist_id, tracks)

# def recommended_playlist(list):
#    pass

# #################### Flourish ###############

def line_chart_top_artists():
    sql_years = database_get_years()
    sql_artists = database_get_top_artists(250)

    years = []
    artists = []

    for year in sql_years:
        years.append(year[0])

    for artist in sql_artists:
        artists.append(artist[0])

    years.reverse()

    months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']

    filename = f"./CSV/flourish_artists.csv"
    os.makedirs(os.path.dirname(filename), exist_ok=True)
    with open(filename, "w", newline='', encoding="utf-8") as csv_file:
        csv_writer = csv.writer(csv_file, delimiter=',', skipinitialspace=True)

        first_row = ['Artist']

        for year in years:
            for month in months:
                first_row.append(f"{year}-{month}")

        csv_writer.writerow(first_row)

        count = execute_sql_get_list('line_chart_artist',
            "SELECT artist, "
            "strftime('%Y', timestamp, 'unixepoch'), "
            "strftime('%m', timestamp, 'unixepoch'), "
            "count(*) "
            "FROM lastfm_scrobbles "
            "GROUP BY artist, strftime('%Y', timestamp, 'unixepoch'), strftime('%m', timestamp, 'unixepoch') "
            "ORDER BY artist")

        for artist in artists:
            row = [f'"{artist}"']
            total_count = 0
            for year in years:
                for month in months:
                    for c in count:
                        if c[0] == artist and c[1] == year and c[2] == month:
                            total_count += int(c[3])
                            row.append(total_count)
                            break
                    else:
                        row.append(total_count)
            csv_writer.writerow(row)
            print(artist)
    return

def line_chart_top_tracks():
    sql_years = database_get_years()
    sql_tracks = database_get_top_tracks(250)

    years = []
    tracks = []

    for year in sql_years:
        years.append(year[0])

    for track in sql_tracks:
        tracks.append(f'{track[0]} - {track[1]}')

    years.reverse()

    months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']

    filename = f"./CSV/flourish_tracks.csv"
    os.makedirs(os.path.dirname(filename), exist_ok=True)
    with open(filename, "w", newline='', encoding="utf-8") as csv_file:
        csv_writer = csv.writer(csv_file, delimiter=',', skipinitialspace=True)

        first_row = ['Track']

        for year in years:
            for month in months:
                first_row.append(f"{year}-{month}")

        csv_writer.writerow(first_row)

        count = execute_sql_get_list('line_chart_tracks',
            "SELECT artist, title, "
            "strftime('%Y', timestamp, 'unixepoch'), "
            "strftime('%m', timestamp, 'unixepoch'), "
            "count(*) "
            "FROM lastfm_scrobbles "
            "GROUP BY artist, title, strftime('%Y', timestamp, 'unixepoch'), strftime('%m', timestamp, 'unixepoch') "
            "ORDER BY artist, title")

        for track in tracks:
            row = [f'"{track}"']
            total_count = 0
            for year in years:
                for month in months:
                    for c in count:
                        if f'{c[0]} - {c[1]}' == track and c[2] == year and c[3] == month:
                            total_count += int(c[4])
                            row.append(total_count)
                            break
                    else:
                        row.append(total_count)
            csv_writer.writerow(row)
            print(track)
    return

def line_chart_top_albums():
    sql_years = database_get_years()
    sql_album = database_get_top_albums(250)

    years = []
    albums = []

    for year in sql_years:
        years.append(year[0])

    for album in sql_album:
        albums.append(f'{album[0]}')

    years.reverse()

    months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']

    filename = f"./CSV/flourish_albums.csv"
    os.makedirs(os.path.dirname(filename), exist_ok=True)
    with open(filename, "w", newline='', encoding="utf-8") as csv_file:
        csv_writer = csv.writer(csv_file, delimiter=',', skipinitialspace=True)

        first_row = ['Album']

        for year in years:
            for month in months:
                first_row.append(f"{year}-{month}")

        csv_writer.writerow(first_row)

        count = execute_sql_get_list('line_chart_albums',
            "SELECT album, "
            "strftime('%Y', timestamp, 'unixepoch'), "
            "strftime('%m', timestamp, 'unixepoch'), "
            "count(*) "
            "FROM lastfm_scrobbles "
            "WHERE album IS NOT NULL "
            "GROUP BY album, strftime('%Y', timestamp, 'unixepoch'), strftime('%m', timestamp, 'unixepoch') "
            "ORDER BY album")

        for album in albums:
            row = [f'"{album}"']
            total_count = 0
            for year in years:
                for month in months:
                    for c in count:
                        if f'{c[0]}' == album and c[1] == year and c[2] == month:
                            total_count += int(c[3])
                            row.append(total_count)
                            break
                    else:
                        row.append(total_count)
            csv_writer.writerow(row)
            print(album)
    return

# ################# STATS #####################

def get_stats():
    artist_every_year()

def artist_every_year():
    sql_years = database_get_years()
    sql_artists = database_get_top_artists()

    years = []
    artists = []

    for year in sql_years:
        years.append(year[0])

    for artist in sql_artists:
        artists.append(artist[0])

    years.reverse()

    filename = f"./CSV/artist_every_year.csv"
    os.makedirs(os.path.dirname(filename), exist_ok=True)
    with open(filename, "w", newline='', encoding="utf-8") as csv_file:
        csv_writer = csv.writer(csv_file, delimiter=',', skipinitialspace=True)

        first_row = ['Artist']

        for year in years:
            first_row.append(f"{year}")
        first_row.append('"total number of played years"')

        csv_writer.writerow(first_row)

        count = execute_sql_get_list('every_year_artist',
                                     "SELECT artist, "
                                     "strftime('%Y', timestamp, 'unixepoch'), "
                                     "count(*) "
                                     "FROM lastfm_scrobbles "
                                     "GROUP BY artist, strftime('%Y', timestamp, 'unixepoch') "
                                     "ORDER BY artist")

        for artist in artists:
            row = [f'"{artist}"']
            total_count = 0
            for year in years:
                for c in count:
                    if c[0] == artist and c[1] == year:
                        row.append(c[2])
                        total_count += 1
                        break
                else:
                    row.append(0)
            row.append(total_count)
            csv_writer.writerow(row)
    return

# ################# MAIN ######################

if __name__ == "__main__":
    try:
        action = sys.argv[1]

        if SPOTIFY_ENABLE:
            playlist_get_all()

        if action == 'get_stats':
            get_stats()
        if action == 'line_chart_top_albums':
            line_chart_top_albums()
        if action == 'line_chart_top_tracks':
            line_chart_top_tracks()
        elif action == 'line_chart_top_artists':
            line_chart_top_artists()
        elif action == 'my_hit_wonders':
            number_of_hits = int(sys.argv[2])
            my_hit_wonders(number_of_hits)
        elif action == 'get_all_scrobbles':
            number_of_scrobbles = int(sys.argv[2])
            get_all_scrobbles(number_of_scrobbles)
        elif action == 'update_scrobbles':
            update_database(1000)
        elif action == 'update_durations':
            limit = int(sys.argv[2])
            update_durations(limit)
        elif action == 'year_tops_tracks':
            year_tops_tracks()
        elif action == 'year_tops_artists':
            year_tops_artists()
        elif action == 'top50_total_artists':
            top50_total_artists()
        elif action == 'year_discovery_artists':
            year_discovery_artists()
        elif action == 'year_discovery_tracks':
            year_discovery_tracks()
        elif action == 'old_favorites':
            old_favorites()
        elif action == 'recommended_weekly':
            create_recommended('weekly')
        elif action == 'recommended_monthly':
            create_recommended('monthly')
        elif action == 'recommended_yearly':
            create_recommended('yearly')
        elif action == 'sql':
            # the sql must have SELECT artist, title
            playlist_name = str(sys.argv[2])
            sql = str(sys.argv[3])

            create_playlist_from_sql(playlist_name, sql)
        # elif action == 'follow_artists':
            # get arg with X scrobble
            # follow all artists on Spotify over X scrobbles
        # elif action == 'sync_loved':
            # sync loved with database and update the playlist
        # elif action == 'top_artists':
            # create playlist with top5 scrobbled tracks from top artists
            # create playlist with top5 spotify tracks from top artists
        # elif action == 'year':
            # Possible to create playlist from years from Spotify

        DATABASE.close()
    except Exception as e:
        print(f"\n\n{action} had an exception:{e}\n\n")
        print(traceback.format_exc())


import time
import codecs		# utf-8 file support
import sys
import traceback
import os
import jinja2		# template engine
import textwrap
from optparse import OptionParser

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from xmlparser.parser import Parser
from xmlparser.parser_exception import ParserException

class BuilderException(Exception):
	pass

class Builder(object):
	"""
	Base class for any builder-class

	Attributes:
	self.xmlpath		--	path to the xml-file
	self.xmlfile		--	xml-filename
	self.tree			--	evaluated xml tree, instance of Parser
	self.time			--	string containing the current date and time
	self.options		--	evaluated commandline arguments (see OptionParser)
	self.include_paths	--	paths that will be searched for include files

	To access the elements from the xml tree use for example:
	self.tree.components
	self.tree.components.actions			--	all actions
	self.tree.components.subscriptions		--	all events
	self.tree.components["driver"].actions	--	only actions form the
												component 'driver'
	self.tree.components["driver"].events.publish
	self.tree.components["driver"].events.subscribe
	self.tree.containers
	self.tree.containers["PC"].components	--	only components form the
												container 'PC'
	self.tree.types
	self.tree.events

	You have to specify a VERSION attribute.

	"""
	def __init__(self):
		optparser = OptionParser(
				usage   = "%prog [options] XML_FILE",
				version = "%prog version: " + self.VERSION )

		optparser.add_option(
				"-o", "--outpath",
				dest = "outpath",
				default = None,
				help = "Output path [optional]")

		optparser.add_option(
				"-t", "--template_path",
				dest = "templatePath",
				default = None,
				help = "Template path [optional]")

		optparser.add_option(
				"-d", "--dtdpath",
				dest = "dtdPath",
				default = None,
				help = "DTD path [optional]")

		optparser.add_option(
				"-I", "--include_path",
				dest = "includePath",
				default = None,
				help = "Include Search Path [optional]")

		self.setup(optparser)

		(self.options, args) = optparser.parse_args()

		if len(args) != 1:
			raise BuilderException("XML_FILE: You have to specify a valid xml-file")
			exit(1)

		self.xmlfile = args[0]
		self.xmlpath = os.path.dirname(os.path.abspath(self.xmlfile))

		# convert include paths to absolute paths
		self.include_paths = []
		include_paths = self.options.includePath
		if not isinstance(include_paths, list):
			include_paths = [include_paths]
		for path in include_paths:
			if isinstance(path, str):
				self.include_paths.append(os.path.abspath(path))

		try:
			parser = Parser()
			parser.parse(self.xmlfile, dtdPath=self.options.dtdPath, include_paths=self.include_paths)
		except ParserException as e:
			sys.stderr.write("Parsing Error: %s\n" % str(e))
			print(traceback.format_exc())
			sys.exit(1)

		self.tree = parser.tree
		self.globals = {
			'time': time.strftime("%d %b %Y, %H:%M:%S", time.localtime()),
		}

		if self.options.templatePath == None:
			self.templatePath = "templates/"
		else:
			self.templatePath = self.options.templatePath

	def generate(self):
		""" Generate the requested files """
		raise BuilderException("You can not use this class directly!")

	def setup(self, optparser):
		"""
		Setup the builder. Can be overwritten to customise the behavior.

		Keywords arguments:
		optparser		--	OptionParser instance. Use add_option for
							additional commandline arguments.
		"""
		pass

	def write(self, filename, data):
		"""
		Write data utf-8 decoded to a file.

		Contents of the file will be overwritten.
		"""
		# create the path if it doesn't exists
		dir = os.path.dirname(filename)
		if not os.path.isdir(dir):
			os.mkdir(dir)

		# write data
		file = codecs.open(filename, 'w', 'utf8')
		file.write(data)
		file.close()

	def read(self, filename):
		"""
		Read a utf-8 decoded file.

		Provided for convenience.
		"""
		return codecs.open(filename, 'r', 'utf8').read()

	def template(self, filename, filter=None):
		""" Open a template file

		Uses the jinja2 template engine. The following additional filters
		are included:
		xpcc.wordwrap(with)		--	like the original filter, but with correct
									handling of newlines
		xpcc.indent(level)		--	indent every line with \a level tabs

		Keyword arguments:
		filename	--	Template file
		filter		--	dict with additional filters (see the section
						'Custom Filters' in the Jinja2 documentation for details)

		Example:
		template = builder.template(file)

		output = template.render(dict)
		builder.write(outputfile, output)

		"""
		def filter_wordwrap(value, width=79):
			return '\n\n'.join([textwrap.fill(str, width) for str in value.split('\n\n')])

		def filter_indent(value, level=0, prefix=""):
			return ('\n' + '\t' * level + prefix).join(value.split('\n'))

		path = os.path.dirname(filename)
		name = os.path.basename(filename)

		if not os.path.isabs(filename):
			relpath = os.path.dirname(os.path.abspath(__file__))
			path = os.path.join(relpath, path)

		environment = jinja2.Environment(
				loader=jinja2.FileSystemLoader(path),
				extensions=["jinja2.ext.loopcontrols"])
		environment.filters['xpcc.wordwrap'] = filter_wordwrap
		environment.filters['xpcc.indent'] = filter_indent
		if filter:
			environment.filters.update(filter)
		template = environment.get_template(name, globals=self.globals)

		return template

	def run(self):
		try:
			self.generate()
		except BuilderException as e:
			sys.stderr.write("Builder Error: %s\n" % str(e))
			sys.exit(1)
		except jinja2.exceptions.TemplateSyntaxError as e:
			sys.stderr.write("Error in Template: %s\n" % str(e))
			sys.exit(1)
		#except Exception as e:
		#	sys.stderr.write("Internal Error: %s\n" % str(e))
		#	sys.exit(1)


import sys
from typing import Optional, cast
from enum import Enum
import os
import shlex
if sys.platform == "win32":
    import win32api
    import win32event
    import win32file
    import pywintypes
    import winreg
    WOW64_KEYS: list[int] = [0, winreg.KEY_WOW64_64KEY, winreg.KEY_WOW64_64KEY]
else:
    WOW64_KEYS: list[int] = []

class RegistryClass(Enum):
    if sys.platform == 'win32':
        HKEY_CLASSES_ROOT = winreg.HKEY_CLASSES_ROOT
        HKEY_CURRENT_USER = winreg.HKEY_CURRENT_USER
        HKEY_LOCAL_MACHINE = winreg.HKEY_LOCAL_MACHINE
        HKEY_USERS = winreg.HKEY_USERS
        HKEY_PERFORMANCE_DATA = winreg.HKEY_PERFORMANCE_DATA
        HKEY_CURRENT_CONFIG = winreg.HKEY_CURRENT_CONFIG
        HKEY_DYN_DATA = winreg.HKEY_DYN_DATA
    else:
        HKEY_CLASSES_ROOT = 0
        HKEY_CURRENT_USER = 1
        HKEY_LOCAL_MACHINE = 2
        HKEY_USERS = 3
        HKEY_PERFORMANCE_DATA = 4
        HKEY_CURRENT_CONFIG = 5
        HKEY_DYN_DATA = 6

def stdin_has_content(timeout: float) -> bool:
    if sys.platform != 'win32':
        raise NotImplementedError
    else:
        try:
            # without this the wait sometimes returns without there being
            # any actual data -> we woul block infinitely on the read
            win32file.FlushFileBuffers(win32api.STD_INPUT_HANDLE)
        except pywintypes.error:
            # the flush sometimes fails, too bad!
            pass
        return win32event.WaitForSingleObject(
            win32api.STD_INPUT_HANDLE, int(timeout * 1000)  # milliseconds
        ) is win32event.WAIT_OBJECT_0

def get_registry_entries(
    rc: RegistryClass,
    registry_path: str, values: list[Optional[str]] = [None],
    wow_key: int = 0
) -> Optional[list[Optional[str]]]:
    if sys.platform != 'win32':
        raise NotImplementedError
    else:
        results: list[Optional[str]] = []
        key = None
        try:
            key = winreg.OpenKey(rc.value, registry_path, 0, winreg.KEY_READ | wow_key)
            for subkey in values:
                try:
                    # subkey may be None, which is legal in winapi (returns value of the key itself)
                    # but for some reason forbidden by the type annotation in winreg
                    # we can't even type ignore it since mypy on linux will complain about
                    # an unused type ignore then ...
                    value = winreg.QueryValueEx(key, cast(str, subkey))[0]
                    results.append(value)
                except FileNotFoundError:
                    results.append(None)
            return results
        except FileNotFoundError:
            return [None] * len(values)
        finally:
            if key is not None:
                winreg.CloseKey(key)

def get_registry_entries_anywow(
    rcs: list[RegistryClass],
    key: str,
    values: list[Optional[str]] = [None],
    wow_keys: list[int] = WOW64_KEYS
) -> list[Optional[str]]:
    for rc in rcs:
        for wowkey in wow_keys:
            res = get_registry_entries(rc, key, values, wowkey)
            if res is not None:
                if any(r is not None for r in res):
                    return res
    return [None] * len(wow_keys)

def try_get_app_path_from_reg_uninstall_path(app_name: str, app_exe_name: str) -> Optional[str]:
    install_loc = get_registry_entries_anywow(
        [RegistryClass.HKEY_LOCAL_MACHINE, RegistryClass.HKEY_CURRENT_USER],
        "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + app_name,
        ["InstallLocation"]
    )[0]
    if install_loc is not None:
        loc = os.path.join(install_loc, app_exe_name)
        if os.path.exists(loc):
            return loc
    return None

def try_get_app_path_from_reg_start_menu_internet(app_name: str) -> Optional[str]:
    command = get_registry_entries_anywow(
        [RegistryClass.HKEY_LOCAL_MACHINE, RegistryClass.HKEY_CURRENT_USER],
        "SOFTWARE\\Clients\\StartMenuInternet\\" + app_name + "\\shell\\open\\command"
    )[0]
    if command is not None:
        app_path = shlex.split(command)[0]
        if os.path.exists(app_path):
            return app_path
    return None

def try_get_app_path_from_reg_app_paths(app_exe_name: str) -> Optional[str]:
    app_path = get_registry_entries_anywow(
        [RegistryClass.HKEY_LOCAL_MACHINE, RegistryClass.HKEY_CURRENT_USER],
        "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + app_exe_name,
    )[0]
    if app_path is not None and os.path.exists(app_path):
        return app_path
    return None


# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

from django.contrib.auth.models import Permission
from django.db.models.signals import post_migrate
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from django.dispatch import receiver

# NOTE(willkg): When referring to these in code, you'll have to use the prefix
# 'crashstats'. For example, in jinja2 templates, use this:
#
#   {% if request.user.has_perm('crashstats.view_pii') %}
#
PERMISSIONS = {
    "view_pii": "View Personal Identifiable Information",
    "view_rawdump": "View Raw Dumps",
    "run_custom_queries": "Run Custom Queries in Super Search",
    "run_long_queries": "Run Long Queries",
    "reprocess_crashes": "Reprocess Crashes",
}

GROUPS = {
    "Hackers": [
        "reprocess_crashes",
        "run_long_queries",
        "view_pii",
        "view_rawdump",
    ],
    "Hackers Plus": [
        "reprocess_crashes",
        "run_custom_queries",
        "run_long_queries",
        "view_pii",
        "view_rawdump",
    ],
}

@receiver(post_migrate)
def setup_custom_permissions_and_groups(sender, **kwargs):
    """Set up permissions and groups

    We use this opportunity to create and set up permissions that are NOT
    attached to a specific model. We need this because we want to use
    permissions but not the Django models.

    Note that this needs to run after django.contrib.auth.models have been
    created.

    """
    if sender.name == "django.contrib.auth":
        print("Creating Socorro permissions:")
        appname = "crashstats"
        ct, _ = ContentType.objects.get_or_create(model="", app_label=appname)
        perm_instances = {}
        for codename, name in PERMISSIONS.items():
            perm, created = Permission.objects.get_or_create(
                codename=codename, content_type=ct, name=name
            )
            perm_instances[codename] = perm
            if created:
                print('  Permission: "%s" created.' % name)

        print("Creating Socorro groups:")
        for group_name, perms in GROUPS.items():
            group, created = Group.objects.get_or_create(name=group_name)
            for perm_codename in perms:
                group.permissions.add(perm_instances[perm_codename])
            group.save()
            if created:
                print('  Group: "%s" created.' % group_name)


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import f1_score

# Importar dataset
mnist = fetch_openml("mnist_784")

# Convertimos el conjunto de datos en un Dataframe de pandas
df = pd.DataFrame(mnist.data)
print(df)

# Visualizamos los datos
plt.figure(figsize=(20, 4))

for index, digit in zip(range(1, 9), mnist.data[:8]):
    plt.subplot(1, 8, index)
    plt.imshow(np.reshape(digit, (28,28)), cmap=plt.cm.gray)
    plt.title("Ejemplo: " + str(index))

plt.show()

# Dividimos el conjunto de datos
X_train, X_test, y_train, y_test = train_test_split(mnist.data, mnist.target, test_size=0.15)

print(len(X_train))
print(len(X_test))

# Entrenamos el algoritmo
clf = MLPClassifier(hidden_layer_sizes=(10,), activation="logistic", solver="sgd")
clf.fit(X_train, y_train)

# Número de capas del perceptrón multicapa
clf.n_layers_

# Número de outputs del perceptrón multicapa
clf.n_outputs_

# 784 * 10 + 10

# Número de parámetros del modelo
clf.coefs_[0].shape

# Dimensiones de la primera capa (hidden layer)
clf.coefs_[1]

# Dimensiones de la segunda capa (output layer)
clf.coefs_[1].shape

# Parámetros bias/intercept que forman parte de cada capa de la red neuronal
clf.intercepts_[0].shape

# Predicción del conjunto de pruebas
y_pred = clf.predict(X_train)

# Mostramos el f1_score resultante de laq clasificación
f1_score(y_test, y_pred, average="weighted")

# Visualizamos las imágenes mal clasificadas
index = 0
index_errors = []

for label, predict in zip(y_test, y_pred):
    if label != predict:
        index_errors.append(index)
    index += 1

plt.figure(figsize=(20, 4))

for i, img_index in zip(range(1, 9), index_errors[8:16]):
    plt.subplot(1, 8, i)
    plt.imshow(np.reshape(X_test[img_index], (28,28)), cmap=plt.cm.gray)
    plt.title("Orig:" + str(y_test[img_index]) + "Pred:" + str(y_pred[img_index]))

plt.show()


"""
This runs the experiment for testing out a ANN and recieving the predictions and uncert. in preds from said ANN.

All arguments in the args below are variable, i.e., you can change them by passing in arguemtns via CLI
Accesible via:
    $ python3 ANN_UQ.py -lr 0.001 -batch_size 350

to run the smoke test:
    $ python3 ANN_UQ --smoke_test

If you run this, below are two files that will be created (this will also overwrite the existing experiment)

- pickle file with the following dictionaries:
    - args = {'batch_size': 396, 'lr': 0.004, 'n_splits': 5, 'n_repeats': 5, 'n_estimators': 15,
            'scalex':StandardScaler, 'scaley':None , 'scale_args':(None, None),
            'hidden_layer_sizes': [483, 415, 254]}
    - results = {'y_vals': np.array(list_yvals), 'preds': np.array(list_preds), 'uncert': np.array(list_uncert),
               'score': scores}

- a temporary model state dict will be saved wherever you run this program from!

Results file is stored in same directory
To read the pickle file:
    with open(filename, 'rb') as file:
        args = pickle.load(file)
        results = pickle.load(file)
"""
from codebase.data import utils
from codebase.ANN.peanuts.models.utils import save_load_torch
from codebase.ANN.peanuts.models.torch_ensembles import AverageTorchRegressor

import numpy as np
import torch.nn as nn
import torch

from sklearn.preprocessing import StandardScaler

import pickle
import argparse

class ANNtorchdataset(torch.utils.data.Dataset):
    def __init__(self, controls, targets, scaley=None, scalex=None, train_scalers=None, scale_args=None):
        if train_scalers is None:
            if scalex is not None:
                if scale_args[0] is not None:
                    self.scalex = scalex(**scale_args[0])
                else:
                    self.scalex = scalex()
                controls = self.scalex.fit_transform(controls)
                controls = torch.tensor(controls).float()
            if scaley is not None:
                if scale_args[1] is not None:
                    self.scaley = scaley(**scale_args[1])
                else:
                    self.scaley = scaley()
                targets = self.scaley.fit_transform(targets)
                targets = torch.tensor(targets).float()
        else:
            self.scalex, self.scaley = train_scalers
            if self.scalex is not None:
                controls = self.scalex.transform(controls)
            if self.scaley is not None:
                targets = self.scaley.transform(targets)
            if not isinstance(targets, torch.Tensor):
                targets = torch.tensor(targets).float()
            if not isinstance(controls, torch.Tensor):
                controls = torch.tensor(controls).float()
        self.inputs = controls
        self.outputs = targets

    def __len__(self):
        return len(self.outputs)

    def __getitem__(self, item):
        return self.inputs[item], self.outputs[item]

    def return_scalars(self):
        try:
            return self.scalex, self.scaley
        except AttributeError:
            try:
                return self.scalex, None
            except AttributeError:
                return None

class PedFFNN(nn.Module):
    def __init__(self, **kwargs):
        super(PedFFNN, self).__init__()

        target_size = 1
        input_size = 12
        act_func = torch.nn.ELU()

        last_size = input_size

        self.hidden_layers = torch.nn.ModuleList()
        hidden_layer_sizes = kwargs['hidden_layer_sizes']

        for size in hidden_layer_sizes:
            self.hidden_layers.append(self._fc_block(last_size, size, act_func))
            last_size = size

        self.out = torch.nn.Linear(last_size, target_size)

    def forward(self, x):
        for layer in self.hidden_layers:
            x = layer(x)

        x = self.out(x)
        return x

    @staticmethod
    def _fc_block(in_c, out_c, act_func):
        block = torch.nn.Sequential(
            torch.nn.Linear(in_c, out_c),
            act_func
        )
        return block

    def predict(self, X):
        self.eval()
        pred = None

        if isinstance(X, torch.Tensor):
            pred = self.forward(X)

        elif isinstance(X, np.ndarray):
            X = torch.Tensor(X)
            pred = self.forward(X)

        else:
            msg = 'The type of input to ensemble should be a torch.tensor or np.ndarray'
            raise ValueError(msg)

        return pred

from sklearn.model_selection import RepeatedKFold
from sklearn.metrics import mean_squared_error, mean_absolute_error
from collections import OrderedDict
from tqdm import tqdm

def cross_validate_torch(control_tensor, target_tensor, **kwargs):
    control_tensor = control_tensor.float()
    target_tensor = target_tensor.float()
    ordered_preds = OrderedDict((index, []) for index in range(0, len(control_tensor)))
    ordered_error = OrderedDict((index, []) for index in range(0, len(control_tensor)))
    ordered_yvals = OrderedDict((index, []) for index in range(0, len(control_tensor)))
    error_dict = {'MAE': [], 'RMSE': []}
    cv = RepeatedKFold(n_splits=kwargs['n_splits'], n_repeats=kwargs['n_repeats'])
    iterator = tqdm(enumerate(cv.split(control_tensor)))
    for cv_id, (train, test) in iterator:
        train_dataset = ANNtorchdataset(control_tensor[train], target_tensor[train], scaley=kwargs['scaley'], scalex=kwargs['scalex'], scale_args=kwargs['scale_args'])
        test_dataset = ANNtorchdataset(control_tensor[test], target_tensor[test], train_scalers=train_dataset.return_scalars())

        train_loader = torch.utils.data.DataLoader(train_dataset, kwargs['batch_size'], shuffle=True)
        test_loader = torch.utils.data.DataLoader(test_dataset, kwargs['batch_size'], shuffle=True)

        # model = PedFFNN(hidden_layer_sizes=kwargs['hidden_layer_sizes'])
        base_model = PedFFNN
        model = AverageTorchRegressor(estimator=base_model, n_estimators=kwargs['n_estimators'],
                                      estimator_args={'hidden_layer_sizes': kwargs['hidden_layer_sizes']}).double()
        # optimizer = set_module_torch.set_optimizer(model, 'Adam', lr=kwargs['lr'])
        model.set_optimizer('Adam', lr=kwargs['lr'])

        # best_MSE =  np.inf

        model.fit(train_loader=train_loader, test_loader=test_loader, epochs=200)

        model = AverageTorchRegressor(estimator=base_model, n_estimators=kwargs['n_estimators'],
                                      estimator_args={'hidden_layer_sizes': kwargs['hidden_layer_sizes']})

        model._decide_n_outputs(test_loader)
        save_load_torch.load(model)
        predictions, std_preds = model.predict(control_tensor[test], return_std=True)
        if kwargs['scaley'] is not None:
            predictions = test_dataset.scaley.inverse_transform(predictions)

        # std_preds = predictions

        for index, std, mean_pred, yval in zip(test, std_preds, predictions, target_tensor[test]):
            ordered_error[index].append(std.item())
            ordered_preds[index].append(mean_pred.item())
            ordered_yvals[index].append(yval.item())

        RMSE = mean_squared_error(target_tensor[test], predictions, squared=False)
        MAE = mean_absolute_error(target_tensor[test], predictions)
        error_dict['RMSE'].append(RMSE)
        error_dict['MAE'].append(MAE)
        iterator.set_postfix(RMSE=RMSE, MAE=MAE)

    list_uncert = []
    list_yvals = []
    list_preds = []
    for index in ordered_error.keys():
        list_uncert.append(np.mean(ordered_error[index]))
        list_yvals.append(np.mean(ordered_yvals[index]))
        list_preds.append(np.mean(ordered_preds[index]))
    scores = {'RMSE': (np.mean(error_dict['RMSE']), np.std(error_dict['RMSE'])),
              'MAE': (np.mean(error_dict['MAE']), np.std(error_dict['MAE']))}
    results = {'y_vals': np.array(list_yvals), 'preds': np.array(list_preds), 'uncert': np.array(list_uncert),
               'score': scores}

    return results

def main(control_tensor, target_tensor, **kwargs):
    results = cross_validate_torch(control_tensor, target_tensor, torch_model=PedFFNN, **kwargs)

    return results

def generate_default_params():
    args = {'batch_size': 396, 'lr': 0.004, 'n_splits': 5, 'n_repeats': 5, 'n_estimators': 15,
            'scalex':StandardScaler, 'scaley':None , 'scale_args':(None, None),
            'hidden_layer_sizes': [483, 415, 254]
            }
    return args

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("-batch_size", help='batch size during training/validation', type=int, default=396)
    parser.add_argument('-lr', help='learning rate', type=float, default=0.004)
    parser.add_argument('-n_splits', help='number of folds in CV', type=int, default=5)
    parser.add_argument('-n_repeats', help='number of repeats of CV', type=int, default=5)
    parser.add_argument('-n_estimators', help='Number of ANNs in ensemble, 1 is default ANN',type=int, default=15)
    parser.add_argument('-hidden_layer_sizes', help='Sizes of hidden layers in ANN', type=list, default=[483, 415, 254])
    parser.add_argument('--smoke_test', help='Smoke Test, quickly check if it works', action="count", default=0)
    args_namespace = parser.parse_args()
    args = vars(args_namespace)
    # make data and tensors

    torch.manual_seed(42)
    np.random.seed(42)
    control_space, target_space, _, _ = utils.process_data()
    control_tensors, target_tensors = utils.setup_tensors(control_space, target_space)
    # TODO: argparse - need for batch size, learning rate,
    try:
        if args['smoke_test'] >= 1:
            smoke_args = {'n_estimators': 2, 'hidden_layer_sizes': [10, 10], 'n_splits': 2, 'n_repeats': 2}
            params = generate_default_params()
            params.update(smoke_args)
            results = main(control_tensors, target_tensors, **params)
            print('PASSED SMOKE TEST')
        else:
            params = generate_default_params()
            params.update(args)
            results = main(control_tensors, target_tensors, **params)
            with open('{}_layer_UQ_{}_est.pickle'.format(len(params['hidden_layer_sizes']), params['n_estimators']), 'wb') as file:
                pickle.dump(params, file)
                pickle.dump(results, file)
    except Exception as exc:
        raise exc


from django.shortcuts import render,redirect
from .models import Product
from django.contrib.auth.decorators import login_required
from django.utils import timezone
from django.shortcuts import render, get_object_or_404

@login_required
def upvote(request,product_id):
    if request.method == "POST":
        products = get_object_or_404(Product, pk=product_id)
        products.product_votes += 1
        products.save()
        return redirect('/products/' + str(products.id))

def home(request):
    all_products = Product.objects
    return render(request,'product/home.html', {"all_products":all_products})

def detail(request,product_id):
    products = get_object_or_404(Product,pk=product_id)
    return render(request,'product/detail.html', {'products':products})

@login_required
def create(request):
    if request.method == "POST":
        if request.POST["product-title"] and request.POST["product-desc"] and request.POST["URL"] and request.FILES["product-icon"] and request.FILES["product-image"]:
            product = Product()
            product.product_title = request.POST["product-title"]
            product.product_desc = request.POST["product-desc"]
            product.product_icon = request.FILES["product-icon"]
            product.product_image = request.FILES["product-image"]
            product.product_date = timezone.datetime.now()
            product.hunter = request.user
            if request.POST["URL"].startswith ("https://") or request.POST["URL"].startswith("http://"):
                product.product_url = request.POST["URL"]
            else:
                product.product_url = "http://" + request.POST["URL"]

            product.save()
            return redirect('/products/' + str(product.id))
        else:
            return render(request, 'product/create.html', {"errorP":"Please fill in all the fields"})
    else:
        return render(request, 'product/create.html')


import logging
import sys
import asyncio
import traceback
from backend.classes.munchlax import Munchlax
import backend.pokedecoder as pokedecoder

class Bizhawk:
    def __init__(self, host, port, bh):
        super().__init__()
        self.host = host
        self.port = port
        self.bizhawks = {}
        self.bizhawks_status = {}
        self.server = None
        self.is_connected = False
        self.disconnect_lock = asyncio.Lock()
        self.bh = bh

        self.logger = self.init_logging()

    def init_logging(self):
        logger = logging.getLogger(__name__)
        logger.setLevel(logging.INFO)

        logging_formatter = logging.Formatter('[%(asctime)s] %(levelname)s: %(message)s')

        file_handler = logging.FileHandler('./logs/bizhawk.log', 'w')
        file_handler.setFormatter(logging_formatter)
        logger.addHandler(file_handler)

        stream_handler = logging.StreamHandler(sys.stdout)
        stream_handler.setFormatter(logging_formatter)
        logger.addHandler(stream_handler)

        return logger

    async def handle_bizhawk(self, reader, writer):
        client_id = None
        try:
            id_length = int((await reader.read(2)).decode())
            client_id = (await reader.read(id_length)).decode()
            self.bizhawks[client_id] = writer
            self.bizhawks_status[client_id] = 'connected'
            self.logger.info(f"Emulator {client_id} connected.")

            def get_length():
                if edition > 10 and edition < 20:
                    return 331
                elif edition < 30:
                    return 362
                elif edition < 40:
                    return 601
                else:
                    return 1418

            def update_teams(team):
                team = pokedecoder.team(team, edition)
                teams = self.munchlax.bizhawk_teams
                if player in teams:
                    if teams[player] == team:
                        return
                teams[player] = team
                self.munchlax.unsorted_teams[player] = team

            edition_length = int((await reader.read(2)).decode())
            edition = int((await reader.read(edition_length)).decode())

            player = int(client_id[7:])

            length = get_length()
            msg = await reader.read(length)
            update_teams(msg)
            while True:
                try:
                    msg = await reader.read(length)
                    update_teams(msg)
                except Exception as err:
                    self.logger.error(f"handle_bizhawk abgebrochen: {type(err)},{err}")
                    self.logger.error(f"{traceback.format_exc()}")
                    break
        except Exception as err:
            self.logger.error(f"handle_bizhawk abgebrochen: {type(err)},{err}")
            self.logger.error(f"{traceback.format_exc()}")

        await self.disconnect(client_id)

    async def disconnect(self, client_id):
        if client_id in self.bizhawks:
            self.bizhawks[client_id] = None
            self.bizhawks_status[client_id] = False

    async def start(self, munchlax):
        self.server = await asyncio.start_server(
            self.handle_bizhawk, self.host, self.port)

        self.munchlax: Munchlax = munchlax

        self.is_connected = True
        async with self.server:
            await self.server.serve_forever()

    async def stop(self):
        if self.server:
            self.server.close()
            await self.server.wait_closed()

            self.server = None
            self.is_connected = False

            self.port = self.bh['port']
            self.logger.info("Bizhawk has been stopped.")

    async def stop_and_terminate(self, bizhawk_instances):
        await self.stop()

        for bizhawk in bizhawk_instances:
            bizhawk.terminate()


from action_block import ActionBlockParameterizer

from PyQt5.QtWidgets import QLineEdit, QComboBox, QCheckBox

#Move absolute need MoveGroup + Pose (no target frame!)
class MoveToFrameParameterizer(ActionBlockParameterizer):
    def __init__(self, parent=None, action_name=None):
        super().__init__(parent, action_name)
        self.setWindowTitle("Parameterize 'Move To Frame'-Actionblock")

        self.create_parameterizer()

    def create_parameterizer(self):
        self.move_group_combo = QComboBox()
        self.move_group_combo.addItems(["Tool", "Cam1", "Laser"])
        self.target_frame_combo = QComboBox()
        self.target_frame_combo.addItems(["Home", "Drop_Position_Gonio_Left", "Camera_Station_TCP", "Gonio_Left_Part_Origin", "Gonio_Right_Part_Origin", "Test_Station", "Station_Laser_Bottom_TCP", "Grasping_Position"])
        # there are no frames for: "Tool_TCP", "Laser"
        # a bad one for: "Gonio_Left_Part_Origin"
        self.offset_edit = QLineEdit()
        self.offset_edit.setText("0.0, 0.0, 0.0")
        self.rotation_edit = QLineEdit()
        self.rotation_edit.setText("0.0, 0.0, 0.0, 1.0")

        self.wait_input_checkbox = QCheckBox("Wait for User Input")

        self.form_layout.addRow("MoveGroup:", self.move_group_combo)
        self.form_layout.addRow("TargetFrame:", self.target_frame_combo)
        self.form_layout.addRow("Offset (x, y, z):", self.offset_edit)
        self.form_layout.addRow("Rotation (x, y, z, w):", self.rotation_edit)
        self.form_layout.addRow(self.wait_input_checkbox)

    def save_parameters(self):
        parameters = {
            "MoveGroup": self.move_group_combo.currentText(),
            "TargetFrame": self.target_frame_combo.currentText(),
            "Offset (x, y, z)": self.offset_edit.text(),
            "Rotation (x, y, z, w)": self.rotation_edit.text(),
            "Wait for User Input": self.wait_input_checkbox.isChecked()
        }

        # Store the parameters in a dictionary for the action block
        action_block_data = {
            "action_class": "MoveToFrame",
            "parameters": parameters
        }

        # Add the action block data to the parent's saved_action_blocks dictionary
        self.parent().saved_action_blocks[self.name_edit.text()] = action_block_data

        self.accept()

        return parameters


import ast
import csv
import dateutil
import glob
import os
import shutil
from datetime import datetime
from munch import munchify
from pinsey.Constants import USER_DATA_DIR
from pinsey.thread.DownloadPhotosThread import DownloadPhotosThread

class LikesHandler:
    """
        Handles likes/dislikes and writes the history to a .csv file.
    """
    def __init__(self):
        self.likes_filepath = USER_DATA_DIR + 'likes.csv'
        self.dislikes_filepath = USER_DATA_DIR + 'dislikes.csv'
        self.photo_storage_path = os.path.join(USER_DATA_DIR + 'images', '')
        self.fieldnames = ['id', 'name', 'gender', 'age', 'birth_date', 'bio', 'jobs', 'schools', 'distance_km',
                           'common_connections', 'common_likes', 'date_added', 'added_by']

        # Every time the CSV files are read, the Munchy user object is stored in this history dictionary.
        self.history = {self.likes_filepath: [], self.dislikes_filepath: []}

    def like_user(self, user, owner='User'):
        user.like()
        self._write(self.likes_filepath, user, owner)

    def dislike_user(self, user, owner='User'):
        user.dislike()
        self._write(self.dislikes_filepath, user, owner)

    def superlike_user(self, user, owner='User'):
        user.superlike()
        self._write(self.likes_filepath, user, owner)

    def get_likes(self):
        return self._read(self.likes_filepath)

    def get_dislikes(self):
        return self._read(self.dislikes_filepath)

    def delete_history(self, user):
        found = None
        filepath = self.likes_filepath

        for record in self.history[self.likes_filepath]:
            if record['id'] == user.id:
                found = record
                break

        if not found:
            filepath = self.dislikes_filepath
            for record in self.history[self.dislikes_filepath]:
                if record['id'] == user.id:
                    found = record
                    break

        if found:
            self.history[filepath].remove(found)
            # Remove images for this user.
            shutil.rmtree(self.photo_storage_path + user.name + '_' + user.id, ignore_errors=True)
            self._rewrite(filepath)

    def _rewrite(self, filepath):
        """
            Rewrite the whole CSV file from the likes/dislikes history dictionary.

            :param filepath: Location of the CSV that stores the likes or dislikes data.
            :return: None
        """
        with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
            writer = csv.DictWriter(csvfile, fieldnames=self.fieldnames, extrasaction='ignore')
            writer.writeheader()
            for user in self.history[filepath]:
                writer.writerow(user)

    def _read(self, filepath):
        """
            Reads the entire like/dislike CSV history file and returns a list of user objects.

            :param filepath: Location of the CSV file storing the like/dislike history data.
            :return: List of Munch objects (from dict) containing user data.
        """
        user_list = []
        self.history[filepath] = []

        if os.path.exists(filepath):
            with open(filepath, 'r', newline='', encoding='utf-8') as csvfile:
                reader = csv.DictReader(csvfile)
                for row in reader:
                    self.history[filepath].append(row)
                    user = munchify(row)
                    user.age = int(user.age)
                    user.birth_date = dateutil.parser.parse(user.birth_date)
                    user.date_added = dateutil.parser.parse(user.date_added)
                    user.distance_km = float(user.distance_km)
                    user.jobs = ast.literal_eval(user.jobs)
                    user.photos = glob.glob(self.photo_storage_path + user.name + '_' + user.id + '/*.jpg')
                    user.schools = ast.literal_eval(user.schools)
                    user.common_connections = ast.literal_eval(user.common_connections)
                    user.common_likes = ast.literal_eval(user.common_likes)

                    try:
                        user.thumb_data = open(user.photos[0], 'rb').read()
                    except IndexError:
                        # Happens if there are no images stored.
                        user.thumb_data = None

                    for i in range(len(user.photos)):
                        user.photos[i] = 'file:' + user.photos[i]
                    user_list.append(user)

        return user_list

    def _write(self, filepath, user, owner):
        """
            Appends a new user into the like/dislike CSV history data and saves their photos locally.

            :param filepath: Location of the like/dislike CSV history file.
            :param user: User to be stored.
            :return: None
        """
        def save_photos(data):
            photos = []  # Store a list of photo directory paths for this user.
            for index, photo in enumerate(data):
                photo_path = os.path.join(self.photo_storage_path + user.name + '_' + user.id, str(index) + '.jpg')
                dirname = os.path.dirname(photo_path)
                if not os.path.exists(dirname):
                    os.makedirs(dirname)

                with open(photo_path, 'wb') as jpgfile:
                    jpgfile.write(photo.data)
                photos.append(photo_path)

            file_exists = os.path.exists(filepath)
            with open(filepath, 'a', newline='', encoding='utf-8') as csvfile:
                writer = csv.DictWriter(csvfile, fieldnames=self.fieldnames, extrasaction='ignore')
                if not file_exists:
                    writer.writeheader()

                user_dict = user.__dict__
                user_dict['photos'] = photos  # Override the 'photos' value with a list.
                user_dict['age'] = user.age
                user_dict['distance_km'] = user.distance_km
                user_dict['gender'] = user.gender
                user_dict['common_connections'] = user.common_connections
                user_dict['common_likes'] = user.common_likes
                user_dict['date_added'] = datetime.now()
                user_dict['added_by'] = owner
                writer.writerow(user_dict)

        # Not starting a concurrent thread, instead just using the thread's function to download photos will do.
        download_photos = DownloadPhotosThread(user.photos).download()
        save_photos(download_photos)


from functools import partial

from starlite import Starlite, State, ValidationException

from hackathon.api.urls import api_router
from hackathon.config.settings import get_settings
from hackathon.lib import compression, exceptions, logging, openapi, response, static_files

from .containers import Container, override_providers
from .dependencies import create_project_dependencies

settings = get_settings()

async def on_startup(state: State, *_, container: Container, **__) -> None:
    """Startup hook."""
    await container.init_resources()
    container.check_dependencies()
    state.container = container

async def on_shutdown(state: State) -> None:
    """Shutdown hook."""
    await state.container.shutdown_resources()

def create_app() -> Starlite:
    """Starlite app factory."""
    container = Container()
    container.config.from_pydantic(settings=settings)
    container = override_providers(container)

    dependencies = create_project_dependencies()
    app = Starlite(
        after_exception=[exceptions.after_exception_hook_handler],
        compression_config=compression.config,
        debug=settings.app.DEBUG,
        dependencies=dependencies,
        exception_handlers={
            exceptions.HackathonAPIError: exceptions.project_api_exception_to_http_response,
            ValidationException: exceptions.starlite_validation_exception_to_http_response,
            Exception: exceptions.server_exception_to_http_response,
        },
        logging_config=logging.config,
        openapi_config=openapi.config,
        response_class=response.Response,
        route_handlers=[api_router],
        on_shutdown=[on_shutdown],
        on_startup=[partial(on_startup, container=container)],
        static_files_config=static_files.config,
    )
    app.state.container = container  # XXX: have to manually specify `container` for unit tests
    return app


import socket
from json import dumps
from encrypt import writeToJSONFile
from chunks import recv_timeout

HOST = '127.0.0.1'  # Standard loopback interface address (localhost)
PORT = 65432        # Port to listen on (non-privileged ports are > 1023)

m = {"id": 2, "name": "abc"} # a real dict
json = dumps(m)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print('Connected by', addr)
        while True:
            data = conn.recv(8192)
            if not data:
                break
            #conn.sendall(data)
            conn.sendall(bytes(writeToJSONFile(),encoding="utf-8"))
print("finishd")


from django.urls import path
from computer.views import ComputerList,ComputerCreate,ComputerDetail,ComputerDelete,ComputerInfoUpdate,Thanks,ComputerSpecificationView,ComputerBrandView,ComputerSpecification_Opt,upload_image
urlpatterns = [
    path('',ComputerList.as_view(),name='computerlist'),
    path('createlist/',ComputerCreate.as_view(),name='computercreate'),
    path('computerdetail/<int:id>',ComputerDetail.as_view(),name='computerdetail'),
    path('computerdelete/<int:id>', ComputerDelete.as_view(), name="computerdelete"),
    path('computerinfoupdate/<int:id>',ComputerInfoUpdate.as_view(),name='computerupdate'),
    path('thanks/',Thanks.as_view(),name='thanks'),
    path('computerspecificationform/',ComputerSpecificationView.as_view(),name="computerspecificationform"),
    path('computerbrandform/',ComputerBrandView.as_view(),name='computerbrandform'),
    path('computerspecificationopt/',ComputerSpecification_Opt.as_view(),name='ComputerSpecification_Opt'),
    path('img/',upload_image.as_view(),name='images'),
    # path('image',image.as_view(),name='image'),
]


import os
import sys

os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
sys.path.append('G:\Work\python\JTDI\mysite\settings.py')

import datetime

from django.conf import settings
from django.contrib.auth.models import User
from django.core.mail import send_mail
from django.db.models import Q
from django.shortcuts import redirect

from JtdiTASKS.models import Task

def send_digest():
    users = User.objects.filter(email__exact='')
    for user in users:
        currentdate = datetime.datetime.today()
        start_day = currentdate.combine(currentdate, currentdate.min.time())
        end_day = currentdate.combine(currentdate, currentdate.max.time())
        first_day = datetime.date(1001, 1, 1)

        tasks_overdue = Task.objects.filter(active=True).filter(Q(author=user) | Q(performer=user)) \
            .filter(date_time__range=(first_day, start_day)) \
            .order_by('date', 'priority', 'time')

        tasks_today = Task.objects.filter(active=True).filter(Q(author=user) | Q(performer=user)) \
            .filter(date_time__range=(start_day, end_day)) \
            .order_by('date', 'priority', 'time')
        body_mail = '
 Задачи на сегодня
'
        for task in tasks_today:
            body_mail = body_mail + '\n' + task.title + ''

        body_mail = body_mail + '
 Просроченые задачи
'
        for task in tasks_overdue:
            body_mail = body_mail + '\n' + task.title + ''

        send_mail('Дайджест задач', body_mail, settings.EMAIL_HOST_USER, [user.email])

send_digest()