import json
import logging

from .types import Pos, Text, Meme
from . import utils

DATA_FILE = utils.relative_path(__file__, "..", "memes.json")

DATA = {}
ALIASES = {}
LIST = []

logger = logging.getLogger("meme_db")


def load_memes(purge=False):
    """
    TODO

    :param (bool) purge:
    """
    global DATA, ALIASES
    if purge:
        DATA = {}
        ALIASES = {}
    try:
        with open(DATA_FILE) as f:
            content = "".join(f.readlines())
        raw_data = json.loads(content)
        if not (isinstance(raw_data, list)):
            raise TypeError(f"Root is not a list")
        for i in range(len(raw_data)):
            load_item(i, raw_data[i])
    except OSError as e:
        logger.error(f"Could not read data file: {e}")
    except json.decoder.JSONDecodeError as e:
        logger.error(f"Wrong JSON syntax '{DATA_FILE}': {e}")
    except TypeError as e:
        logger.error(f"Invalid data file: {e}")


def load_item(i, item):
    """
    TODO

    :param (int) i:
    :param (dict) item:
    """
    global LIST
    item_id = ""
    try:
        if not (isinstance(item, dict)):
            raise TypeError(f"root is not a dict")
        item_id = utils.read_key(item, "id", types=[str])
        if item_id in DATA:
            raise NameError(f"id '{item_id}' already existing")
        based_on = utils.read_key_safe(item, "based_on", types=[str])
        if based_on is not None:
            if based_on in DATA:
                meme = DATA[based_on].clone()
                meme.id = item_id
            else:
                raise NameError(f"Reference '{based_on}' not found in data, make sur it's placed before this one")
        else:
            meme = Meme(item_id)
        meme.abstract = utils.read_key_safe(item, "abstract", False, types=[bool])
        meme.aliases = utils.read_key_safe(item, "aliases", [], types=[str], is_list=True)
        meme.info = utils.read_key_safe(item, "info", types=[str])
        meme.text_base = load_text(0, item, meme.text_base)
        if not meme.abstract:
            meme.template = utils.read_key(item, "template", meme.template, types=[str])
        raw_texts = utils.read_key(item, "texts", meme.texts, types=[dict], is_list=True)
        if "texts" in item:
            meme.texts = []
            c = 1
            for j in range(len(raw_texts)):
                raw_text = raw_texts[j]
                try:
                    text = load_text(c, raw_text)
                    if text.text_ref is None:
                        c += 1
                    elif text.text_ref < 1 or text.text_ref > len(meme.texts):
                        logger.warning(
                            f"Item '{item_id}'({i + 1}) / Text {j + 1}: invalid text reference {text.text_ref}")
                        continue
                    else:
                        text.text_ref -= 1
                        text.text = meme.texts[text.text_ref].text
                    if text.style_ref is not None:
                        if text.style_ref < 1 or text.style_ref > len(meme.texts):
                            logger.warning(
                                f"Item '{item_id}'({i + 1}) / Text {j + 1}: invalid style reference {text.style_ref}")
                        else:
                            text.style_ref -= 1
                            text.update(meme.texts[text.style_ref])
                    meme.texts += [text]
                    meme.texts_len = c - 1
                except TypeError as e:
                    logger.warning(f"Item '{item_id}'({i + 1}) / Text {j + 1}: {e}")
        for text in meme.texts:
            text.update(meme.text_base)
        if not meme.abstract and len(meme.texts) == 0:
            logger.warning(f"Item '{item_id}'({i + 1}): no texts loaded")
        else:
            DATA[item_id] = meme
            if not meme.abstract:
                LIST += [item_id]
                ALIASES[item_id] = item_id
                for alias in meme.aliases:
                    if alias in ALIASES:
                        logger.warning(
                            f"Item '{item_id}'({i + 1}): alias '{alias}' already registered by '{ALIASES[alias]}'")
                    else:
                        ALIASES[alias] = item_id
            logger.info(f"Loaded meme '{item_id}' with {len(meme.texts)} texts")
    except KeyError as e:
        logger.warning(f"Item '{item_id}'({i + 1}): key {e} not found")
    except TypeError as e:
        logger.warning(f"Item '{item_id}'({i + 1}): {e}")
    except NameError as e:
        logger.warning(f"Item '{item_id}'({i + 1}): {e}")


def load_text(c, raw_text, text=None):
    """
    TODO

    :param (int) c:
    :param (dict) raw_text:
    :param (Text|None) text:
    :raises TypeError:
    :rtype: Text
    :return:
    """
    if text is None:
        text = Text(f"text {c}")
    text.font = utils.read_key_safe(raw_text, "font", text.font, types=[str])
    text.x_range = utils.read_key_safe(raw_text, "x_range", types=[float, int], is_list=True, is_list_size=2)
    text.y_range = utils.read_key_safe(raw_text, "y_range", types=[float, int], is_list=True, is_list_size=2)
    text.text_ref = utils.read_key_safe(raw_text, "text_ref", types=[int])
    text.style_ref = utils.read_key_safe(raw_text, "style_ref", types=[int])
    text.angle = utils.read_key_safe(raw_text, "angle", types=[float, int])
    text.font_size = utils.read_key_safe(raw_text, "font_size", text.font_size, types=[float, int])
    text.fill = utils.read_key_safe(raw_text, "fill", text.fill, types=[int], is_list=True, is_list_size=3)
    text.stroke_width = utils.read_key_safe(raw_text, "stroke_width", text.stroke_width, types=[float, int])
    text.stroke_fill = utils.read_key_safe(raw_text, "stroke_fill", text.stroke_fill, types=[int], is_list=True,
                                           is_list_size=3)
    if "position" in raw_text:
        if raw_text["position"] not in [p.name for p in Pos]:
            raise TypeError(f"'position' is not a valid position (ex: NW, E, SE, ...)")
        text.position = [p for p in Pos if p.name == raw_text["position"]][0]
    if "align" in raw_text:
        if raw_text["align"] not in ["left", "center", "right"]:
            raise TypeError(f"'align' is not 'left', 'center' or 'right'")
        text.align = raw_text["align"]
    return text


def get_meme(name):
    """
    TODO

    :param (str) name:
    :rtype: Meme|None
    :return:
    """
    name = name.lower().strip().replace(" ", "_")
    if name in ALIASES:
        return DATA[ALIASES[name]].clone()
    else:
        return None


def find_nearest(word):
    word = word.lower().strip().replace(" ", "_")
    return utils.find_nearest(word, ALIASES.keys())
