#!/usr/bin/env python

from __future__ import print_function
import argparse
import getpass
import re
import os
from u115 import API, File, Directory, Task

# SOCKS proxy
if os.environ.get('PROXY_ADDR'):
    proxy_addr = os.environ.get('PROXY_ADDR')
    proxy_port = int(os.environ.get('PROXY_PORT'))
    import socket
    import socks
    socks.set_default_proxy(socks.SOCKS5, proxy_addr, proxy_port)
    socket.socket = socks.socksocket
DRY_RUN = False


def parse_args():
    parser = argparse.ArgumentParser(prog='115down')
    group = parser.add_mutually_exclusive_group(required=False)
    group.add_argument('-u', '--username', help='account or username')
    group.add_argument('-d', '--section', default='default',
                       help='section name in credential file')
    parser.add_argument('entry_num', nargs='?', help='entry number',
                        type=int)
    parser.add_argument(
        'sub_num', nargs='?',
        help='sub-entry number(s) or range (e.g. 1,2,4-6); star (*) for all')
    parser.add_argument('-t', '--tasks', action='store_true',
                        help="list tasks")
    parser.add_argument('-l', '--list', action='store_true', default=True,
                        help="list the downloads directory")
    parser.add_argument('-n', '--count', default=30, type=int,
                        help="number of entries to get")
    parser.add_argument('-m', '--sub-count', default=30, type=int,
                        dest='sub_count',
                        help="number of sub-entries to get (0 for all)")
    parser.add_argument('-s', '--dry-run', action='store_true',
                        help="print urls instead of downloading")
    args = parser.parse_args()
    if args.entry_num is not None:
        if args.tasks is True:
            raise parser.error('Cannot list entries of tasks.')
        if args.entry_num <= 0:
            msg = 'Entry number must be a positive integer.'
            raise parser.error(msg)
    if args.sub_num is not None:
        args.sub_num = parse_sub_num(args.sub_num, parser)
    return args


def get_entries(username, password, section, entry_num, sub_num,
                count, sub_count, list_tasks=False):
    api = API()
    api.login(username, password, section)
    if list_tasks is True:
        entries = api.get_tasks(count)
    else:
        entries = api.downloads_directory.list(count)
    if entry_num is None:
        for k, entry in enumerate(entries, start=1):
            if list_tasks:
                print_entry(entry, num=k)
            else:
                print_entry(entry, num=k)
    elif entry_num is not None and sub_num is None:
        entry = entries[entry_num - 1]
        # entry may be a File object
        if isinstance(entry, File):
            download_file(entry)
        else:
            # Directory object
            if sub_count == 0:
                sub_count = entry.count
            list_entry(entry, sub_count)
    elif entry_num is not None and sub_num is not None:
        entry = entries[entry_num - 1]
        print(repr(entry))
        # entry may be a File object
        if isinstance(entry, File):
            download_file(entry)
            return
        # Directory object
        if sub_count == 0:
            sub_count = entry.count
        subs = entry.list(sub_count)
        if sub_num == '*':
            for k, f in enumerate(subs, start=1):
                print_entry(f, num=k)
                download_file(f)
        else:
            # sub_num is a set of integers
            nums = sub_num
            for num in nums:
                f = subs[num - 1]
                print_entry(f, num=num)
                download_file(f)


def print_entry(entry, prefix=None, num=None):
    size_human = None
    status_human = None
    if isinstance(entry, File):
        size_human = entry.size_human
    elif isinstance(entry, Task):
        size_human = entry.size_human
        status_human = entry.status_human
    args = []
    if prefix is not None:
        args.append(prefix)
    if num is not None:
        args.append(num)
    if status_human is not None:
        args.append('[%s]' % status_human)
    if size_human is not None:
        args.append('[%s]' % size_human)
    args.append(repr(entry))
    print(*args)


def list_entry(entry, sub_count):
    """List a directory"""
    actual_count = entry.count if sub_count > entry.count else sub_count
    stat = '(%d out of %d)' % (actual_count, entry.count)
    print(repr(entry), stat)
    for k, f in enumerate(entry.list(sub_count), start=1):
        print_entry(f, prefix='\t', num=k)


def download_file(f):
    if isinstance(f, File):
        print(f.url)
        if not DRY_RUN:
            f.download()
    elif isinstance(f, Directory):
        ffiles = f.list()
        for ff in ffiles:
            print(ff)
            print(ff.url)
            if not DRY_RUN:
                ff.download()


def parse_sub_num(s, parser):
    """
    Parse sub_num
    Return a string '*' or a set of integers
    """
    s = s.strip()
    if s == '*':
        return s
    nums = s.split(',')
    msg = 'Invalid sub-entry number.'
    res = set()
    for num in nums:
        num = num.strip()
        if num.isdigit():
            try:
                num = int(num)
                assert num > 0
                res.add(num)
            except:
                raise parser.error(msg)
        else:
            try:
                m = re.search('(\d+)-(\d+)', num)
                if m is None:
                    raise parser.error(msg)
                else:
                    a = int(m.group(1))
                    b = int(m.group(2))
                    assert a > 0
                    assert b > 0
                    assert a <= b
                    r = range(a, b + 1)
                    res.update(r)
            except:
                raise parser.error(msg)
    return res


def main():
    args = parse_args()
    global DRY_RUN
    DRY_RUN = args.dry_run
    password = None
    if args.username is not None:
        password = getpass.getpass()
    get_entries(args.username, password, args.section, args.entry_num,
                args.sub_num, args.count, args.sub_count, args.tasks)


if __name__ == '__main__':
    main()
