#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
# 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.
#

import re
from collections import deque
from unicodedata import east_asian_width, normalize

import deluge.common
from deluge.ui.common import FILE_PRIORITY


def format_size(size):
    return deluge.common.fsize(size, shortform=True)


def format_speed(speed):
    if speed > 0:
        return deluge.common.fspeed(speed, shortform=True)
    else:
        return '-'


def format_time(time):
    if time > 0:
        return deluge.common.ftime(time)
    elif time == 0:
        return '-'
    else:
        return '∞'


def format_date_dash(time):
    if time > 0:
        return deluge.common.fdate(time, date_only=True)
    else:
        return '-'


def format_date_never(time):
    if time > 0:
        return deluge.common.fdate(time, date_only=True)
    else:
        return 'Never'


def format_float(x):
    if x < 0:
        return '-'
    else:
        return '%.3f' % x


def format_seeds_peers(num, total):
    return '%d (%d)' % (num, total)


def format_progress(value):
    return ('%.2f' % value).rstrip('0').rstrip('.') + '%'


def f_progressbar(progress, width):
    """
    Returns a string of a progress bar.

    :param progress: float, a value between 0-100

    :returns: str, a progress bar based on width

    """

    w = width - 2  # we use a [] for the beginning and end
    s = '['
    p = int(round((progress / 100) * w))
    s += '#' * p
    s += '-' * (w - p)
    s += ']'
    return s


def f_seedrank_dash(seed_rank, seeding_time):
    """Display value if seeding otherwise dash"""

    if seeding_time > 0:
        if seed_rank >= 1000:
            return '%ik' % (seed_rank // 1000)
        else:
            return str(seed_rank)
    else:
        return '-'


def ftotal_sized(first, second):
    return '{} ({})'.format(
        deluge.common.fsize(first, shortform=True),
        deluge.common.fsize(second, shortform=True),
    )


def format_pieces(num, size):
    return '%d (%s)' % (num, deluge.common.fsize(size, shortform=True))


def format_priority(prio):
    if prio == -2:
        return '[Mixed]'
    elif prio < 0:
        return '-'
    return FILE_PRIORITY[prio]


def format_queue(qnum):
    if qnum < 0:
        return ''
    return '%d' % (qnum + 1)


def trim_string(string, w, have_dbls):
    if w <= 0:
        return ''
    elif w == 1:
        return ' '
    elif have_dbls:
        # have to do this the slow way
        chrs = []
        width = 4
        idx = 0
        while width < w:
            chrs.append(string[idx])
            if east_asian_width(string[idx]) in 'WF':
                width += 2
            else:
                width += 1
            idx += 1
        if width != w:
            chrs.pop()
            chrs.append('.')
        return '%s ' % (''.join(chrs))
    else:
        return '%s ' % (string[0 : w - 1])


def format_column(col, lim):
    try:
        # might have some double width chars
        col = normalize('NFC', col)
        dbls = sum(east_asian_width(c) in 'WF' for c in col)
    except TypeError:
        dbls = 0

    size = len(col) + dbls
    if size >= lim - 1:
        return trim_string(col, lim, dbls > 0)
    else:
        return '{}{}'.format(col, ' ' * (lim - size))


def format_row(row, column_widths):
    return ''.join(
        [format_column(row[i], column_widths[i]) for i in range(0, len(row))]
    )


_strip_re = re.compile(r'\{!.*?!\}')
_format_code = re.compile(r'\{\|(.*)\|\}')


def remove_formatting(string):
    return re.sub(_strip_re, '', string)


def shorten_hash(tid, space_left, min_width=13, placeholder='...'):
    """Shorten the supplied torrent infohash by removing chars from the middle.

    Use a placeholder to indicate shortened.
    If unable to shorten will justify so entire tid is on the next line.

    """
    tid = tid.strip()
    if space_left >= min_width:
        mid = len(tid) // 2
        trim, remain = divmod(len(tid) + len(placeholder) - space_left, 2)
        return tid[0 : mid - trim] + placeholder + tid[mid + trim + remain :]
    else:
        # Justity the tid so it is completely on the next line.
        return tid.rjust(len(tid) + space_left)


def wrap_string(string, width, min_lines=0, strip_colors=True):
    """
    Wrap a string to fit in a particular width.  Returns a list of output lines.

    :param string: str, the string to wrap
    :param width: int, the maximum width of a line of text
    :param min_lines: int, extra lines will be added so the output tuple contains at least min_lines lines
    :param strip_colors: boolean, if True, text in {!!} blocks will not be considered as adding to the
                              width of the line.  They will still be present in the output.
    """
    ret = []
    s1 = string.split('\n')
    indent = ''

    def insert_clr(s, offset, mtchs, clrs):
        end_pos = offset + len(s)
        while mtchs and (mtchs[0] <= end_pos) and (mtchs[0] >= offset):
            mtc = mtchs.popleft() - offset
            clr = clrs.popleft()
            end_pos += len(clr)
            s = f'{s[:mtc]}{clr}{s[mtc:]}'
        return s

    for s in s1:
        offset = 0
        indent = ''
        m = _format_code.search(remove_formatting(s))
        if m:
            if m.group(1).startswith('indent:'):
                indent = m.group(1)[len('indent:') :]
            elif m.group(1).startswith('indent_pos:'):
                begin = m.start(0)
                indent = ' ' * begin
            s = _format_code.sub('', s)

        if strip_colors:
            mtchs = deque()
            clrs = deque()
            for m in _strip_re.finditer(s):
                mtchs.append(m.start())
                clrs.append(m.group())
            cstr = _strip_re.sub('', s)
        else:
            cstr = s

        def append_indent(line, string, offset):
            """Prepends indent to string if specified"""
            if indent and offset != 0:
                string = indent + string
            line.append(string)

        while cstr:
            # max with for a line. If indent is specified, we account for this
            max_width = width - (len(indent) if offset != 0 else 0)
            if len(cstr) < max_width:
                break
            sidx = cstr.rfind(' ', 0, max_width - 1)
            sidx += 1
            if sidx > 0:
                if strip_colors:
                    to_app = cstr[0:sidx]
                    to_app = insert_clr(to_app, offset, mtchs, clrs)
                    append_indent(ret, to_app, offset)
                    offset += len(to_app)
                else:
                    append_indent(ret, cstr[0:sidx], offset)
                cstr = cstr[sidx:]
                if not cstr:
                    cstr = None
                    break
            else:
                # can't find a reasonable split, just split at width
                if strip_colors:
                    to_app = cstr[0:width]
                    to_app = insert_clr(to_app, offset, mtchs, clrs)
                    append_indent(ret, to_app, offset)
                    offset += len(to_app)
                else:
                    append_indent(ret, cstr[0:width], offset)
                cstr = cstr[width:]
                if not cstr:
                    cstr = None
                    break
        if cstr is not None:
            to_append = cstr
            if strip_colors:
                to_append = insert_clr(cstr, offset, mtchs, clrs)
            append_indent(ret, to_append, offset)

    if min_lines > 0:
        for i in range(len(ret), min_lines):
            ret.append(' ')

    # Carry colors over to the next line
    last_color_string = ''
    for i, line in enumerate(ret):
        if i != 0:
            ret[i] = f'{last_color_string}{ret[i]}'

        colors = re.findall('\\{![^!]+!\\}', line)
        if colors:
            last_color_string = colors[-1]

    return ret


def strwidth(string):
    """
    Measure width of a string considering asian double width characters
    """
    return sum(1 + (east_asian_width(char) in ['W', 'F']) for char in string)


def pad_string(string, length, character=' ', side='right'):
    """
    Pad string with specified character to desired length, considering double width characters.
    """
    w = strwidth(string)
    diff = length - w
    if side == 'left':
        return f'{character * diff}{string}'
    elif side == 'right':
        return f'{string}{character * diff}'


def delete_alt_backspace(input_text, input_cursor, sep_chars=' *?!._~-#$^;\'"/'):
    """
    Remove text from input_text on ALT+backspace
    Stop removing when countering any of the sep chars
    """
    deleted = 0
    seg_start = input_text[:input_cursor]
    seg_end = input_text[input_cursor:]
    none_space_deleted = False  # Track if any none-space characters have been deleted

    while seg_start and input_cursor > 0:
        if (not seg_start) or (input_cursor == 0):
            break
        if deleted and seg_start[-1] in sep_chars:
            if seg_start[-1] == ' ':
                if seg_start[-2] == ' ' or none_space_deleted is False:
                    # Continue as long as:
                    # * next char is also a space
                    # * no none-space characters have been deleted
                    pass
                else:
                    break
            else:
                break

        if not none_space_deleted:
            none_space_deleted = seg_start[-1] != ' '
        seg_start = seg_start[:-1]
        deleted += 1
        input_cursor -= 1

    input_text = seg_start + seg_end
    return input_text, input_cursor
