#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#

from os.path import sep as dirsep

import deluge.component as component
import deluge.ui.console.utils.colors as colors
from deluge.common import TORRENT_STATE, fsize, fspeed
from deluge.ui.client import client
from deluge.ui.common import FILE_PRIORITY
from deluge.ui.console.utils.format_utils import (
    f_progressbar,
    f_seedrank_dash,
    format_date_never,
    format_progress,
    format_time,
    ftotal_sized,
    pad_string,
    remove_formatting,
    shorten_hash,
    strwidth,
    trim_string,
)

from . import BaseCommand

STATUS_KEYS = [
    'state',
    'download_location',
    'tracker_host',
    'tracker_status',
    'next_announce',
    'name',
    'total_size',
    'progress',
    'num_seeds',
    'total_seeds',
    'num_peers',
    'total_peers',
    'eta',
    'download_payload_rate',
    'upload_payload_rate',
    'ratio',
    'distributed_copies',
    'num_pieces',
    'piece_length',
    'total_done',
    'files',
    'file_priorities',
    'file_progress',
    'peers',
    'is_seed',
    'is_finished',
    'active_time',
    'seeding_time',
    'time_since_transfer',
    'last_seen_complete',
    'seed_rank',
    'all_time_download',
    'total_uploaded',
    'total_payload_download',
    'total_payload_upload',
    'time_added',
    'label',
]

# Add filter specific state to torrent states
STATES = ['Active'] + TORRENT_STATE


class Command(BaseCommand):
    """Show information about the torrents"""

    sort_help = 'sort items.  Possible keys: ' + ', '.join(STATUS_KEYS)

    epilog = """
  You can give the first few characters of a torrent-id to identify the torrent.

  Tab Completion in interactive mode (info *pattern*<tab>):\n
      | First press of <tab> will output up to 15 matches;
      | hitting <tab> a second time, will print 15 more matches;
      | and a third press will print all remaining matches.
      | (To modify behaviour of third <tab>, set `third_tab_lists_all` to False)
"""

    def add_arguments(self, parser):
        parser.add_argument(
            '-v',
            '--verbose',
            action='store_true',
            default=False,
            dest='verbose',
            help=_('Show more information per torrent.'),
        )
        parser.add_argument(
            '-d',
            '--detailed',
            action='store_true',
            default=False,
            dest='detailed',
            help=_('Show more detailed information including files and peers.'),
        )
        parser.add_argument(
            '-s',
            '--state',
            action='store',
            dest='state',
            help=_('Show torrents with state STATE: %s.' % (', '.join(STATES))),
        )
        parser.add_argument(
            '--sort',
            action='store',
            type=str,
            default='',
            dest='sort',
            help=self.sort_help,
        )
        parser.add_argument(
            '--sort-reverse',
            action='store',
            type=str,
            default='',
            dest='sort_rev',
            help=_('Same as --sort but items are in reverse order.'),
        )
        parser.add_argument(
            'torrent_ids',
            metavar='<torrent-id>',
            nargs='*',
            help=_('One or more torrent ids. If none is given, list all'),
        )

    def add_subparser(self, subparsers):
        parser = subparsers.add_parser(
            self.name,
            prog=self.name,
            help=self.__doc__,
            description=self.__doc__,
            epilog=self.epilog,
        )
        self.add_arguments(parser)

    def handle(self, options):
        self.console = component.get('ConsoleUI')
        # Compile a list of torrent_ids to request the status of
        torrent_ids = []

        if options.torrent_ids:
            for t_id in options.torrent_ids:
                torrent_ids.extend(self.console.match_torrent(t_id))
        else:
            torrent_ids.extend(self.console.match_torrent(''))

        def on_torrents_status(status):
            # Print out the information for each torrent
            sort_key = options.sort
            sort_reverse = False
            if not sort_key:
                sort_key = options.sort_rev
                sort_reverse = True
            if not sort_key:
                sort_key = 'name'
                sort_reverse = False
            if sort_key not in STATUS_KEYS:
                self.console.write('')
                self.console.write(
                    '{!error!}Unknown sort key: ' + sort_key + ', will sort on name'
                )
                sort_key = 'name'
                sort_reverse = False
            for key, value in sorted(
                status.items(),
                key=lambda x: x[1].get(sort_key),
                reverse=sort_reverse,
            ):
                self.show_info(key, status[key], options.verbose, options.detailed)

        def on_torrents_status_fail(reason):
            self.console.write('{!error!}Error getting torrent info: %s' % reason)

        status_dict = {'id': torrent_ids}

        if options.state:
            options.state = options.state.capitalize()
            if options.state in STATES:
                status_dict.state = options.state
            else:
                self.console.write('Invalid state: %s' % options.state)
                self.console.write('Possible values are: %s.' % (', '.join(STATES)))
                return

        d = client.core.get_torrents_status(status_dict, STATUS_KEYS)
        d.addCallback(on_torrents_status)
        d.addErrback(on_torrents_status_fail)
        return d

    def show_file_info(self, torrent_id, status):
        spaces_per_level = 2

        if hasattr(self.console, 'screen'):
            cols = self.console.screen.cols
        else:
            cols = 80

        prevpath = []
        for index, torrent_file in enumerate(status['files']):
            filename = torrent_file['path'].split(dirsep)[-1]
            filepath = torrent_file['path'].split(dirsep)[:-1]

            for depth, subdir in enumerate(filepath):
                indent = ' ' * depth * spaces_per_level
                if depth >= len(prevpath):
                    self.console.write(f'{indent}{{!cyan!}}{subdir}')
                elif subdir != prevpath[depth]:
                    self.console.write(f'{indent}{{!cyan!}}{subdir}')

            depth = len(filepath)

            indent = ' ' * depth * spaces_per_level

            col_filename = indent + filename
            col_size = ' ({!cyan!}%s{!input!})' % fsize(torrent_file['size'])
            col_progress = ' {!input!}%.2f%%' % (status['file_progress'][index] * 100)

            col_priority = ' {!info!}Priority: '

            file_priority = FILE_PRIORITY[status['file_priorities'][index]]

            if status['file_progress'][index] != 1.0:
                if file_priority == 'Skip':
                    col_priority += '{!error!}'
                else:
                    col_priority += '{!success!}'
            else:
                col_priority += '{!input!}'
            col_priority += file_priority

            def tlen(string):
                return strwidth(remove_formatting(string))

            col_all_info = col_size + col_progress + col_priority
            # Check how much space we've got left after writing all the info
            space_left = cols - tlen(col_all_info)
            # And how much we will potentially have with the longest possible column
            maxlen_space_left = cols - tlen(' (1000.0 MiB) 100.00% Priority: Normal')
            if maxlen_space_left > tlen(col_filename) + 1:
                # If there is enough space, pad it all nicely
                col_all_info = ''
                col_all_info += ' ('
                spaces_to_add = tlen(' (1000.0 MiB)') - tlen(col_size)
                col_all_info += ' ' * spaces_to_add
                col_all_info += col_size[2:]
                spaces_to_add = tlen(' 100.00%') - tlen(col_progress)
                col_all_info += ' ' * spaces_to_add
                col_all_info += col_progress
                spaces_to_add = tlen(' Priority: Normal') - tlen(col_priority)
                col_all_info += col_priority
                col_all_info += ' ' * spaces_to_add
                # And remember to put it to the left!
                col_filename = pad_string(
                    col_filename, maxlen_space_left - 2, side='right'
                )
            elif space_left > tlen(col_filename) + 1:
                # If there is enough space, put the info to the right
                col_filename = pad_string(col_filename, space_left - 2, side='right')
            else:
                # And if there is not, shorten the name
                col_filename = trim_string(col_filename, space_left, True)
            self.console.write(col_filename + col_all_info)

            prevpath = filepath

    def show_peer_info(self, torrent_id, status):
        if len(status['peers']) == 0:
            self.console.write('    None')
        else:
            s = ''
            for peer in status['peers']:
                if peer['seed']:
                    s += '%sSeed\t{!input!}' % colors.state_color['Seeding']
                else:
                    s += '%sPeer\t{!input!}' % colors.state_color['Downloading']

                s += peer['country'] + '\t'

                if peer['ip'].count(':') == 1:
                    # IPv4
                    s += peer['ip']
                else:
                    # IPv6
                    s += '[{}]:{}'.format(
                        ':'.join(peer['ip'].split(':')[:-1]),
                        peer['ip'].split(':')[-1],
                    )

                c = peer['client']
                s += '\t' + c

                if len(c) < 16:
                    s += '\t\t'
                else:
                    s += '\t'
                s += '{}{}\t{}{}'.format(
                    colors.state_color['Seeding'],
                    fspeed(peer['up_speed']),
                    colors.state_color['Downloading'],
                    fspeed(peer['down_speed']),
                )
                s += '\n'

            self.console.write(s[:-1])

    def show_info(self, torrent_id, status, verbose=False, detailed=False):
        """
        Writes out the torrents information to the screen.

        Format depends on switches given.
        """
        self.console.set_batch_write(True)

        if hasattr(self.console, 'screen'):
            cols = self.console.screen.cols
        else:
            cols = 80

        sep = ' '

        if verbose or detailed:
            self.console.write('{!info!}Name: {!input!}%s' % (status['name']))
            self.console.write('{!info!}ID: {!input!}%s' % (torrent_id))
            s = '{{!info!}}State: {}{}'.format(
                colors.state_color[status['state']],
                status['state'],
            )
            # Only show speed if active
            if status['state'] in ('Seeding', 'Downloading'):
                if status['state'] != 'Seeding':
                    s += sep
                    s += '{!info!}Down Speed: {!input!}%s' % fspeed(
                        status['download_payload_rate'], shortform=True
                    )
                s += sep
                s += '{!info!}Up Speed: {!input!}%s' % fspeed(
                    status['upload_payload_rate'], shortform=True
                )
            self.console.write(s)

            if status['state'] in ('Seeding', 'Downloading', 'Queued'):
                s = '{{!info!}}Seeds: {{!input!}}{} ({})'.format(
                    status['num_seeds'],
                    status['total_seeds'],
                )
                s += sep
                s += '{{!info!}}Peers: {{!input!}}{} ({})'.format(
                    status['num_peers'],
                    status['total_peers'],
                )
                s += sep
                s += (
                    '{!info!}Availability: {!input!}%.2f' % status['distributed_copies']
                )
                s += sep
                s += '{!info!}Seed Rank: {!input!}%s' % f_seedrank_dash(
                    status['seed_rank'], status['seeding_time']
                )
                self.console.write(s)

            total_done = fsize(status['total_done'], shortform=True)
            total_size = fsize(status['total_size'], shortform=True)
            if total_done == total_size:
                s = '{!info!}Size: {!input!}%s' % (total_size)
            else:
                s = f'{{!info!}}Size: {{!input!}}{total_done}/{total_size}'
            s += sep
            s += '{!info!}Downloaded: {!input!}%s' % fsize(
                status['all_time_download'], shortform=True
            )
            s += sep
            s += '{!info!}Uploaded: {!input!}%s' % fsize(
                status['total_uploaded'], shortform=True
            )
            s += sep
            s += '{!info!}Share Ratio: {!input!}%.2f' % status['ratio']
            self.console.write(s)

            s = '{!info!}ETA: {!input!}%s' % format_time(status['eta'])
            s += sep
            s += '{!info!}Seeding: {!input!}%s' % format_time(status['seeding_time'])
            s += sep
            s += '{!info!}Active: {!input!}%s' % format_time(status['active_time'])
            self.console.write(s)

            s = '{!info!}Last Transfer: {!input!}%s' % format_time(
                status['time_since_transfer']
            )
            s += sep
            s += '{!info!}Complete Seen: {!input!}%s' % format_date_never(
                status['last_seen_complete']
            )
            self.console.write(s)

            s = '{!info!}Tracker: {!input!}%s' % status['tracker_host']
            self.console.write(s)

            self.console.write(
                '{!info!}Tracker status: {!input!}%s' % status['tracker_status']
            )

            if not status['is_finished']:
                pbar = f_progressbar(
                    status['progress'], cols - (13 + len('%.2f%%' % status['progress']))
                )
                s = '{{!info!}}Progress: {{!input!}}{:.2f}% {}'.format(
                    status['progress'], pbar
                )
                self.console.write(s)

            s = '{!info!}Download Folder: {!input!}%s' % status['download_location']
            self.console.write(s)

            if 'label' in status:
                s = '{!info!}Label: {!input!}%s' % status['label']
                self.console.write(s)

            if detailed:
                self.console.write('\n{!info!}Files in torrent')
                self.show_file_info(torrent_id, status)
                self.console.write('{!info!}Connected peers')
                self.show_peer_info(torrent_id, status)
        else:
            up_color = colors.state_color['Seeding']
            down_color = colors.state_color['Downloading']

            s = '{}{}'.format(
                colors.state_color[status['state']],
                '[' + status['state'][0] + ']',
            )

            s += ' {!info!}' + format_progress(status['progress']).rjust(6, ' ')
            s += ' {!input!}%s' % (status['name'])

            # Shorten the ID if it's necessary. Pretty hacky
            # XXX: should make a nice function for it that can partition and shorten stuff
            space_left = cols - strwidth('[S] 99.99% ' + status['name'])

            if self.console.interactive and space_left >= len(sep + torrent_id):
                # Not enough line space so shorten the hash (for interactive mode).
                torrent_id = shorten_hash(torrent_id, space_left)
            s += sep
            s += '{!cyan!}%s' % torrent_id
            self.console.write(s)

            dl_info = '{!info!}DL: {!input!}'
            dl_info += '%s' % ftotal_sized(
                status['all_time_download'], status['total_payload_download']
            )

            if status['download_payload_rate'] > 0:
                dl_info += ' @ {}{}'.format(
                    down_color,
                    fspeed(status['download_payload_rate'], shortform=True),
                )

            ul_info = ' {!info!}UL: {!input!}'
            ul_info += '%s' % ftotal_sized(
                status['total_uploaded'], status['total_payload_upload']
            )
            if status['upload_payload_rate'] > 0:
                ul_info += ' @ {}{}'.format(
                    up_color,
                    fspeed(status['upload_payload_rate'], shortform=True),
                )

            eta = ' {!info!}ETA: {!magenta!}%s' % format_time(status['eta'])

            self.console.write('    ' + dl_info + ul_info + eta + '\n')

        self.console.set_batch_write(False)

    def complete(self, line):
        # We use the ConsoleUI torrent tab complete method
        return component.get('ConsoleUI').tab_complete_torrent(line)
