#!/usr/bin/env python
from steamctl.clients import CachingSteamClient
import sys
import json
import requests
from pathlib import Path
from argparse import Namespace
from io import StringIO

steamClient = False

class App:
    appId = 0
    name = ""
    executable = ""
    start = ""
    icon = ""
    shortcut = ""
    options = ""
    hidden = False
    desktopConfig = False
    overlay = False
    vr = False
    devkit = False
    devkitId = ""
    playTime = 0
    tags = "null"
    
    def __init__(self, appId):
        self.appId = appId
    
    def __repr__(self):
        return "App %s (%s at %s)" % (self.appId, self.name, self.executable)
    
    def __hash__(self):
        return self.appId
    
    def __eq__(self, other):
        if self.appId != other.appId:
            return False
        if self.name != other.name:
            return False
        if self.executable != other.executable:
            return False
        if self.start != other.start:
            return False
        if self.icon != other.icon:
            return False
        if self.shortcut != other.shortcut:
            return False
        if self.options != other.options:
            return False
        if self.hidden != other.hidden:
            return False
        if self.desktopConfig != other.desktopConfig:
            return False
        if self.overlay != other.overlay:
            return False
        if self.vr != other.vr:
            return False
        if self.devkit != other.devkit:
            return False
        if self.devkitId != other.devkitId:
            return False
        if self.playTime != other.playTime:
            return False
        if self.tags != other.tags:
            return False
        return True

def parseShortcuts(shortcutFile):
    data = []
    with open(shortcutFile, 'rb') as shortcutData:
        rawData = shortcutData.read()
        i = 0
        buffer = bytearray()
        app = None
        while i < len(rawData):
            buffer.append(rawData[i])
            try:
                buffCont = buffer.decode()
            except UnicodeDecodeError:
                #print(rawData[:i])
                #print("Clearing buffer %s after %d" % (buffer[:-1].decode(), buffer[-1]))
                buffer.clear()
                buffCont = ""
            found = True
            if buffCont.endswith("shortcuts"):
                buffer.clear()
                i += 2
                while rawData[i] != 2:
                    buffer.append(rawData[i])
                    i += 1
                data.append({
                    "shortcuts": buffer[:-1].decode()
                })
            if buffCont.endswith("appid"):
                buffer.clear()
                i += 2
                # Some app names do have a 1 for one of their bits
                while rawData[i + 1:i + 8] != bytearray("AppName", "utf-8"):
                    buffer.append(rawData[i])
                    i += 1
                app = App(int.from_bytes(buffer, "little"))
                data.append(app)
            elif buffCont.endswith("AppName"):
                buffer.clear()
                i += 2
                while rawData[i + 1:i + 4] != bytearray("Exe", "utf-8"):
                    buffer.append(rawData[i])
                    i += 1
                app.name = buffer[:-1].decode()
            elif buffCont.endswith("Exe"):
                buffer.clear()
                i += 2
                while rawData[i + 1:i + 9] != bytearray("StartDir", "utf-8"):
                    buffer.append(rawData[i])
                    i += 1
                app.executable = buffer[:-1].decode()
            elif buffCont.endswith("StartDir"):
                buffer.clear()
                i += 2
                while rawData[i] != 0:
                    buffer.append(rawData[i])
                    i += 1
                app.start = buffer.decode()
            elif buffCont.endswith("icon"):
                buffer.clear()
                i += 2
                while rawData[i + 1:i + 13] != bytearray("ShortcutPath", "utf-8"):
                    buffer.append(rawData[i])
                    i += 1
                app.icon = buffer[:-1].decode()
            elif buffCont.endswith("ShortcutPath"):
                buffer.clear()
                i += 2
                while rawData[i + 1:i + 14] != bytearray("LaunchOptions", "utf-8"):
                    buffer.append(rawData[i])
                    i += 1
                app.shortcut = buffer[:-1].decode()
            elif buffCont.endswith("LaunchOptions"):
                buffer.clear()
                i += 2
                while rawData[i + 1:i + 9] != bytearray("IsHidden", "utf-8"):
                    buffer.append(rawData[i])
                    i += 1
                app.options = buffer[:-1].decode()
            elif buffCont.endswith("IsHidden"):
                buffer.clear()
                i += 2
                while len(buffer) < 3:
                    buffer.append(rawData[i])
                    i += 1
                app.hidden = int.from_bytes(buffer, "little")
            elif buffCont.endswith("AllowDesktopConfig"):
                buffer.clear()
                i += 2
                while len(buffer) < 3:
                    buffer.append(rawData[i])
                    i += 1
                app.desktopConfig = int.from_bytes(buffer, "little")
            elif buffCont.endswith("AllowOverlay"):
                buffer.clear()
                i += 2
                while len(buffer) < 3:
                    buffer.append(rawData[i])
                    i += 1
                app.overlay = int.from_bytes(buffer, "little")
            elif buffCont.endswith("OpenVR"):
                buffer.clear()
                i += 2
                while len(buffer) < 3:
                    buffer.append(rawData[i])
                    i += 1
                app.vr = int.from_bytes(buffer, "little")
            elif buffCont.endswith("Devkit") and type(app.devkit) == bool:
                buffer.clear()
                i += 2
                while len(buffer) < 3:
                    buffer.append(rawData[i])
                    i += 1
                app.devkit = int.from_bytes(buffer, "little")
            elif buffCont.endswith("DevkitGameID"):
                buffer.clear()
                i += 2
                while rawData[i + 1:i + 13] != bytearray("LastPlayTime", "utf-8"):
                    buffer.append(rawData[i])
                    i += 1
                app.devkitId = buffer[:-1].decode()
            elif buffCont.endswith("LastPlayTime"):
                buffer.clear()
                i += 2
                while len(buffer) < 4:
                    buffer.append(rawData[i])
                    i += 1
                app.playTime = int.from_bytes(buffer, "little")
            elif buffCont.endswith("tags"):
                buffer.clear()
                i += 2
                while i < len(rawData) and rawData[i] != 2:
                    buffer.append(rawData[i])
                    i += 1
                if i < len(rawData) and rawData[i] == 2:
                    buffer = buffer[:-1]
                app.tags = buffer.decode()
            else:
                found = False
            #print('\r' + buffCont, end='')
            if found:
                #try:
                    #print(": %s" % buffer.decode())
                #except UnicodeDecodeError:
                    #print(": %s" % int.from_bytes(buffer, "little"))
                buffer.clear()
            i += 1
    return data

def writeShortcuts(apps, shortcutFile):
    encoding = "utf-8"
    data = bytearray([0x00])
    for app in apps:
        if type(app) == dict:
            data += bytearray("shortcuts", encoding)
            data.append(0x00)
            data += bytearray(apps[0]["shortcuts"], encoding)
        else:
            data.append(0x00)
            data.append(0x02)
            data += bytearray("appid", encoding)
            data.append(0x00)
            data += int(app.appId).to_bytes(4, "little")
            data.append(0x01)
            data += bytearray("AppName", encoding)
            data.append(0x00)
            data += bytearray(app.name, encoding)
            data.append(0x00)
            data.append(0x01)
            data += bytearray("Exe", encoding)
            data.append(0x00)
            data += bytearray(app.executable, encoding)
            data.append(0x00)
            data.append(0x01)
            data += bytearray("StartDir", encoding)
            data.append(0x00)
            data += bytearray(app.start, encoding)
            data.append(0x00)
            data.append(0x01)
            data += bytearray("icon", encoding)
            data.append(0x00)
            data += bytearray(app.icon, encoding)
            data.append(0x00)
            data.append(0x01)
            data += bytearray("ShortcutPath", encoding)
            data.append(0x00)
            data += bytearray(app.shortcut, encoding)
            data.append(0x00)
            data.append(0x01)
            data += bytearray("LaunchOptions", encoding)
            data.append(0x00)
            data += bytearray(app.options, encoding)
            data.append(0x00)
            data.append(0x02)
            data += bytearray("IsHidden", encoding)
            data.append(0x00)
            data += int(app.hidden).to_bytes(3, "little")
            data.append(0x00)
            data.append(0x02)
            data += bytearray("AllowDesktopConfig", encoding)
            data.append(0x00)
            data += int(app.desktopConfig).to_bytes(3, "little")
            data.append(0x00)
            data.append(0x02)
            data += bytearray("AllowOverlay", encoding)
            data.append(0x00)
            data += int(app.overlay).to_bytes(3, "little")
            data.append(0x00)
            data.append(0x02)
            data += bytearray("OpenVR", encoding)
            data.append(0x00)
            data += int(app.vr).to_bytes(3, "little")
            data.append(0x00)
            data.append(0x02)
            data += bytearray("Devkit", encoding)
            data.append(0x00)
            data += int(app.devkit).to_bytes(3, "little")
            data.append(0x00)
            data.append(0x01)
            data += bytearray("DevkitGameID", encoding)
            data.append(0x00)
            data += bytearray(app.devkitId, encoding)
            data.append(0x00)
            data.append(0x02)
            data += bytearray("LastPlayTime", encoding)
            data.append(0x00)
            data += int(app.playTime).to_bytes(4, "little")
            data.append(0x00)
            data += bytearray("tags", encoding)
            data.append(0x00)
            data += bytearray(app.tags, encoding)
    with open(shortcutFile, 'wb') as stream:
        stream.write(data)

def getSteamArgs():
    global steamClient
    if not steamClient:
        args = Namespace()
        args.user = None
        args.anonymous = True
        args.skip_licenses = True
        steamClient = CachingSteamClient()
        print("Logging into steam")
        steamClient.login_from_args(args)
    return steamClient

def getHeader(code):
    return "https://cdn.cloudflare.steamstatic.com/steam/apps/%s/header.jpg" % code

def getLibraryTall(code):
    return "https://cdn.cloudflare.steamstatic.com/steam/apps/%s/library_600x900.jpg" % code

def getBackground(code):
    return "https://cdn.cloudflare.steamstatic.com/steam/apps/%s/library_hero.jpg" % code

def getLogo(code):
    return "https://cdn.cloudflare.steamstatic.com/steam/apps/%s/logo.png" % code

def getGameInfo(code):
    fromContent = requests.get(fromURL, allow_redirects=True)
    return "https://cdn.cloudflare.steamstatic.com/steam/apps/%s/logo.png" % code

def copyContent(name, content, byte=True):
    userdata = Path(Path.home(), ".steam", "root", "userdata")
    for user in userdata.iterdir():
        toFile = Path(user, name)
        print("Writing to %s" % (toFile))
        mode = 'w'
        if byte:
            mode += 'b'
        with open(toFile, mode) as stream:
            stream.write(content)

def copyFile(fromURL, toCode):
    fromURLComp = fromURL.split('/')
    fromName = fromURLComp[6]
    fromCode = fromURLComp[5]
    userdata = Path(Path.home(), ".steam", "root", "userdata")
    
    # Gets the final file name
    if fromName == "library_hero.jpg":
        toName = "_hero.jpg"
    elif fromName == "library_600x900.jpg":
        toName = "p.jpg"
    elif fromName == "header.jpg":
        toName = ".jpg"
    else:
        toName = "_" + fromName
    toName = toCode + toName
    
    print("Copying %s (%s) to %s" % (fromName, fromCode, toName))
    fromContent = requests.get(fromURL, allow_redirects=True)
    copyContent(Path("config", "grid", toName), fromContent.content)

def copyContents(ids):
    client = getSteamArgs()
    fromCodes = []
    toCodes = []
    reverse = {}
    for id in ids:
        fromCode = id[0]
        toCode = id[1]
        reverse[toCode] = fromCode
        fromCodes.append(fromCode)
        toCodes.append(toCode)
        copyFile(getHeader(str(fromCode)), str(toCode))
        copyFile(getLibraryTall(str(fromCode)), str(toCode))
        copyFile(getBackground(str(fromCode)), str(toCode))
        copyFile(getLogo(str(fromCode)), str(toCode))
    data = client.get_product_info(apps=fromCodes)
    i = 0
    for i in range(len(ids)):
        logo = data["apps"][ids[i][0]]["common"]["library_assets"]["logo_position"]
        logo2 = {
            "nversion": 1,
            "logoPosition": {
                "pinnedPosition": logo["pinned_position"],
                "nWidthPct": logo["width_pct"],
                "nHeightPct": logo["height_pct"]
            }
        }
        copyContent(Path("config", "grid", str(ids[i][1]) + ".json"), json.dumps(logo2), byte=False)
        
        logo2 = [[
                "achievements", {
                    "version":2,
                    "data": {
                        "vecHighlight": [],
                        "vecUnachieved": [],
                        "vecAchievedHidden": [],
                        "nTotal": 0,
                        "nAchieved": 0
                    }
                }
            ], [
                "customimage", {
                    "version": 1,
                    "data": logo2
                }
            ]
        ]
        copyContent(Path("config", "librarycache", str(ids[i][1]) + ".json"), json.dumps(logo2), byte=False)
    
    userdata = Path(Path.home(), ".steam", "root", "userdata")
    for user in userdata.iterdir():
        shortcutData = parseShortcuts(Path(user, "config", "shortcuts.vdf"))
        for app in shortcutData:
            if type(app) != dict:
                if app.appId in toCodes:
                    print("Setting shortcut data for %s", app.name)
                    app.name = data["apps"][reverse[app.appId]]["common"]["name"]
                    app.icon = str(Path(user, "config", "grid", str(app.appId) + ".jpg"))
                    print("Setting %s to %s" % (app.name, app.icon))
        writeShortcuts(shortcutData, Path(user, "config", "shortcuts.vdf"))
        
    
if __name__ == "__main__":
    keys = []
    ids = []
    ops = []
    for term in sys.argv[1:]:
        if not term.startswith('-'):
            keys.append(term)
            if term.isnumeric():
                if len(ids) > 0 and type(ids[-1]) == int:
                    ids[-1] = (ids[-1], int(term))
                else:
                    ids.append(int(term))
            else:
                ids = None
        else:
            if term[1] == '-':
                ops.append(term[2:])
            else:
                for op in term[1:]:
                    ops.append(op)
    if len(keys) < 2 and not "a" in ops:
        print("%s [OPTIONS] (<fromId> <toId>)..." % __file__)
        print("%s [OPTIONS] <term>..." % __file__)
        exit(1)
    if ids:
        if type(ids[-1]) == int:
            print("Must have an even number of fromID - toId pairs")
            exit(2)
    
        copyContents(ids)
    else:
        tempApps = {}
        apps = []
        userdata = Path(Path.home(), ".steam", "root", "userdata")
        for user in userdata.iterdir():
            shortcutData = parseShortcuts(Path(user, "config", "shortcuts.vdf"))
            for app in shortcutData:
                if type(app) != dict:
                    if "a" in ops:
                        apps.append(app)
                    else:
                        for term in keys:
                            if term.lower() in app.name.lower():
                                if not app in tempApps:
                                    tempApps[app] = 1
                                else:
                                    tempApps[app] += 1
        if not "a" in ops:
            for app in tempApps:
                if tempApps[app] / len(keys) >= 0.7:
                    apps.append(app)
        if len(apps) == 0:
            if "v" in ops:
                print("No matches found")
            exit(0)
        elif len(apps) == 1 and not "v" in ops and not "l" in ops:
            print(apps[0].appId)
        else:
            for app in apps:
                print("%d - %s" % (app.appId, app.name))
                if "l" in ops:
                    print("\tExecutable: %s" % app.executable)
                    print("\tRuntime Directory: %s" % app.start)
                    print("\tIcon: %s" % app.icon)
                    print("\tShortcut: %s" % app.shortcut)
                    print("\tRuntime Options: %s" % app.options)
                    print("\tIs Hidden: %s" % bool(app.hidden))
                    print("\tDesktop Configuration: %s" % bool(app.desktopConfig))
                    print("\tOverlay Enabled: %s" % bool(app.overlay))
                    print("\tIs VR: %s" % bool(app.vr))
                    print("\tDevkit: %d" % app.devkit)
                    print("\tDevkit ID: %s" % app.devkitId)
                    print("\tDate of last Play: %s" % app.playTime)
                    print("\tTags: %s" % app.tags)
