<gh_stars>0
from __future__ import print_function
import os
import shutil
import sys
import requests
import zipfile
import time


DEFAULT_REPO = "SnowleopardXI"
DEFAULT_BRANCH = "master"
TMPDIR = os.environ.get('TMPDIR', os.environ.get('TMP'))
URL_TEMPLATE = 'https://github.com/{}/stash/archive/{}.zip'
TEMP_ZIPFILE = os.path.join(TMPDIR, 'StaSh.zip')
TEMP_PTI = os.path.join(TMPDIR, 'ptinstaller.py')
URL_PTI = 'https://raw.githubusercontent.com/Snowleopard/pythonista-tools-installer/master/ptinstaller.py'
BASE_DIR = os.path.expanduser('~')
DEFAULT_INSTALL_DIR = os.path.join(BASE_DIR, 'Documents/site-packages/stash')
DEFAULT_PTI_PATH = os.path.join(DEFAULT_INSTALL_DIR, "bin", "ptinstaller.py")
IN_PYTHONISTA = sys.executable.find('Pythonista') >= 0
UNWANTED_FILES = [
        'getstash.py',
        'run_tests.py',
        'testing.py',
        'dummyui.py',
        'dummyconsole.py',
        'bin/pcsm.py',
        'bin/bh.py',
        'bin/pythonista.py',
        'bin/cls.py',
        'stash.py',
        'lib/librunner.py'
        'system/shui.py',
        'system/shterminal.py',
        'system/dummyui.py',
        'tests/stashtest.py',
    ]


class DownloadError(Exception):
    """
    Exception indicating a problem with a download.
    """
    pass


def download_stash(repo=DEFAULT_REPO, branch=DEFAULT_BRANCH, outpath=TEMP_ZIPFILE, verbose=False):
    """
    Download the StaSh zipfile from github.
    :param repo: user owning the repo to download from
    :type repo: str
    :param branch: branch to download
    :type branch: str
    :param verbose: if True, print additional information
    :type verbose: bool
    """
    url = URL_TEMPLATE.format(repo, branch)
    if verbose:
        print('Downloading {} ...'.format(url))
    r = requests.get(url, stream=True)
    file_size = r.headers.get('Content-Length')
    if file_size is not None:
        file_size = int(file_size)

    with open(outpath, 'wb') as outs:
        block_sz = 8192
        for chunk in r.iter_content(block_sz):
            outs.write(chunk)


def install_pti(url=URL_PTI, outpath=DEFAULT_PTI_PATH, verbose=False):
    """
    Download and install the pythonista tools installer.
    :param url: url to download from
    :type url: str
    :param outpath: path to save to
    :type outpath: str
    :param verbose: if True, print additional information
    :type verbose: bool
    """
    if verbose:
        print("Downloading {} to {}".format(url, outpath))
    r = requests.get(url)
    with open(outpath, 'w') as outs:
        outs.write(r.text)


def install_from_zip(path=TEMP_ZIPFILE, outpath=DEFAULT_INSTALL_DIR, launcher_path=None, verbose=False):
    """
    Install StaSh from its zipfile.
    :param path: path to zipfile
    :type path: str
    :param outpath: path to extract to
    :type outpath: str
    :param launcher_path: path to install launch_stash.py to
    :type launcher_path: str
    :param verbose: print additional information
    :type verbose: bool
    """
    unzip_into(path, outpath, verbose=verbose)
    if launcher_path is not None:
        # Move launch script to Documents for easy access
        shutil.move(os.path.join(outpath, 'launch_stash.py'), launcher_path)


def unzip_into(path, outpath, verbose=False):
    """
    Unzip zipfile at path into outpath.
    :param path: path to zipfile
    :type path: str
    :param outpath: path to extract to
    :type outpath: str
    """
    if not os.path.exists(outpath):
        os.makedirs(outpath)
    if verbose:
        print('Unzipping into %s ...' % outpath)
        
    with zipfile.ZipFile(path) as zipfp:
        toplevel_directory = None
        namelist = zipfp.namelist()
        
        # find toplevel directory name
        for name in namelist:
            if os.path.dirname(os.path.normpath(name)) == "":
                # is toplevel
                toplevel_directory = name
                break
        
        for name in namelist:
            data = zipfp.read(name)
            name = name.split(toplevel_directory, 1)[-1]  # strip the top-level directory
            if name == '':  # skip top-level directory
                continue

            fname = os.path.join(outpath, name)
            if fname.endswith('/'):  # A directory
                if not os.path.exists(fname):
                    os.makedirs(fname)
            else:
                fp = open(fname, 'wb')
                try:
                    fp.write(data)
                finally:
                    fp.close()


def remove_unwanted_files(basepath, reraise=False):
    """
    Remove unwanted files.
    :param basepath: path os StaSh installation
    :type basepath: str
    :param reraise: If True, reraise any exception occuring
    :type reraise: bool
    """
    for fname in UNWANTED_FILES:
        try:
            os.remove(os.path.join(basepath, fname))
        except:
            pass



def pythonista_install(install_path, repo=DEFAULT_REPO, branch=DEFAULT_BRANCH, launcher_path=None, zippath=None, verbose=False):
    """
    Download and install StaSh and other dependencies for pythonista.
    :param install_path: directory to install into
    :type install_path: str
    :param repo: name of user owning the github repo to download/install from
    :type repo: str
    :param branch: branch to download/install
    :type repo: str
    :param launcher_path: path to install launcher to
    :type launcher_path: str
    :param zippath: if not None, it specifies a path to a StaSh zipfile, otherwise download it from repo:branch
    :type zippath: str
    :param verbose: if True, print additional information
    :type verbose: bool
    """
    if zippath is None:
        zp = TEMP_ZIPFILE
        # download StaSh
        try:
            download_stash(repo=repo, branch=branch, outpath=zp, verbose=verbose)
        except:
            raise DownloadError("Unable to download StaSh from {}:{}".format(repo, branch))
    else:
        if verbose:
            print("Using '{}' as source.".format(zippath))
        zp = zippath
    try:
        # install StaSh
        install_from_zip(zp, install_path, launcher_path, verbose=verbose)
        # install pythonista tools installer
        # TODO: should this script realy install it?
        pti_path = os.path.join(install_path, "bin", "ptinstaller.py")
        install_pti(outpath=pti_path)
    finally:
        # cleanup
        if verbose:
            print("Cleaning up...")
        if os.path.exists(zp):
            os.remove(zp)
        remove_unwanted_files(install_path, reraise=False)


def setup_install(repo=DEFAULT_REPO, branch=DEFAULT_BRANCH, install_path=None, as_user=False, zippath=None, dryrun=False, verbose=False):
    """
    Download and install StaSh using setup.py
    :param repo: name of user owning the github repo to download/install from
    :type repo: str
    :param branch: branch to download/install
    :type repo: str
    :param install_path: path to install to (as --prefix)
    :type install_path: str
    :param as_user: install into user packages
    :type as_user: bool
    :param zippath: alternative path to zip to install from (default: download from repo:branch)
    :param dryrun: if True, pass --dry-run to setup.py
    :param verbose: if True, print additional information
    :type verbose: bool
    """
    if zippath is None:
        zp = TEMP_ZIPFILE
        # download StaSh
        try:
            download_stash(repo=repo, branch=branch, outpath=zp, verbose=verbose)
        except:
            raise DownloadError("Unable to download StaSh from {}:{}".format(repo, branch))
    else:
        zp = zippath
    tp = os.path.join(TMPDIR, "getstash-{}".format(time.time()))
    unzip_into(zp, tp, verbose=verbose)
    # run setup.py
    os.chdir(tp)
    argv = ["setup.py", "install"]
    if as_user:
        argv.append("--user")
    if install_path is not None:
        argv += ["--prefix", install_path]
    if dryrun:
        argv.append("--dry-run")
    sys.argv = argv
    fp = os.path.abspath("setup.py")
    ns = {
        "__name__": "__main__",
        "__file__": fp,
    }
    with open(fp, "rU") as fin:
        content = fin.read()
        code = compile(content, fp, "exec", dont_inherit=True)
        exec(code, ns, ns)
    

def main(defs={}):
    """
    The main function.
    :param defs: namespace which may contain additional parameters
    :type defs: dict
    """
    # read additional arguments
    # These arguments will not be defined when StaSh is normally installed,
    # but both selfupdate and tests may specify different values
    # i would like to use argparse here, but this must be compatible with older StaSh versions
    repo = defs.get("_owner", DEFAULT_REPO)                  # owner of repo
    branch = defs.get("_br", DEFAULT_BRANCH)                 # target branch
    is_update = '_IS_UPDATE' in defs                         # True if update
    install_path = defs.get("_target", None)                 # target path
    launcher_path = defs.get("_launcher_path", None)         # target path for launch_stash.py
    force_dist = defs.get("_force_dist", None)               # force install method
    zippath = defs.get("_zippath", None)                     # alternate path of zipfile to use
    dryrun = defs.get("_dryrun", None)                       # do not do anything if True
    asuser = defs.get("_asuser", None)                       # install as user if True
    
    # find out which install to use
    if force_dist is None:
        if IN_PYTHONISTA:
            dist = "pythonista"
        else:
            dist = "setup"
    else:
        dist = force_dist
    
    if dist.lower() == "pythonista":
        if install_path is None:
            install_path = DEFAULT_INSTALL_DIR
        if launcher_path is None:
            launcher_path = os.path.join(BASE_DIR, "Documents", "launch_stash.py")
        pythonista_install(install_path, repo=repo, branch=branch, launcher_path=launcher_path, zippath=zippath, verbose=True)
    elif dist.lower() == "setup":
        setup_install(repo, branch, install_path=install_path, zippath=zippath, dryrun=dryrun, as_user=asuser, verbose=True)
    else:
        raise ValueError("Invalid install type: {}".format(dist))
        
    if not is_update:
        # print additional instructions
        print('Installation completed.')
        print('Please restart Pythonista and run launch_stash.py under the home directory to start StaSh.')


# if __name__ == "__main__":
#     print("executing main()")
#     main(locals())
    
main(locals())  # older StaSh versions do not pass __name__="__main__" to getstash.py, so we must run this on toplevel
