import os
from fsbc.system import windows
from fsbc.task import current_task
from fsgs.input.mapper import InputMapper
from fsgs.runner import GameRunner


# noinspection PyAttributeOutsideInit
class MAMERunner(GameRunner):

    def init(self):
        self.mame_init()

    def mame_emulator_name(self):
        return "mame"

    def mame_init_input(self):
        """Override in inherited classes"""
        pass

    def mame_romset(self):
        """Override in inherited classes"""
        raise NotImplementedError()

    def init_input(self):
        self.mame_init_input()
        if windows:
            self.input_device_order = "DINPUT8"

    def prepare(self):
        self.configure()
        self.mame_prepare()

    def mame_init(self):
        # override in subclasses
        pass

    def mame_prepare(self):
        pass

    def configure(self):

        self.configure_romset()

        self.default_xml = ["""\ufeff<?xml version="1.0"?>
<!-- This file is autogenerated; comments and unknown tags will be stripped -->
<mameconfig version="10">
    <system name="default">\n"""]

        self.game_xml = ["""\ufeff<?xml version="1.0"?>
<!-- This file is autogenerated; comments and unknown tags will be stripped -->
<mameconfig version="10">
    <system name="{0}">\n""".format(self.romset)]

        self.add_arg("-skip_gameinfo")
        # state_dir = self.get_state_dir()
        state_dir = self.emulator_state_dir(self.mame_emulator_name())
        state_dir = state_dir + os.sep

        self.cwd_dir = self.create_temp_dir("mame-cwd")
        self.cfg_dir = self.create_temp_dir("mame-cfg")
        self.roms_dir = self.create_temp_dir("mame-roms")
        rom_path = self.roms_dir.path
        assert self.romset
        system_rom_path = os.path.join(rom_path, self.romset)
        os.makedirs(system_rom_path)

        for sha1, name in self.romset_files:
            file_uri = self.fsgs.file.find_by_sha1(sha1)
            current_task.set_progress("Preparing ROM {name}".format(name=name))
            input_stream = self.fsgs.file.open(file_uri)
            if input_stream is None:
                raise Exception("Cannot not find required ROM " + repr(name))
            path = os.path.join(system_rom_path, name)
            with open(path, "wb") as f:
                f.write(input_stream.read())

        # shutil.copy(self.get_game_file(),
        #             os.path.join(rom_path, self.romset + '.zip'))

        # MAME uses ; as path separator on all systems, apparently
        try:
            # self.args.extend(["-rompath", self.bios_dir()])
            rom_path = rom_path + ";" + self.mame_get_bios_dir()
        except:
            pass
        # rom_path = rom_path + ";" + os.path.dirname(self.get_game_file())

        # rom_path = rom_path + os.pathsep + os.path.dirname(
        #        self.get_game_file())
        self.add_arg("-rompath", rom_path)

        # copy initial nvram disk, if any, to nvram dir
        # p, e = os.path.splitext(self.get_game_file())
        # initram = p + ".nv"
        # if os.path.exists(initram):
        #    shutil.copy(initram, os.path.join(state_dir, self.romset + '.nv'))

        game_cfg_file = os.path.join(
            self.cfg_dir.path, "{romset}.cfg".format(romset=self.romset))
        self.add_arg("-cfg_directory", self.cfg_dir.path)
        self.add_arg("-nvram_directory", state_dir)
        # self.add_arg("-memcard_directory", state_dir)
        # self.add_arg("-hiscore_directory", state_dir)
        self.add_arg("-state_directory", state_dir)
        self.add_arg("-diff_directory", state_dir)

        self.add_arg("-snapshot_directory", self.screenshots_dir())
        self.add_arg("-snapname", "{0}-%i".format(self.screenshots_name()))

        # self.change_handler = GameChangeHandler(self.cfg_dir.path)
        # self.change_handler.init(
        #     os.path.join(self.get_state_dir(), "cfg"))

        self.configure_input()
        self.configure_video()

        self.game_xml.append('    </system>\n')
        self.game_xml.append('</mameconfig>\n')
        with open(game_cfg_file, 'wb') as f:
            f.write("".join(self.game_xml).encode("UTF-8"))
            print("")
            print("")
            print("".join(self.game_xml))
            print("")
            print("")

        self.default_xml.append('    </system>\n')
        self.default_xml.append('</mameconfig>\n')
        with open(os.path.join(self.cfg_dir.path, "default.cfg"), "wb") as f:
            f.write("".join(self.default_xml).encode("UTF-8"))
            print("")
            print("")
            print("".join(self.default_xml))
            print("")
            print("")

        if self.use_doubling():
            self.add_arg("-prescale", "2")

        if self.use_smoothing():
            self.add_arg("-filter")
        else:
            self.add_arg("-nofilter")

        # start mame/mess with UI keyboard keys enabled by default,
        # full keyboard can be activated with INS / Scroll-Lock
        # self.add_arg("-ui_active")

        self.mame_configure()

    def mame_configure(self):
        # override in subclasses
        pass

    def configure_romset(self):
        self.romset, self.romset_files = self.mame_romset()
        try:
            # convert from dict to list
            # noinspection PyUnresolvedReferences
            self.romset_files = list(self.romset_files.items())
        except AttributeError:
            # already a list
            pass

    def configure_input(self):
        # return
        # FIXME: ignoring input
        self.default_xml.append("        <input>\n")
        self.game_xml.append("        <input>\n")
        ports = {}
        # for i, input in enumerate(self.inputs):
        for i, port in enumerate(self.ports):
            input_mapping = self.mame_input_mapping(i)

            mapper = MAMEInputMapper(port, input_mapping)
            for key, value in mapper.items():
                print("---->", key, value)
                if isinstance(key, tuple):
                    key, type = key
                else:
                    type = 'standard'
                if 'type=' not in key:
                    key = 'type="{0}"'.format(key)
                key = key.replace("#", str(i + 1))
                # if '/' in key:
                #     key, tag = key.split('/')
                # else:
                #     tag = None
                # if ':' in key:
                #     key, type = key.split(':')
                # else:
                #     type = 'standard'
                if 'AD_STICK' in key: # and type == 'standard':
                    analog_axis = True
                else:
                    analog_axis = False
                if analog_axis and 'AXIS' in value:
                    value = value[:value.index('AXIS') + 4]
                    # remove increment / decrement type, set type
                    # to standard since this is an analog axis
                    type = 'standard'
                ports.setdefault(key, {}).setdefault(type, set()).add(value)
        for key, port in ports.items():
            # key, tag = key
            if 'tag=' in key:
                xml = self.game_xml
                # xml.append(
                #         '            <port tag="{tag}" '
                #         'type="{key}" mask="1" default="0">'
                #         '\n'.format(tag=tag, key=key))
                # xml.append(
                #         '            <port {key}>\n'.format(key=key))
            else:
                xml = self.default_xml
                # xml.append(
                #         '            <port type="{key}">\n'.format(key=key))
                # xml.append(
                #         '            <port {key}>\n'.format(key=key))
            xml.append('            <port {key}>\n'.format(key=key))
            for type, values in port.items():
                xml.append('                <newseq type="{type}">\n'.format(
                    type=type))
                xml.append('                    ')
                for i, value in enumerate(values):
                    if i > 0:
                        xml.append(' OR ')
                    xml.append(value)
                xml.append('\n                </newseq>\n')
            xml.append('            </port>\n')
        self.default_xml.append("        </input>\n")
        self.game_xml.append("        </input>\n")

    def configure_video(self):
        if windows and False:
            self.add_arg("-video", "d3d")
        else:
            self.add_arg("-video", "opengl")

        if self.use_fullscreen():
            self.add_arg("-nowindow")
        else:
            self.add_arg("-window")

        if self.use_stretching():
            self.add_arg("-nokeepaspect")
        else:
            self.add_arg("-keepaspect")

        self.game_xml.append('        <video>\n')
        self.game_xml.append('            <screen index="0" ')
        ox, oy, sx, sy = self.mame_offset_and_scale()
        self.game_xml.append('hstretch="{0:0.6f}" '.format(sx))
        self.game_xml.append('vstretch="{0:0.6f}" '.format(sy))
        self.game_xml.append('hoffset="{0:0.6f}" '.format(ox))
        self.game_xml.append('voffset="{0:0.6f}" '.format(oy))
        self.game_xml.append('/>\n')
        self.game_xml.append('        </video>\n')

        # effect = 'none'
        # filter_mapping = {
        #     'auto': 'aperture1x2rb',
        #     'rgb': 'aperture1x2rb',
        # }
        # for filter in self.context.config.filters:
        #     try:
        #         effect = filter_mapping[filter]
        #     except KeyError:
        #         continue
        #     break
        # self.args.extend(['-effect', effect])

        video_args = []
        if self.configure_vsync():
            video_override = "mame_video_options_vsync"
            video_args.append("-waitvsync")
            video_args.append("-syncrefresh")
            if windows and False:
                # no-throttle seems to enable turbo mode (no vsync throttling)
                # when used on Windows
                pass
            else:
                video_args.append("-nothrottle")
                pass
            # if self.get_game_refresh_rate():
            #     # should always be true since vsync was enabled...
            #     self.args.extend(
            #         ["-override_fps", str(self.get_game_refresh_rate())])
            if windows and False:
                video_args.append('-notriplebuffer')
        else:
            video_override = "mame_video_options"
            if windows and False:
                video_args.append('-triplebuffer')

        # print("mame_video_options_vsync", self.config[video_override])
        # raise Exception("gnit")

        if self.config[video_override]:
            for arg in self.config[video_override].split(" "):
                arg = arg.strip()
                if arg:
                    self.add_arg(arg)
        else:
            self.add_arg(*video_args)

    def mame_offset_and_scale(self):
        ox, oy, sx, sy = 0.0, 0.0, 1.0, 1.0
        viewport = self.config["viewport"]
        if viewport:
            src, dst = viewport.split("=")
            src = src.strip()
            dst = dst.strip()
            src_x, src_y, src_w, src_h = [int(v) for v in src.split(" ")]
            dst_x, dst_y, dst_w, dst_h = [int(v) for v in dst.split(" ")]
            sx = src_w / dst_w
            sy = src_h / dst_h
            ox = (((src_x + src_w / 2) - (dst_x + dst_w / 2)) / dst_w)
            oy = ((src_y + src_h / 2) - (dst_y + dst_h / 2)) / dst_h
        return ox, oy, sx, sy

    def mame_get_bios_dir(self):
        for dir in self.resources_dirs():
            bios_dir = os.path.join(dir, self.context.game.platform)
            if os.path.exists(bios_dir):
                return bios_dir
        raise Exception("Could not find bios dir")

    def run(self):
        self.add_arg(self.romset)
        # return self.start_emulator("fs-mame/mame")
        return self.start_emulator_from_plugin_resource("fs-mame")

    def finish(self):
        # if os.path.exists(os.path.join(self.cfg_dir.path, "default.cfg")):
        #     os.unlink(os.path.join(self.cfg_dir.path, "default.cfg"))
        # self.change_handler.update(
        #     os.path.join(self.get_state_dir(), "cfg"))
        pass


class MAMEInputMapper(InputMapper):

    def axis(self, axis, positive):
        axis_str = ["X", "Y", "Z", "RX", "RY", "RZ"][axis]
        if axis == 0:
            dir_str = "RIGHT" if positive else "LEFT"
        elif axis == 1:
            dir_str = "DOWN" if positive else "UP"
        else:
            # FIXME: Check this
            dir_str = "POS" if positive else "NEG"
        return "JOYCODE_%d_%sAXIS_%s_SWITCH" % (
            self.device.index + 1, axis_str, dir_str)

    def hat(self, hat, direction):
        if windows and False:
            dir_str = {
                'left': 'L',
                'right': 'R',
                'up': 'U',
                'down': 'D',
            }[direction]
            return "JOYCODE_{0}_HATSWITCH{1}".format(
                self.device.index + 1, dir_str)
        else:
            dir_str = {
                'left': 'LEFT',
                'right': 'RIGHT',
                'up': 'UP',
                'down': 'DOWN',
            }[direction]
            # in older MAME/MESS versions, hat 0 was specified here as 0,
            # but in recent versions, the first hat is called 1 in the config
            return "JOYCODE_{0}_HAT{1}{2}".format(
                self.device.index + 1, hat + 1, dir_str)

    def button(self, button):
        return "JOYCODE_{0}_BUTTON{1}".format(
            self.device.index + 1, button + 1)

    def key(self, key):
        return "KEYCODE_" + keycodes[key.sdl_name[5:].upper()]


from .keycodes import keycodes
