import copy
import re
import warnings
from base64 import b64decode, b64encode
from functools import reduce
from json import loads, dumps
from operator import call
from typing import *
from zlib import decompress, compress

import unicodedata

try:
    import pyperclip
except ImportError:
    pyperclip = None

# region Functools
def invcall(func, *args, **kwargs):
    """ Version of `functional.predef.call()` with inversed arguments order. """
    return call(*reversed(args), func, **kwargs)

def chain(el, *funcs: Callable):
    return reduce(invcall, funcs, el)
# endregion
# region Encoding
encode_string = lambda data: chain(data, dumps, str.encode, compress, b64encode)
decode_string = lambda data: chain(data, b64decode, decompress, loads)

def decode_blueprint(data: bytes):
    if (not data):
        raise ValueError("Cannot decode empty data")
    elif (data[0] == b'0'[0]):
        return decode_string(data[1:])
    else:
        raise ValueError(f"Unsupported blueprint format: wrong heading byte, got {data[0]!r}")

def encode_blueprint(data) -> bytes:
    return b'0' + encode_string(data)
# endregion

BLUEPRINT_TEMPLATE = b'''0eNp9UMFqwzAM/ZWhs1PWNC4ksG/oYb2VUZxU2wSxnDlKaAj598kJ29gOwxf5Penp6c1QtwN2kVigmoGawD1Ulxl6emPXJoydR6ggMeJYsib4mthJiLAYIL7hHar98mIAWUgIN4H1M1158DVGbTD/CRnoQq+zgdNG1cuKx501MOngYWd1z40iNltDYZKGxNBea3x3I6mATvUb3/+u1cmXRQOv1ArGv6hMXfI1UpRBT/42ukWQnRT5UEKvUZBD9GuTuu9cXN1X8LQCQwpRk9C3pDxI0Cv5k7CB1tWoqcJz8PhwxnvCRrW0HmaPeVmUpbW5PR7y/bJ8Ah9Wir4='''

def sanitize_text(text: str) -> str:
    slug =  unicodedata.normalize('NFD', text.lower()).encode('ascii', 'ignore')
    return re.sub(r'[^a-zA-Z\s0-9]+', ' ', slug.decode('utf-8'))

def load_template():
    container = decode_blueprint(BLUEPRINT_TEMPLATE)
    return container

def make_text_blueprint(text: str):
    container = load_template()
    bp = container['blueprint']

    bp['label'] = text
    template = bp['entities'][0]
    entity_number = 0
    bp['entities'] = list()
    for y, line in enumerate(sanitize_text(text).splitlines()):
        for x, char in enumerate(line):
            if (char.isspace()):
                continue
            else:
                entity_number += 1
            
            cc = copy.deepcopy(template)
            cc['entity_number'] = entity_number
            cc['control_behavior']['sections']['sections'][0]['filters'][0]['name'] = f'signal-{char.upper()}'
            cc['position']['x'] += x
            cc['position']['y'] += y
            bp['entities'].append(cc)
            
    
    return container

def print_text(text: str, *, copy_to_clipboard: bool = False) -> None:
    
    print(f"Preparing a blueprint for constant combinators text with text '{text}'...")
    bp = make_text_blueprint(text)
    
    print("Encoding the resulted blueprint...")
    encoded = encode_blueprint(bp).decode()
    print()
    print(encoded)
    print()
    
    if (copy_to_clipboard):
        if (pyperclip is None):
            warnings.warn(ImportWarning("Optional module pyperclip is not installed. Copy to clipboard feature can't be used"))
        else:
            print("Copying to clipboard...")
            pyperclip.copy(encoded)
    
    print("Done!")


__all__ = \
[
    'sanitize_text',
    'make_text_blueprint',
    'print_text',
]

def main():
    print_text("Hello\nworld", copy_to_clipboard=True)
    
    return 0

if (__name__ == '__main__'):
    exit_code = main()
    exit(exit_code)
