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())


from time import sleep
from bs4 import BeautifulSoup
import requests
import pandas as pd
from pprint import pprint

# url = 'https://www.python.org/'
# url = 'https://tech-diary.net/python-scraping-books/'
url = 'https://suumo.jp/chintai/tokyo/sc_shinjuku/?page={}'
d_list = []

for i in range(1, 3):
    print('d_listの大きさ：', len(d_list))
    target_url = url.format(i)

# target_url = url.format(1)
# r = requests.get(url)
    r = requests.get(target_url)
    soup = BeautifulSoup(r.text)

    sleep(1)
    contents = soup.find_all('div', class_='cassetteitem')
    # print(len(contents))
    # content = contents[0]
    # print(content)
    for content in contents:
        detail = content.find('div', class_='cassetteitem-detail')
        table = content.find('table', class_='cassetteitem_other')
        title = detail.find('div', class_='cassetteitem_content-title').text
        address = detail.find('li', class_='cassetteitem_detail-col1').text
        access = detail.find('li', class_='cassetteitem_detail-col2').text
        age = detail.find('li', class_='cassetteitem_detail-col3').text

        # print(title, address, access, age)

        tr_tags = table.find_all('tr', class_='js-cassette_link')
        for tr_tag in tr_tags:
            floor, price, first_fee, capacity = tr_tag.find_all('td')[2:6]
            fee, management_fee = price.find_all('li')
            deposite, gratuity = first_fee.find_all('li')
            madori, menseki = capacity.find_all('li')
            # print(td_tag)
            d = {
                'title': title,
                'address': address,
                'access': access,
                'age': age,
                'floor': floor.text,
                'fee': fee.text,
                'management_fee': management_fee.text,
                'deposite': deposite.text,
                'gratuity': gratuity.text,
                'madori': madori.text,
                'menseki': menseki.text
            }
            d_list.append(d)

    # pprint(d_list[1])
# pprint(d_list[:2])

df = pd.DataFrame(d_list)
print(df.head())
print(df.shape)

print(len(df.title.unique()))

df.to_csv('test.csv', index=None, encoding='utf-8-sig')

# print(d)

# print(r.url)
# print(r.text)

# soup = BeautifulSoup(r.text)
# print(soup.find('h1').text)
# print(soup.find_all('h2')[0].text)

# h2_tag_list = []
# for i, h2_tag in enumerate(soup.find_all("h2")):
    # h2_tag_list.append(h2_tag.text)

# print(h2_tag_list)

# h2_tag_list2= [i.text for i in soup.find_all("h2")]
# print(h2_tag_list2)

# print(len(soup.find_all('span')))
# print(soup.find_all('span', class_=['say-no-more', 'message']))

# tag_list = [i.text for i in soup.find('article').find_all(['h2', 'h3'])]
# for i in tag_list:
    # print(i)


import webapp2
from models.snippet import Snippet
from models.user import User
from models.usersnippet import Rate
from webapp2_extras import jinja2, sessions
from handlers.register import RegisterHandler
from handlers.base import BaseHandler
from handlers.login import LoginHandler, LogoutHandler
from handlers.snippets \
    import CreateSnippetHandler, MySnippetsHandler, DeleteSnippetHandler, EditSnippetHandler,RateSnippetHandler

class MainHandler(BaseHandler):
    def get(self):
        snippets = Snippet.query().order(-Snippet.creation_date).fetch()
        session = self.session.get('user')
        is_logged = False
        logged_user = None
        if session is not None:
            is_logged = True
            logged_user = User.query(User.username == self.session.get('user')).get()

        for snippet in snippets:
            user = User.query(User.key == snippet.user).get()
            snippet.completeuser = user
            if logged_user:
                rate = Rate.query(Rate.snippet == snippet.key, Rate.user == logged_user.key).get()
                snippet.user_valoration = rate.positive_valoration if rate else None

        data = {
            "is_logged": is_logged,
            "snippets": snippets
        }

        jinja = jinja2.get_jinja2(app=self.app)
        self.response.write(jinja.render_template("index.html", **data))

config = {}
config['webapp2_extras.sessions'] = {
    'secret_key': 'prepre',
}

app = webapp2.WSGIApplication([
    ('/', MainHandler),
    ('/register', RegisterHandler),
    ('/logout', LogoutHandler),
    ('/login', LoginHandler),
    ('/newsnippet', CreateSnippetHandler),
    ('/mysnippets', MySnippetsHandler),
    ('/mysnippets/delete', DeleteSnippetHandler),
    ('/editsnippet', EditSnippetHandler),
    ('/rate', RateSnippetHandler)
], debug=True, config=config)


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import telebot
import telebot.types as types

import re
import cash
import botan
import config
import chisla_fun

#import configTest as config
bot = telebot.TeleBot(config.token)

# Инлайн-режим с непустым запросом
@bot.inline_handler(lambda query: len(query.query) > 0)
def query_text(query):
    #botan.track(config.botan_key, query.from_user.id, {}, 'inline запрос')
    kb = types.InlineKeyboardMarkup()
    # Добавляем колбэк-кнопку с содержимым "test"
    kb.add(types.InlineKeyboardButton(text="Нажми меня", callback_data="test"))
    results = []
    single_msg = types.InlineQueryResultArticle(
        id="1", title="Press me",
        input_message_content=types.InputTextMessageContent(message_text="Решил проверить, что я умею?)"),
        reply_markup=kb
    )
    results.append(single_msg)
    bot.answer_inline_query(query.id, results)

#Если сообщение на которое тыкнули из инлайн-режима
@bot.callback_query_handler(lambda call: call.inline_message_id)
def callback_inline(call):
    if call.inline_message_id:
        if call.data == "test":
            bot.edit_message_text(inline_message_id=call.inline_message_id, text="Бдыщь")

# Если сообщение из чата с ботом
@bot.callback_query_handler(lambda call: call.message)
def callback_inline(call):
    if call.data == "test":
        bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text=call.message.text)
        text = 'Добро пожаловать!\nВыберите интересующий вас раздел:'
        keyboardStart = telebot.types.InlineKeyboardMarkup()
        callback_button0 = telebot.types.InlineKeyboardButton(text="Клиент", callback_data="cl")
        callback_button1 = telebot.types.InlineKeyboardButton(text="Гость", callback_data="g")
        keyboardStart.add(callback_button0,callback_button1)
        bot.send_message(call.message.chat.id, text,reply_markup=keyboardStart)
    if call.data == "cl":
        text = "Для записи на консультацию *позвоните* по номеру:\n\n[+79165127377](tel:+79165127377)"
        # bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text=call.message.text)
        bot.send_message(call.message.chat.id, text, parse_mode='Markdown')
    if call.data == "g":
        text = "Для посчёта вашего числа введите полностью *ФИО* или *дату рождения*.\nДата рождения в формате:\n*ГГГГ ММ ДД* или *ДД ММ ГГГГ*"
        # bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text=call.message.text)
        bot.send_message(call.message.chat.id, text, parse_mode='Markdown')
    if call.data == "sll":
        keyboard = telebot.types.InlineKeyboardMarkup()
        for text in cash.keyb["students"]:
            callback_button = telebot.types.InlineKeyboardButton(text=text, callback_data=cash.keyb["students"][text])
            keyboard.add(callback_button)
        bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text=call.message.text)
        bot.send_message(call.message.chat.id, text='Вы Выбрали раздел Студент.', reply_markup=keyboard)

@bot.message_handler(commands=['start'])
def send_welcome(message):
    botan.track(config.botan_key, message.chat.id, message, '/start')
    text = 'Добро пожаловать!\nЯ помогу тебе разобраться с числами!\nВыберите интересующий вас раздел:'
    keyboardStart = telebot.types.InlineKeyboardMarkup()
    callback_button0 = telebot.types.InlineKeyboardButton(text="Посчитать моё число", callback_data="g")
    callback_button1 = telebot.types.InlineKeyboardButton(text="Записаться на консультацию", callback_data="cl")
    keyboardStart.add(callback_button0)
    keyboardStart.add(callback_button1)
    bot.send_message(message.chat.id, text, reply_markup=keyboardStart)

@bot.message_handler(commands=['help'])
def send_help(message):
    botan.track(config.botan_key, message.chat.id, message, '/help')
    text = 'Помошь:\n/help - это сообещние\n/about - о боте\n\nДля содсчёт "собственного числа имени" введите ФИО полностью.\nДля подсчёта "собственного числа даты вождения" введите дату рождения в формате ДД ММ ГГГГ или ГГГГ ММ ДД.'
    bot.send_message(message.chat.id, text)

@bot.message_handler(commands=['about'])
def send_about(message):
    botan.track(config.botan_key, message.chat.id, message, '/about')
    text = 'Бот предназначен для помощи в посчёте "своих чисел".\nОбратная связь/разработчик: @pa_antya'
    bot.send_message(message.chat.id, text)

@bot.message_handler(commands=['test'])
def send_help(message):
    #botan.track(config.botan_key, message.chat.id, message, '/test')
    text = "Бот работает"
    keyboardTest = telebot.types.InlineKeyboardMarkup()
    callback_button_test = telebot.types.InlineKeyboardButton(text="ok!", callback_data="ok")
    keyboardTest.add(callback_button_test)
    bot.send_message(message.chat.id, text, reply_markup=keyboardTest)

# Обычный режим
@bot.message_handler(content_types=["text"])
def any_msg(message):
    print(message.text)
    pdate = re.compile(r'(\d{1,4})[\-_:;\\\|\/ ,.]{0,}(\d{1,2})[\-_:;\\\|\/ ,.]{0,}(\d{1,4})')
    pfio = re.compile(r'([a-zA-ZА-Яа-яёЁ\-\s]{2,})')

    mfio = pfio.match(message.text)
    mdate = pdate.match(message.text)

    #print(mfio)
    #print(mdate)
    if (mfio or mdate):
        if mdate:
            #print('Match found: "{}"'.format(mdate.group()))
            chislo = chisla_fun.date(message)
        if mfio:
            #print('Match found: "{}"'.format(mfio.group()))
            chislo = chisla_fun.fio(message)
        text = 'Вы ввели: *{}*\nВыше число: *{}*'.format(message.text, chislo)
        bot.send_message(message.chat.id, text, parse_mode='Markdown')
    else:
        text = 'Введены *не верные* данные.\nПерепроверьте, пожалуйста:\n{}\n*Если* данные *верные* - свяжитесь с разработчиком в раздете /about.'.format(message.text)
        bot.send_message(message.chat.id, text,parse_mode='Markdown')

    text = 'Выберите интересующий вас раздел:'
'''keyboardText = telebot.types.InlineKeyboardMarkup()
    callback_button0 = telebot.types.InlineKeyboardButton(text="Клиент", callback_data="cl")
    callback_button1 = telebot.types.InlineKeyboardButton(text="Гость", callback_data="g")
    keyboardText.add(callback_button0, callback_button1)
    bot.send_message(message.chat.id, text, reply_markup=keyboardText)
'''
if __name__ == '__main__':
    try:
        bot.polling(none_stop=True)
    except Exception:
        print('error Exception')

#YandexMetrica.setCustomAppVersion("1.13.2");


import unittest
from spell_checker import SpellChecker
from tests.trie_stub import TrieStub

class TestSpellChecker(unittest.TestCase):
    def setUp(self):
        self.trie=TrieStub().trie

    def create_checker(self, data):
        checker=SpellChecker(data)
        checker._dictionary=self.trie
        checker._incorrect_words={}
        checker._get_incorrect_words()
        return checker

    def test_give_corrections(self):
        misspelled_data="cat e pars tins"
        checker=self.create_checker(misspelled_data)
        suggestions=[s for i, s in checker.get_suggestions().items()]
        self.assertEqual(suggestions, ["rat", "a", "a", "in"])

    def test_get_word(self):
        data="one two three"
        checker=self.create_checker(data)
        result=checker.get_word_at((1, 3))
        self.assertEqual(result, "one")
        result=checker.get_word_at((5, 3))
        self.assertEqual(result, "two")
        result=checker.get_word_at((9, 5))
        self.assertEqual(result, "three")

    def test_get_text_after_correcting(self):
        misspelled_data="cat"
        checker=self.create_checker(misspelled_data)
        suggestion=[(i,s) for i,s in checker.get_suggestions().items()][0]
        checker.correct(suggestion[0], suggestion[1])
        self.assertEqual("rat", checker.get_text())

    def test_word_stays_capitalized(self):
        misspelled_data="Cat"
        checker=self.create_checker(misspelled_data)
        suggestion=[s for i,s in checker.get_suggestions().items()][0]
        self.assertEqual("Rat", suggestion)

    def test_input_with_no_letters(self):
        data="123!"
        checker=self.create_checker(data)
        self.assertEqual({}, checker._incorrect_words)


# https://school.programmers.co.kr/learn/courses/30/lessons/161990

def solution(wallpaper):
    # (row_min, col_min) (row_max, col_max)

    answer = []

    n = len(wallpaper)
    m = len(wallpaper[0])

    row_min = n
    col_min = m
    row_max = 0
    col_max = 0

    for i in range(n):
        for j in range(m):
            if wallpaper[i][j] == '#':
                row_min = min(i,row_min)
                col_min = min(j,col_min)
                row_max = max(i,row_max)
                col_max = max(j,col_max)

    answer =[row_min,col_min,row_max+1,col_max+1]
    return answer


from __future__ import annotations

import torch.nn as nn
from typing import NamedTuple, List

class LightweightNode(NamedTuple):
    """Attach a symbolic name to an ``nn.Module``."""

    name:   str
    module: nn.Module

    @property
    def qualified_path(self) -> List[str]:
        return self.name.split('.')

    def __eq__(self, other: LightweightNode) -> bool:
        cond_type = isinstance(other, LightweightNode)
        cond_name = self.name == other.name
        cond_obj  = self.module is other.module
        return cond_type and cond_name and cond_obj

class LightweightNodeList(list):  # https://stackoverflow.com/a/24160909
    """A list sub-class accepting only ``LightweightNode`` items."""

    def append(self, item):
        if not isinstance(item, LightweightNode):
            raise TypeError
        super(LightweightNodeList, self).append(item)

    def insert(self, index, item):
        if not isinstance(item, LightweightNode):
            raise TypeError
        super(LightweightNodeList, self).insert(index, item)

    def __add__(self, item):
        if not isinstance(item, LightweightNode):
            raise TypeError
        super(LightweightNodeList, self).__add__(item)

    def __iadd__(self, item):
        if not isinstance(item, LightweightNode):
            raise TypeError
        super(LightweightNodeList, self).__iadd__(item)

    def __str__(self) -> str:
        """When printed, this string shows a nicely formatted list of named
        ``nn.Module``s.

        This functionality is useful, for instance, when users want to
        visually inspect a floating-point ``nn.Module`` to decide how to
        quantise it.

        This method overwrites the default behaviour of Python lists'
        ``__str__`` dunder method, which resolves to calling the ``__repr__``
        method of each item in the list (https://stackoverflow.com/a/727779).

        """

        # overwrite the default behaviour of Python lists' `__str__` method
        max_chars = max(map(lambda node: len(node.name), self))

        str_ = f""
        for node in self:
            str_ += f"\n"
            name = ".".join(reversed(node.qualified_path)).rjust(max_chars)
            str_ += f"{name}\t{node.module}"

        return str_

    def show(self) -> None:
        print(self.__str__())


"""
An example of using Python's built in `multiprocessing` or `discomp`
to test many hyperparameters.

To test 30 hyperparameter configurations on my MacBook Pro it took:
* 81 minutes and 30 seconds using `map`
* 34 minutes and 40 seconds using `multiprocessing.Pool().map`
* 8 minutes and 52 seconds using `discomp.Pool().map`

More background at https://dis.co/blog/machine-learning-with-disco/
"""

import os
import random
import multiprocessing

import numpy as np
import xgboost
from sklearn.model_selection import train_test_split
from sklearn import metrics

import pandas as pd
import discomp

os.environ["DISCO_LOGIN_USER"] = "your-email-or-use-environment-variables"
os.environ["DISCO_LOGIN_PASSWORD"] = "your-password-or-api-key"

print("loading data")
url = "https://discodemo.s3-us-west-2.amazonaws.com/yuval/creditcard.csv"
df = pd.read_csv(url, header=0)

def test_fit(params):
    # This function will be launched multiple times in other processes
    Y = df["Class"]
    X = df.drop(["Class"], axis=1)
    print("Dataset X shape: {}".format(X.shape))
    print("Dataset Y shape: {}".format(Y.shape))
    print("{}\n\n{}".format(X[:2], Y[:2]))

    # split data into train and test sets
    seed = 7
    test_size = 0.33
    X_train, X_test, y_train, y_test = train_test_split(
        X, Y, test_size=test_size, random_state=seed
    )

    print("fit model")
    model = xgboost.XGBRegressor(objective="reg:squarederror", **params)
    model.fit(X_train, y_train)

    # make predictions for test data
    print("predict")
    y_pred = model.predict(X_test)

    print("evaluating predictions")
    fpr, tpr, thresholds = metrics.roc_curve(y_test, y_pred)
    roc_auc = metrics.auc(fpr, tpr)

    print(f"roc_auc: {roc_auc:.2} params: {params}")
    return roc_auc

# Set up a random search through hyper parameters
param_options = []
param_test_count = 30
for i in range(param_test_count):
    params = dict(
        max_depth=random.randint(2, 100),
        num_boost_round=random.randint(2, 1000),
        learning_rate=random.uniform(0.0001, 1.0),
        gamma=random.randint(0, 20),
        subsample=random.uniform(0.00001, 1.0),
    )
    param_options.append(params)

print("distributing tests")
# For purely local `multiprocessing`, replace the `discomp` line with the following.
# with multiprocessing.Pool() as pool:
with discomp.Pool() as pool:
    # `discomp.Pool().map` will send the `test_fit` to multiple machines to run in parallel.
    # The amount of machines depends on the number of items in the `param_options` list.
    results = pool.map(test_fit, param_options)

    # For a serial example, replace the above two lines with the following.
    # results = list(map(test_fit, param_options))
    best_index = np.argmax(results)
    print(f"Ran {param_test_count} tests")
    print(f"Best result {results[best_index]:0.2f}")
    print(f"Best params: {param_options[best_index]}")


import json
import os
from git import Repo
import sys
from github import Github

def get_values_json(payload):
    quotes_payload = json.loads(payload)
    #Extract branch of main repo
    main_branch = quotes_payload['pull_request']
    main_branch = main_branch['base']
    main_branch = main_branch['ref']
    #Extract branch of forked repo
    head_branch = quotes_payload['pull_request']
    head_branch = head_branch['head']
    head_branch = head_branch['ref']
    #Extract name of main repo
    main_repo = quotes_payload['pull_request']
    main_repo = main_repo['base']
    main_repo = main_repo['repo']
    main_repo = main_repo['full_name']
    #Extract name of forked repo
    head_repo = quotes_payload['pull_request']
    head_repo = head_repo['head']
    head_repo = head_repo['repo']
    head_repo = head_repo['full_name']
    #Extract pull request number
    pull_number = quotes_payload['number']

    return main_repo, main_branch, head_repo, head_branch, pull_number

def repair_file_path(file_path : str) -> str:
    ret = file_path
    # Remove unwanted prefix
    if len(ret) >= 1 and ret[0] == "/":
        ret = ret[1:]
    if len(ret) == 1 and ret[0] == ".":
        ret = ret[1:]
    if len(ret) >= 2 and ret[0] == "." and ret[1] == "/":
        ret = ret[2:]
    # This one is probably redundant.
    if len(ret) >= 1 and ret[0] == "/":
        ret = ret[1:]
    # Remove unwanted quotes.
    ret = ret.replace("\"", "")
    ret = ret.replace("\'", "")
    return ret

def repair_folder_path(folder_path: str) -> str:
    ret = repair_file_path(folder_path)
    # Add desired postfix
    if len(ret) >= 1 and ret[-1] != "/":
        ret += "/"
    return ret

# Returns all file additions that were made within the base folder.
# The added files are returned as lists of the folders (and file name) constituting their paths.
# E.g. a file "fol1/fol2/fol3/file1.txt" is represented as ["fol1", "fol2", "fol3", "file1.txt"]
def extract_candidates(file_additions : "list of str", base_folder_segments : "list of str") -> "list of lists of str":
    ret = []
    for file_path in file_additions:
        path_segments = file_path.split("/")
        is_candidate = True
        for j, base_segment in enumerate(base_folder_segments):
            if(base_segment != path_segments[j]):
                is_candidate = False
                break
        if is_candidate:
            ret.append(path_segments)
    return ret

# Returns the files which are named "README.md" (capitalization doesn't matter)
def extract_readme(candidates : "list of lists of str") -> "list of lists of str":
    return [e for e in candidates if e[-1].lower() == "readme.md"]

# Given a string "name1-name2-...", returns a list of all strings that are seperated by a hyphen.
# The strings are also sorted so that e.g. "name1-name2" and "name2-name1" are considered equal.
def extract_and_sort_names(folder_name : str) -> "list of str":
    l = folder_name.split("-")
    l.sort()
    return l

# Returns true if the readme contains KTH mail addresses corresponding to the ID:s extracted from its parent folder name.
def readme_is_valid(id_list : "list of str", readme_path : str) -> bool:
    file_content = []
    with open(readme_path, 'r') as f:
        file_content = f.read()
    is_valid = True
    for kth_id in id_list:
        if kth_id + "@kth.se" not in file_content:
            is_valid = False
            break
    return is_valid

# Returns the possible sorted subgroups, including the empty group as the first entry. Thus [1:] will return groups of at least one element.
# Member inclusions can be set as list of ints. This will make it so that the index of the member inclusions list will indicate whether to include
# a particular member when creating subgroups or not. An int of at least 1 is required to include the member.
def subgroups_recursion(group_members: "list of str", index : int, member_inclusions=None) -> "list of lists of str":
    if index >= len(group_members):
        return [[]]
    subgroups = subgroups_recursion(group_members, index + 1, member_inclusions=member_inclusions)
    additions = []
    if member_inclusions == None or member_inclusions[len(group_members) - 1 - index] > 0:
        for l in subgroups:
            new_entry = l.copy()
            new_entry.append(group_members[len(group_members) - 1 - index])
            additions.append(new_entry)
    return subgroups + additions

# Returns a list of tuples with the number of collaborations among the subgroups of the group members, along with which group members are part of this.
# The subgroups considered are at least of size 2.
# The special case of a group with a single members does not count as a subset to anything else but itself.
def most_collaborations(base_folder_path : str, group_members : "list of str") -> "list of pairs (int, list of str)":
    # https://stackoverflow.com/questions/973473/getting-a-list-of-all-subdirectories-in-the-current-directory
    folder_lists = [x[1] for x in os.walk(base_folder_path) if x[1] != []]
    folders = [item for sublist in folder_lists for item in sublist]

    subgroupsR = subgroups_recursion(group_members, 0)
    subgroups = [sub for sub in subgroupsR if len(sub) > 1]

    subgroups_map = {}
    for sub in subgroups:
        subgroups_map["-".join(sub)] = 0

    if len(group_members) == 1:
        counter = 0
        for f in folders:
            if f == group_members[0]:
                counter += 1
        return [(counter, [group_members[0]])]

    for f in folders:
        name_inclusions = [0 for _ in group_members]
        names = extract_and_sort_names(f)
        for n1 in names:
            for i, n2 in enumerate(group_members):
                if n1 == n2:
                    name_inclusions[i] += 1
        keysR = subgroups_recursion(group_members, 0, member_inclusions=name_inclusions)
        keys = ["-".join(key) for key in keysR if len(key) > 1]
        for key in keys:
            subgroups_map[key] += 1

    ret_list = []
    for sub in subgroups:
        ret_list.append((subgroups_map["-".join(sub)], sub))
    return ret_list

def write_json_output(report : str, is_valid_pull_request : bool, pr_number : int, is_student_submission : bool, ids_match : bool, valid_group : bool) -> "no return":
    print(json.dumps({"report":report,
                      "valid_pr":("true" if is_valid_pull_request else "false"),
                      "pr_num":pr_number,
                      "student_submission":("true" if is_student_submission else "false"),
                      "ids_match":("true" if ids_match else "false"),
                      "valid_group":("true" if valid_group else "false")
                      }))
#Writes comment on the pul request with the final report
def write_comment_pr(git_token, repo_name, pull_number, report):
    g = Github(git_token)
    repo = g.get_repo(repo_name)
    pr = repo.get_pull(pull_number)
    pr.create_issue_comment(report)

# Expects six command line arguments in the following order:
# - a github access token
# - the pull request payload as a json object
# - paths of files that have been added on the form "[file1_path,file2_path,...]".
# - the path to the base folder, which is the folder that is to be considered root when running this script.
# - an int with the maximum group size,
# - and an int with the maximum number of times the same group is allowed to work together (this includes groups of only a single person).
#
# For all paths specified, they should not begin with a "./" but every other folder in the path should be followed by "/" .
# E.g. "fol/base_folder/" .
# Pointing to current directory would be just the empty string "".
# The array of file additions should not be an array of strings denoted by quotes, i.e NOT ["file1_path","file2_path","..."],
# but rather just a single string "[file1_path,file2_path,...]".
# However, even if the input deviates from this it's attempted to repair it.
def main() -> "no return":
    report = ""
    verdict = ""
    is_valid_pull_request = True
    pr_number = -1
    is_student_submission = False
    valid_readme = False
    valid_group = False

    access_token = sys.argv[1]
    folder_name = ""

    payload = sys.argv[2]
    try:
        base_repo, base_branch, head_repo, head_branch, pull_number = get_values_json(payload)
        pr_number = pull_number
    except:
        is_valid_pull_request = False
        write_json_output(report, is_valid_pull_request, pr_number, is_student_submission, valid_readme, valid_group)
        return

    # using an access token
    # https://<token>@github.com/owner/repo.git
    # 'http://user:password@github.com/user/project.git'
    head_path = "./head"
    os.mkdir(head_path)
    repo = Repo.clone_from("https://" + access_token + "@github.com/" + head_repo + ".git", head_path, branch=head_branch)
    file_additions = sys.argv[3][1:-1].split(",")
    for i, f in enumerate(file_additions):
        file_additions[i] = repair_file_path(f)

    base_folder = sys.argv[4]
    base_folder = repair_folder_path(base_folder)
    base_folder_segments = base_folder.split("/")[:-1]
    candidates = extract_candidates(file_additions, base_folder_segments)
    readme_list = extract_readme(candidates)

    if len(readme_list) != 1:
        report += "There wasn't exactly one readme added under \"" + base_folder + "\" . This is assumed not to be a student project proposal.\n"
        is_student_submission = False
    else:
        is_student_submission = True
        folder_name = readme_list[0][-2]
        id_list = extract_and_sort_names(readme_list[0][-2]) # There is only one readme, and the ID:s should be in the immediate parent folder name.
        is_valid = readme_is_valid(id_list, head_path + "/" + "/".join(readme_list[0]))
        if is_valid:
            report +=  "The ID:s constituting the folder name matched with the email addresses in the README file.\n"
            valid_readme = True
        else:
            report += "The ID:s constituting the folder name did not match with the email addresses in the README file. "
            report += "If this is a student project proposal, please revise the pull request.\n"
            valid_readme = False

    if not is_student_submission or not valid_readme:
        write_json_output(report, is_valid_pull_request, pull_number, is_student_submission, valid_readme, valid_group)
        write_comment_pr(access_token, base_repo, pr_number, report + verdict)
        return

    base_path = "./base"
    os.mkdir(base_path)
    Repo.clone_from("https://" + access_token + "@github.com/" + base_repo + ".git", base_path, branch=base_branch)

    group_members = extract_and_sort_names(folder_name)
    collaborations = most_collaborations(base_path + "/" + base_folder, group_members)
    allowed_group_size = int(sys.argv[5])
    allowed_collaboration_times = int(sys.argv[6])

    valid_group = True
    for num, members in collaborations:
        if num >= allowed_collaboration_times:
            verdict += "The group consisting of "
            for g in members:
                verdict += g + ", "
            verdict += "appears to have worked together " + str(num) + " times, while the maximum allowed is " + str(allowed_collaboration_times) + ". "
            verdict += "Consequently they may not work together here.\n"
            valid_group = False

        report += "The group consisting of "
        for g in members:
            report += g + ", "
        report += "appears to have worked together "  + str(num) + " times.\n"
    report += "Maximum group size allowed: " + str(allowed_group_size) + ".\n"
    report += "Maximum number of collaborations allowed: " + str(allowed_collaboration_times) + ".\n"

    if len(group_members) > allowed_group_size:
        verdict += "The group size is " + str(len(group_members))
        verdict += ", but the maximum allowed group size is " + str(allowed_group_size) + ". This group is thus not allowed.\n"
        valid_group = False

    if len(verdict) == 0:
        verdict += "The group composition is allowed.\n"
    write_json_output(report + verdict, is_valid_pull_request, pull_number, is_student_submission, valid_readme, valid_group)
    write_comment_pr(access_token, base_repo, pr_number, report + verdict)

if __name__ == "__main__":
    main()


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

from flask_script import Command, Option, Server

import flask
import requests

from whyis.data_extensions import DATA_EXTENSIONS

import rdflib
from whyis.nanopub import Nanopublication
import tempfile

from whyis.namespace import np
from whyis.blueprint.nanopub.nanopub_utils import load_nanopub_graph
import sys

class LoadNanopub(Command):
    '''Add a nanopublication to the knowledge graph.'''

    _TEMP_STORE_DEFAULT = None

    def get_options(self):
        return [
            Option('-i', '--input', dest='input_file', required=True, help='Path to file containing nanopub', type=str),
            Option('-f', '--format', dest='file_format', default='trig', help='File format (default: trig; also turtle, json-ld, xml, nquads, nt, rdfa)', type=str),
            Option('-r', '--revises', dest='was_revision_of', help="URI of nanopublication that this is a revision of", type=str),
            Option("--temp-store", dest="temp_store", type=str, default=self._TEMP_STORE_DEFAULT, help="backing store type to use for temporary graphs; deprecated")
        ]

    def run(self, input_file, file_format, temp_store=_TEMP_STORE_DEFAULT, was_revision_of=None):
        flask.current_app.managed = True
        if was_revision_of is not None:
            wasRevisionOf = set(flask.current_app.db.objects(predicate=np.hasAssertion,
                                                             subject=rdflib.URIRef(was_revision_of)))
            if len(wasRevisionOf) == 0:
                print("Could not find active nanopublication to revise:", was_revision_of)
                return
            was_revision_of = wasRevisionOf
        g = rdflib.ConjunctiveGraph(identifier=rdflib.BNode().skolemize())
        if temp_store == "Oxigraph":
            g_store_tempdir = tempfile.mkdtemp()
            g.store.open(g_store_tempdir, True)
        else:
            g_store_tempdir = None
        # g = rdflib.ConjunctiveGraph(identifier=rdflib.BNode().skolemize())

        try:
            g1 = load_nanopub_graph(location=input_file, format=file_format, store=g.store)

            nanopubs = []
            for npub in flask.current_app.nanopub_manager.prepare(g):
                if was_revision_of is not None:
                    for r in was_revision_of:
                        print("Marking as revision of", r)
                        npub.pubinfo.add((npub.assertion.identifier, flask.current_app.NS.prov.wasRevisionOf, r))
                print('Prepared', npub.identifier)
                nanopubs.append(npub)
            flask.current_app.nanopub_manager.publish(*nanopubs)
            print("Published", npub.identifier)
        finally:
            if g_store_tempdir is not None:
                shutil.rmtree(g_store_tempdir)


import glob2
import os
import cv2
import numpy as np
threshold = 35
input_dir = 'SCUT-EnsText/train_sets'
save_str = "stroke"

if not os.path.exists(os.path.join(input_dir, save_str)):
    os.mkdir(os.path.join(input_dir, save_str))
images = glob2.glob(os.path.join(input_dir, 'all_images', '*.jpg'))
print("#item: ", len(images))

for i, item in enumerate(images):
    label_img_path = os.path.join(input_dir, 'all_labels', os.path.basename(item))
    img = cv2.imread(item)
    label_img = cv2.imread(label_img_path)
    gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray_label_img = cv2.cvtColor(label_img, cv2.COLOR_BGR2GRAY)

    diff = np.abs(gray_img.astype(np.float32) - gray_label_img.astype(np.float32))
    diff_threshold = threshold
    diff[diff < diff_threshold] = 1
    diff[diff >= diff_threshold] = 0
    diff = (diff * 255).astype("uint8")
    cv2.imwrite(os.path.join(input_dir, save_str, os.path.basename(item)), diff)


#
# @lc app=leetcode id=856 lang=python3
#
# [856] Score of Parentheses
#

# @lc code=start
class Solution:
    def scoreOfParentheses(self, S: str) -> int:
        _sum = depth = 0
        for i, j in zip(S, S[1:]):
            _sum += 1 << depth if i + j == "()" else 0
            depth += 1 if i == "(" else -1

        return _sum
# @lc code=end


#!/usr/bin/env python
# coding: utf-8

# In[2]:

import pandas as pd
import matplotlib.pyplot as plt

# In[3]:

a = pd.read_excel('Sentiment_Analysis_1.xlsx')
data = pd.read_csv("disinformation_full_1.csv")
types=[]
for i in a['Sources of News']:
    b=i.split(',')
    types.append(len(b))
a['Number of News Sources']=types

# In[116]:

## STRONG RESULTS##

#a.groupby('Age')[['Frequency of sharing news']].mean().loc[['Less than 18 years','18 to 30 years','31 to 45 years','45 to 60 years', 'More than 60 years']].plot.barh()

#a.groupby('Age')['Verify authenticity of news'].mean().loc[['Less than 18 years','18 to 30 years','31 to 45 years','45 to 60 years', 'More than 60 years']].plot.barh()
#plt.legend(loc=(1.04,0))
#plt.show()

# In[4]:

print(data.columns)

# In[19]:

dark=[]
for i in (data['Money1 or 2']):
    if i =='B':
        dark.append(-1)
    elif i == 'A':
        dark.append(1)
    else:
        dark.append(0)
data['Image Formatted/Plain']=dark

# In[21]:

data.groupby('Age')[['Image Formatted/Plain']].mean().loc[['Less than 18 years','18 to 30 years','31 to 45 years','45 to 60 years', 'More than 60 years']].plot.barh()
plt.xlabel('Likelihood Scale')
plt.legend(loc=(1.04,0))
plt.show()

# In[139]:

bel=[]
fai=[]

for i in a.columns:
    if 'Faith' in i:
        fai.append(i)
    elif 'Believability' in i:
        bel.append(i)

# In[140]:

for i in range(len(bel)):
    (a.groupby('Age')[[bel[i],fai[i]]].mean().loc[['Less than 18 years','18 to 30 years','31 to 45 years','45 to 60 years', 'More than 60 years']].plot.barh())
    plt.xlabel('Likelihood Scale')
    plt.legend(loc=(1.04,0))
    plt.show()

# In[126]:

# In[128]:

for i in range(4):
    (a.groupby('Age')[[pm[i],who[i],doc[i]]].mean().loc[['Less than 18 years','18 to 30 years','31 to 45 years','45 to 60 years', 'More than 60 years']].plot.barh())
    plt.xlabel('Likelihood Scale')
    plt.legend(loc=(1.04,0))
    plt.show()

# In[63]:

for i in range(3):
    (a.groupby('Age')[[fact[i],fact1[i]]].mean().loc[['Less than 18 years','18 to 30 years','31 to 45 years','45 to 60 years', 'More than 60 years']].plot.barh())
    plt.xlabel('Likelihood Scale')
    plt.legend(loc=(1.04,0))
    plt.show()

# In[69]:

fake1=[]
fake2=[]
for i in fake:
    if '1' in i:

        fake1.append(i)
    else:
        fake2.append(i)

for i in range(3):
    (a.groupby('Age')[[fake1[i],fake2[i]]].mean().loc[['Less than 18 years','18 to 30 years','31 to 45 years','45 to 60 years', 'More than 60 years']].plot.barh())
    plt.xlabel('Likelihood Scale')
    plt.legend(loc=(1.04,0))
    plt.show()

# In[71]:

print(trust)
trust1=[]
trust2=[]
for i in trust:
    if '1' in i:

        trust1.append(i)
    else:
        trust2.append(i)

for i in range(3):
    (a.groupby('Age')[[trust1[i],trust2[i]]].mean().loc[['Less than 18 years','18 to 30 years','31 to 45 years','45 to 60 years', 'More than 60 years']].plot.barh())
    plt.xlabel('Likelihood Scale')
    plt.legend(loc=(1.04,0))
    plt.show()


import winsound
import time

# define notes and their corresponding frequencies
NOTES = {'C': 262, 'D': 294, 'E': 330, 'F': 349, 'G': 392, 'A': 440, 'B': 494}

# example melody
twinkle_twinkle = [('C', 500), ('C', 500), ('G', 500), ('G', 500),
                   ('A', 500), ('A', 500), ('G', 1000),
                   ('F', 500), ('F', 500), ('E', 500), ('E', 500),
                   ('D', 500), ('D', 500), ('C', 1000)]

# define a function to play a note for a given duration
def play_note(note, duration):
    frequency = NOTES.get(note, 0)
    if frequency:
        winsound.Beep(frequency, duration)

# define a function to play a melody using a list of notes and durations
def play_melody(melody):
    for note, duration in melody:
        play_note(note, duration)
        time.sleep(duration / 1000)

# play the melody
play_melody(twinkle_twinkle)


def car_info(company, model, **more_info):
    """Making a dictionary storing information about cars."""
    more_info['Company'] = company
    more_info['Model'] = model
    # adding the first two parameters to the dictionary

    for key, value in more_info.items():
        # looping through arbitrary arguments
        more_info[key] = value
        return more_info

complete_car_info = car_info('Toyota', 'Chrome', Origin='Germany', Seats=5)
# the variable complete_car_info equals the function including all arguments
print(complete_car_info)


l = int(input())
r = int(input())

if len(str(l)) == 1 and len(str(r)) == 1:
    print(r - l + 1)
    exit()

if len(str(l)) == 1:
    ans = 10 - l
    rstep = 1
    while rstep <= r:
        rstep *= 10
    rstep //= 10
    rstep *= (r // rstep)
    ans += (rstep // 10 - 1) * 10
    for d in range(10):
        cur = list(str(rstep))
        for pos in range(1, len(str(rstep))):
            cur[pos] = str((int(cur[pos - 1]) + d) % 10)
        if int(''.join(cur)) <= r:
            ans += 1
    print(ans)
    exit()

rstep = 1;
while rstep <= r:
    rstep *= 10
rstep //= 10
rstep *= (r // rstep)

lstep = 1
while lstep <= l:
    lstep *= 10
lstep //= 10
lstep *= (l // lstep)

lcp = lstep
rcp = rstep
while lcp % 10 == 0:
    lcp //= 10
    rcp //= 10

ans = (rcp - lcp) * 10

if len(str(l)) == 1:
    for d in range(10):
        if d < l:
            ans -= 1
else:
    for d in range(10):
        cur = list(str(lstep))
        for pos in range(1, len(str(lstep))):
            cur[pos] = str((int(cur[pos - 1]) + d) % 10)
        if int(''.join(cur)) < l:
            ans -= 1

for d in range(10):
    cur = list(str(rstep))
    for pos in range(1, len(str(rstep))):
        cur[pos] = str((int(cur[pos - 1]) + d) % 10)
    if int(''.join(cur)) <= r:
        ans += 1

print(ans)


def f1(n):
    c = 1
    for i in range(n):
        i = i + 1
        c = c * i
    return c

def f2(n):
    if n > 1:
        return n *f2(n-1)
    else:
        return 1

if __name__ == '__main__':
    print(f1(10))
    print(f2(10))