# -*- coding:utf-8 -*-
# Author: hankcs
# Date: 2019-08-25 00:19
import unicodedata
from typing import List, Dict, Tuple


def format_scores(results: Dict[str, float]) -> str:
    return ' - '.join(f'{k}: {v:.4f}' for (k, v) in results.items())


def ispunct(token):
    return all(unicodedata.category(char).startswith('P')
               for char in token)


def split_long_sentence_into(tokens: List[str], max_seq_length, sent_delimiter=None, char_level=False,
                             hard_constraint=False):
    punct_offset = [i for i, x in enumerate(tokens) if
                    ((sent_delimiter and x in sent_delimiter) or (not sent_delimiter and ispunct(x)))]
    if not punct_offset:
        # treat every token as punct
        punct_offset = [i for i in range(len(tokens))]
    punct_offset += [len(tokens)]
    token_to_char_offset = []
    if char_level:
        offset = 0
        for token in tokens:
            token_to_char_offset.append(offset)
            offset += len(token)
        token_to_char_offset.append(offset)

    start = 0
    for i, offset in enumerate(punct_offset[:-1]):
        end = punct_offset[i + 1]
        length_at_next_punct = _len(start, end, token_to_char_offset, char_level)
        if length_at_next_punct >= max_seq_length:
            if hard_constraint:
                yield from _gen_short_sent(tokens, start, offset, max_seq_length, token_to_char_offset, char_level)
            else:
                yield tokens[start: offset + 1]
            start = offset + 1
    offset = punct_offset[-1]
    if start < offset:
        offset -= 1
        length_at_next_punct = _len(start, offset, token_to_char_offset, char_level)
        if length_at_next_punct >= max_seq_length and hard_constraint:
            yield from _gen_short_sent(tokens, start, offset, max_seq_length, token_to_char_offset, char_level)
        else:
            yield tokens[start:]


def _gen_short_sent(tokens, start, offset, max_seq_length, token_to_char_offset, char_level):
    while start <= offset:
        for j in range(offset + 1, start, -1):
            if _len(start, j, token_to_char_offset, char_level) <= max_seq_length or j == start + 1:
                yield tokens[start: j]
                start = j
                break


def _len(start, end, token_to_char_offset, char_level):
    if char_level:
        length_at_next_punct = token_to_char_offset[end] - token_to_char_offset[start]
    else:
        length_at_next_punct = end - start
    return length_at_next_punct


def guess_delimiter(tokens):
    if all(ord(c) < 128 for c in ''.join(tokens)):
        delimiter_in_entity = ' '
    else:
        delimiter_in_entity = ''
    return delimiter_in_entity


def split_long_sent(sent, delimiters, max_seq_length):
    parts = []
    offset = 0
    for idx, char in enumerate(sent):
        if char in delimiters:
            parts.append(sent[offset:idx + 1])
            offset = idx + 1
    if not parts:
        yield sent
        return
    short = []
    for idx, part in enumerate(parts):
        short += part
        if idx == len(parts) - 1:
            yield short
        else:
            if len(short) + len(parts[idx + 1]) > max_seq_length:
                yield short
                short = []


def possible_tokenization(text: str) -> List[Tuple[str]]:
    """Enumerate all possible tokenizations of a text.

    Args:
        text: A text.

    Returns: All possible tokenizations.

    """
    states = [((), ())]
    for c in text:
        new_states = []
        for t, b in states:
            # to split
            new_states.append((t + (''.join(b + (c,)),), ()))
            # not to split
            new_states.append((t, b + (c,)))
        states = new_states
    return [t for t, b in states if not b]
