import argparse
import copy
import json
import os
import logging
import random
import textwrap
import shlex
import sys

import source.classes.constants as CONST
from source.classes.BabelFish import BabelFish

from Utils import update_deprecated_args


class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):

    def _get_help_string(self, action):
        return textwrap.dedent(action.help)

def parse_cli(argv, no_defaults=False):
    def defval(value):
        return value if not no_defaults else None

    # get settings
    settings = parse_settings()

    lang = "en"
    fish = BabelFish(lang=lang)

    # we need to know how many players we have first
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255))
    multiargs, _ = parser.parse_known_args(argv)

    parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)

    # get args
    args = []
    with open(os.path.join("resources","app","cli","args.json")) as argsFile:
      args = json.load(argsFile)
      for arg in args:
        argdata = args[arg]
        argname = "--" + arg
        argatts = {}
        argatts["help"] = "(default: %(default)s)"
        if "action" in argdata:
          argatts["action"] = argdata["action"]
        if "choices" in argdata:
          argatts["choices"] = argdata["choices"]
          argatts["const"] = argdata["choices"][0]
          argatts["default"] = argdata["choices"][0]
          argatts["nargs"] = "?"
        if arg in settings:
          default = settings[arg]
          if "type" in argdata and argdata["type"] == "bool":
              default = settings[arg] != 0
          argatts["default"] = defval(default)
        arghelp = fish.translate("cli","help",arg)
        if "help" in argdata and argdata["help"] == "suppress":
          argatts["help"] = argparse.SUPPRESS
        elif not isinstance(arghelp,str):
          argatts["help"] = '\n'.join(arghelp).replace("\\'","'")
        else:
          argatts["help"] = arghelp + " " + argatts["help"]
        parser.add_argument(argname,**argatts)

    parser.add_argument('--seed', default=defval(int(settings["seed"]) if settings["seed"] != "" and settings["seed"] is not None else None), help="\n".join(fish.translate("cli","help","seed")), type=int)
    parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else 1), help="\n".join(fish.translate("cli","help","count")), type=int)
    parser.add_argument('--customitemarray', default={}, help=argparse.SUPPRESS)

    # included for backwards compatibility
    parser.add_argument('--beemizer', default=defval(settings["beemizer"]), type=lambda value: min(max(int(value), 0), 4))
    parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255))
    parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1))

    if multiargs.multi:
        for player in range(1, multiargs.multi + 1):
            parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS)

    ret = parser.parse_args(argv)

    if ret.keysanity:
        ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4

    if multiargs.multi:
        defaults = copy.deepcopy(ret)
        for player in range(1, multiargs.multi + 1):
            playerargs = parse_cli(shlex.split(getattr(ret,f"p{player}")), True)

            for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
                         'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid',
                         'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
                         'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
                         'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
                         'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
                         'remote_items']:
                value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
                if player == 1:
                    setattr(ret, name, {1: value})
                else:
                    getattr(ret, name)[player] = value

    return ret


def parse_settings():
    # set default settings
    settings = {
        "lang": "en",
        "retro": False,
        "mode": "open",
        "logic": "noglitches",
        "goal": "ganon",
        "crystals_gt": "7",
        "crystals_ganon": "7",
        "swords": "random",
        "difficulty": "normal",
        "item_functionality": "normal",
        "timer": "none",
        "progressive": "on",
        "accessibility": "items",
        "algorithm": "balanced",

        # Shuffle Ganon defaults to TRUE
        "openpyramid": False,
        "shuffleganon": True,
        "shuffle": "vanilla",

        "shufflepots": False,
        "shuffleenemies": "none",
        "shufflebosses": "none",
        "enemy_damage": "default",
        "enemy_health": "default",
        "enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"),

        "mapshuffle": False,
        "compassshuffle": False,
        "keyshuffle": False,
        "bigkeyshuffle": False,
        "keysanity": False,
        "door_shuffle": "basic",
        "experimental": False,
        "dungeon_counters": "default",

        "multi": 1,
        "names": "",

        # Hints default to TRUE
        "hints": True,
        "no_hints": False,
        "disablemusic": False,
        "quickswap": False,
        "heartcolor": "red",
        "heartbeep": "normal",
        "sprite": os.path.join(".","data","sprites","official","001.link.1.zspr"),
        "fastmenu": "normal",
        "ow_palettes": "default",
        "uw_palettes": "default",

        # Spoiler     defaults to FALSE
        # Playthrough defaults to TRUE
        # ROM         defaults to TRUE
        "create_spoiler": False,
        "calc_playthrough": True,
        "create_rom": True,
        "usestartinventory": False,
        "custom": False,
        "rom": os.path.join(".", "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"),

        "seed": "",
        "count": 1,
        "startinventory": "",
        "beemizer": 0,
        "remote_items": False,
        "race": False,
        "customitemarray": {
            "bow": 0,
            "progressivebow": 2,
            "boomerang": 1,
            "redmerang": 1,
            "hookshot": 1,
            "mushroom": 1,
            "powder": 1,
            "firerod": 1,
            "icerod": 1,
            "bombos": 1,
            "ether": 1,
            "quake": 1,
            "lamp": 1,
            "hammer": 1,
            "shovel": 1,
            "flute": 1,
            "bugnet": 1,
            "book": 1,
            "bottle": 4,
            "somaria": 1,
            "byrna": 1,
            "cape": 1,
            "mirror": 1,
            "boots": 1,
            "powerglove": 0,
            "titansmitt": 0,
            "progressiveglove": 2,
            "flippers": 1,
            "pearl": 1,
            "heartpiece": 24,
            "heartcontainer": 10,
            "sancheart": 1,
            "sword1": 0,
            "sword2": 0,
            "sword3": 0,
            "sword4": 0,
            "progressivesword": 4,
            "shield1": 0,
            "shield2": 0,
            "shield3": 0,
            "progressiveshield": 3,
            "mail2": 0,
            "mail3": 0,
            "progressivemail": 2,
            "halfmagic": 1,
            "quartermagic": 0,
            "bombsplus5": 0,
            "bombsplus10": 0,
            "arrowsplus5": 0,
            "arrowsplus10": 0,
            "arrow1": 1,
            "arrow10": 12,
            "bomb1": 0,
            "bomb3": 16,
            "bomb10": 1,
            "rupee1": 2,
            "rupee5": 4,
            "rupee20": 28,
            "rupee50": 7,
            "rupee100": 1,
            "rupee300": 5,
            "blueclock": 0,
            "greenclock": 0,
            "redclock": 0,
            "silversupgrade": 0,
            "generickeys": 0,
            "triforcepieces": 0,
            "triforcepiecesgoal": 0,
            "triforce": 0,
            "rupoor": 0,
            "rupoorcost": 10
        },
        "randomSprite": False,
        "outputpath": os.path.join("."),
        "saveonexit": "ask",
        "outputname": "",
        "startinventoryarray": {}
    }

    if sys.platform.lower().find("windows"):
        settings["enemizercli"] += ".exe"

    # read saved settings file if it exists and set these
    settings_path = os.path.join(".", "resources", "user", "settings.json")
    if os.path.exists(settings_path):
        with(open(settings_path)) as json_file:
            data = json.load(json_file)
            for k, v in data.items():
                settings[k] = v
    return settings

# Priority fallback is:
#  1: CLI
#  2: Settings file
#  3: Canned defaults
def get_args_priority(settings_args, gui_args, cli_args):
    args = {}
    args["settings"] = parse_settings() if settings_args is None else settings_args
    args["gui"] = gui_args
    args["cli"] = cli_args

    args["load"] = args["settings"]
    if args["gui"] is not None:
        for k in args["gui"]:
            if k not in args["load"] or args["load"][k] != args["gui"]:
                args["load"][k] = args["gui"][k]

    if args["cli"] is None:
        args["cli"] = {}
        cli = vars(parse_cli(None))
        for k, v in cli.items():
            if isinstance(v, dict) and 1 in v:
                args["cli"][k] = v[1]
            else:
                args["cli"][k] = v
        args["cli"] = argparse.Namespace(**args["cli"])

    cli = vars(args["cli"])
    for k in vars(args["cli"]):
        load_doesnt_have_key = k not in args["load"]
        cli_val = cli[k]
        if isinstance(cli_val,dict) and 1 in cli_val:
            cli_val = cli_val[1]
        different_val = (k in args["load"] and k in cli) and (str(args["load"][k]) != str(cli_val))
        cli_has_empty_dict = k in cli and isinstance(cli_val, dict) and len(cli_val) == 0
        if load_doesnt_have_key or different_val:
            if not cli_has_empty_dict:
                args["load"][k] = cli_val

    newArgs = {}
    for key in [ "settings", "gui", "cli", "load" ]:
        if args[key]:
            if isinstance(args[key],dict):
                newArgs[key] = argparse.Namespace(**args[key])
            else:
                newArgs[key] = args[key]

        else:
            newArgs[key] = args[key]
        newArgs[key] = update_deprecated_args(newArgs[key])

    args = newArgs

    return args
