#! /usr/bin/env python3

import argparse
import sys
import os
from os.path import join, expanduser, dirname
from getpass import getpass
from http.cookiejar import LWPCookieJar
import fnmatch

import yaml

import humbleclient
from humbleclient.utils    import *
from humbleclient.version  import version
from humbleclient.website  import Website, LoginFailed
from humbleclient.database import Database
from humbleclient.download import Downloader

config = {
    "account": "",
    "directory": "~/Humble",
    "cookies": join(xdg_cache_dir(), "cookies.txt"),
    "database": join(xdg_cache_dir(), "humble.db"),
    "keys": [],
    "format"   : {
        "purchases": "- {human_name} ({machine_name}, {url}, ${total}, {created})",
        "library": "{human_name} ({machine_name}, {url})",
        "download": "{human_name}/{platform}/{filename}",
    },
    "exclude": {
        "bundles": [],
        "platforms": [],
        "games": [],
        "files": [],
    },
}

class FormatError(ValueError):
    pass

def format(format, name, variables, indent=0):
    try:
        return ' ' * indent + format[name].format(**variables)
    except KeyError as e:
        raise FormatError(name, *e.args)

# def warning_if_empty(humble):
#     if humble.is_empty():
#         if humble.is_logged():
#             print("You are logged in but your library is empty.")
#             print("You should add some games to your account or use `--key`.")
#         else:
#             print("You are not logged in and you did not provide any key.")
#             print("You should log in or use `--key`.")

def login(config, humble, db, args):
    email    = config["account"]
    password = getpass("Password for account `%s`: " % email)
    
    try:
        result = humble.login_start(email, password)
    except LoginFailed as e:
        print("Login failed: %s." % e.args)
        sys.exit(1)
    
    if not result:
        code = input("Enter the code from the email to proceed: ")
        try:
            humble.login_verify(code)
        except VerifyFailed as e:
            print("Verification failed: %s." % e.args)
            sys.exit(1)
    
    humble.login_end()

def update(config, humble, db, args):
    keys = config["keys"]
    if args.key is not None:
        keys.append(args.key)
    db.update(humble, keys)

def purchases(config, humble, db, args):
    for purchase in db.purchases(order=args.sort,
                                 human_name=args.title):
        print(format(config["format"], "purchases", purchase, indent=0))

def library(config, humble, db, args):
    for product in db.products(order=args.sort):
        print(format(config["format"], "library", product, indent=0))

def ls(config, humble, db, args):
    downloads = []
    for download in db.downloads(config["exclude"]):
        downloads.append(format(config["format"], "download", download, indent=0))
    
    if '*' in args.glob:
        glob = args.glob
    else:
        glob = args.glob + '*'
    
    downloads.sort()
    for download in downloads:
        if fnmatch.fnmatch(download, glob):
            print(download)

def get(config, humble, db, args):
    download = Downloader(dryrun=args.dryrun)
    details  = db.find_filename(args.filename)
    
    if details is None:
        print("""The file "{}" is not known in the database.
You may try to run `humblec update` to update the cache.""".format(args.filename)
              , file=sys.stderr)
        sys.exit(1)
    
    url = humble.find_url(details["gamekey"], details["machine_name"], args.filename)
    download.get(url, args.filename)

def download(config, humble, db, args):
    download = Downloader(dryrun=args.dryrun)
    
    for file in db.downloads(config["exclude"]):
        filename = format(config["format"], "download", file, indent=0)
        url = humble.find_url(file["orders_gamekey"],
                              file["products_machine_name"],
                              file["filename"])
        dest = os.path.join(config["directory"], filename)
        if url is None:
            print("Error, missing url for %s" % dest)
        else:
            download.get(url, dest, size=file["size"])

def check(config, humble, db, args):
    for file in db.downloads():
        filename = format(config["format"], "download", file, indent=0)
        dest = os.path.join(config["directory"], filename)
        
        if not os.path.exists(dest):
            print(dest, "Not found")
            continue
        
        size = file["size"]
        try:
            if int(size) > 0:
                if not check_file_size(dest, size):
                    print(dest, "Wrong size")
                    continue
        except TypeError:
            pass
        
        md5 = file["md5"]
        failed_md5 = None
        if len(md5) == 32:
            failed_md5 = not check_file_md5(dest, md5)
        
        sha1 = file["sha1"]
        failed_sha1 = None
        if len(sha1) == 40:
            failed_sha1 = not check_file_sha1(dest, sha1)
        
        # occasionnaly, one of the two values (MD5 and SHA1) is crappy,
        # so when both are given we require two failures to display an
        # error.
        failure = False
        if failed_md5 is None and failed_sha1 == True:
            failure = True
        if failed_md5 == True and failed_sha1 is None:
            failure = True
        if failed_md5 == True and failed_sha1 == True:
            failure = True
        
        if failure:
            print(dest, "Wrong hashes")
        else:
            if not args.quiet:
                print(dest, "OK")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Humble Client -- Non official client for Humble Bundle')
    parser.add_argument('--version', action='version', version='%(prog)s ' + version)
    parser.add_argument("-c", "--config",
                        dest="config",
                        default=join(xdg_config_dir(), "config.yml"),
                        help="Configuration file")
    parser.add_argument("--key", "-k",
                        dest="key",
                        metavar="KEY",
                        nargs=1,
                        default=None,
                        help="Work on the given key instead of using an account")
    subparsers = parser.add_subparsers()
    
    parser_login = subparsers.add_parser('login', help='Log in to an account')
    parser_login.set_defaults(func=login)
    
    parser_update = subparsers.add_parser('update', help='Update database')
    parser_update.set_defaults(func=update)
    
    parser_purchases = subparsers.add_parser('purchases', help='List purchases')
    parser_purchases.add_argument("--title",
                                  dest="title",
                                  default=None,
                                  metavar="GLOB",
                                  help="Filter on title")
    parser_purchases.add_argument('--sort',
                                  dest="sort",
                                  default='date',
                                  choices=['date', 'alpha', 'rdate', 'ralpha'],
                                  help="Sort criterion (default is date)")
    parser_purchases.set_defaults(func=purchases)
    
    parser_library = subparsers.add_parser('library', help='List library content')
    parser_library.add_argument('--sort',
                                dest="sort",
                                default='alpha',
                                choices=['date', 'alpha', 'rdate', 'ralpha'],
                                help="Sort criterion (default is alpha)")
    parser_library.set_defaults(func=library)
    
    parser_ls = subparsers.add_parser('ls', help='List files')
    parser_ls.add_argument('glob',
                           default='*',
                           nargs='?',
                           help="Filename filter (glob expression)")
    parser_ls.set_defaults(func=ls)
    
    parser_get = subparsers.add_parser('get', help='Download a single file')
    parser_get.add_argument('filename',
                            help="Filename or path")
    parser_get.add_argument("--dry-run", "-n",
                            dest="dryrun",
                            default=False,
                            action="store_true",
                            help="Do not download anything, just display commands")
    parser_get.set_defaults(func=get)
    
    parser_download = subparsers.add_parser('download', help='Download all files')
    parser_download.add_argument("--dry-run", "-n",
                            dest="dryrun",
                            default=False,
                            action="store_true",
                            help="Do not do anything, just display commands")
    parser_download.set_defaults(func=download)
    
    parser_check = subparsers.add_parser('check', help='Check downloaded files')
    parser_check.add_argument("--quiet", "-q",
                              dest="quiet",
                              default=False,
                              action="store_true",
                              help="Do not display anything for correct files")
    parser_check.set_defaults(func=check)
    
    args = parser.parse_args()
    
    default_format = config["format"]
    try:
        with open(args.config) as f:
            config.update(yaml.safe_load(f))
    except FileNotFoundError:
        pass
    
    config["directory"] = os.path.expanduser(config["directory"])
    config["cookies"]   = os.path.expanduser(config["cookies"])
    config["database"]  = os.path.expanduser(config["database"])
    
    for f in default_format.keys():
        if f not in config["format"]:
            config["format"][f] = default_format[f]
    
    makedirs(os.path.dirname(config["cookies"]))
    makedirs(os.path.dirname(config["database"]))
    humble = Website(cookies=LWPCookieJar(config["cookies"]))
    db     = Database(config["database"])
    
    if args.key is not None:
        humble.keys(args.key)
    
    # simulate python2 argparse behavior
    try:
        if "func" in args:
            args.func(config, humble, db, args)
        else:
            parser.print_usage()
            print("%s: error: too few arguments" % parser.prog)
            sys.exit(2)
    
    except FormatError as e:
        print("Invalid key '%s' in format '%s'." % (e.args[1], e.args[0]), file=sys.stderr)
        sys.exit(2)
    except KeyboardInterrupt:
        sys.exit(0)

