import os
import subprocess
from lutris.runners.runner import Runner
from lutris.util.display import get_current_resolution
from lutris.util.log import logger
from lutris.util.joypad import get_controller_mappings


class mednafen(Runner):
    human_name = "Mednafen"
    description = ("Multi-system emulator including NES, GB(A), PC Engine "
                   "support.")
    platforms = [
        'Nintendo Game Boy (Color)',
        'Nintendo Game Boy Advance',
        'Sega Game Gear',
        'Sega Genesis/Mega Drive',
        'Atari Lynx',
        'Sega Master System',
        'SNK Neo Geo Pocket (Color)',
        'Nintendo NES',
        'NEC PC Engine TurboGrafx-16',
        'NEC PC-FX',
        'Sony PlayStation',
        'Sega Saturn',
        'Nintendo SNES',
        'Bandai WonderSwan',
        'Nintendo Virtual Boy',
    ]
    machine_choices = (
        ('Game Boy (Color)', 'gb'),
        ('Game Boy Advance', 'gba'),
        ('Game Gear', 'gg'),
        ('Genesis/Mega Drive', 'md'),
        ('Lynx', 'lynx'),
        ('Master System', 'sms'),
        ('Neo Geo Pocket (Color)', 'gnp'),
        ('NES', 'nes'),
        ('PC Engine', 'pce'),
        ('PC-FX', 'pcfx'),
        ('PlayStation', 'psx'),
        ('Saturn', 'ss'),
        ('SNES', 'snes'),
        ('WonderSwan', 'wswan'),
        ('Virtual Boy', 'vb'),
    )
    runner_executable = 'mednafen/bin/mednafen'
    game_options = [
        {
            "option": "main_file",
            "type": "file",
            "label": "ROM file",
            'help': ("The game data, commonly called a ROM image. \n"
                     "Mednafen supports GZIP and ZIP compressed ROMs.")
        },
        {
            "option": "machine",
            "type": "choice",
            "label": "Machine type",
            "choices": machine_choices,
            'help': ("The emulated machine.")
        }
    ]
    runner_options = [
        {
            "option": "fs",
            "type": "bool",
            "label": "Fullscreen",
            "default": False,
        },
        {
            "option": "stretch",
            "type": "choice",
            "label": "Aspect ratio",
            "choices": (
                ("Disabled", "0"),
                ("Stretched", "full"),
                ("Preserve aspect ratio", "aspect"),
                ("Integer scale", "aspect_int"),
                ("Multiple of 2 scale", "aspect_mult2"),
            ),
            "default": "0"
        },
        {
            "option": "scaler",
            "type": "choice",
            "label": "Video scaler",
            "choices": (
                ("none", "none"),
                ("hq2x", "hq2x"),
                ("hq3x", "hq3x"),
                ("hq4x", "hq4x"),
                ("scale2x", "scale2x"),
                ("scale3x", "scale3x"),
                ("scale4x", "scale4x"),
                ("2xsai", "2xsai"),
                ("super2xsai", "super2xsai"),
                ("supereagle", "supereagle"),
                ("nn2x", "nn2x"),
                ("nn3x", "nn3x"),
                ("nn4x", "nn4x"),
                ("nny2x", "nny2x"),
                ("nny3x", "nny3x"),
                ("nny4x", "nny4x"),
            ),
            "default": "hq4x",
        },
        {
            "option": "dont_map_controllers",
            "type": "bool",
            "label": "Use default Mednafen controller configuration",
            "default": False,
        }

    ]

    def get_platform(self):
        machine = self.game_config.get('machine')
        if machine:
            for index, choice in enumerate(self.machine_choices):
                if choice[1] == machine:
                    return self.platforms[index]
        return ''

    def find_joysticks(self):
        """ Detect connected joysticks and return their ids """
        joy_ids = []
        if not self.is_installed:
            return []
        output = subprocess.Popen([self.get_executable(), "dummy"],
                                  stdout=subprocess.PIPE,
                                  universal_newlines=True).communicate()[0]
        ouput = output.split("\n")
        found = False
        joy_list = []
        for line in ouput:
            if found and "Joystick" in line:
                joy_list.append(line)
            else:
                found = False
            if "Initializing joysticks" in line:
                found = True

        for joy in joy_list:
            index = joy.find("Unique ID:")
            joy_id = joy[index + 11:]
            logger.debug('Joystick found id %s ' % joy_id)
            joy_ids.append(joy_id)
        return joy_ids

    def set_joystick_controls(self, joy_ids, machine):
        """ Setup joystick mappings per machine """

        # Get the controller mappings
        controller_mappings = get_controller_mappings()
        if not controller_mappings:
            logger.warning("No controller detected for joysticks %s.", joy_ids)
            return []

        # TODO currently only supports the first controller. Add support for
        # other controllers.
        mapping = controller_mappings[0][1]

        # Construct a dictionnary of button codes to parse to mendafen
        map_code = {
            'a': '', 'b': '', 'c': '',
            'x': '', 'y': '', 'z': '',
            'back': '', 'start': '',
            'leftshoulder': '', 'rightshoulder': '',
            'lefttrigger': '', 'righttrigger': '',
            'leftstick': '', 'rightstick': '',
            'select': '',
            'shoulder_l': '', 'shoulder_r': '',
            'i': '', 'ii': '', 'iii': '', 'iv': '', 'v': '', 'vi': '', 'run': '',
            'ls': '', 'rs': '', 'fire1': '', 'fire2': '', 'option_1': '', 'option_2': '',
            'cross': '', 'circle': '', 'square': '', 'triangle': '',
            'r1': '', 'r2': '', 'l1': '', 'l2': '', 'option': '', 'l': '', 'r': '',
            'right-x': '', 'right-y': '', 'left-x': '', 'left-y': '',
            'up-x': '', 'up-y': '', 'down-x': '', 'down-y': '', 'up-l': '',
            'up-r': '', 'down-l': '', 'down-r': '', 'left-l': '', 'left-r': '',
            'right-l': '', 'right-r': '',
            'lstick_up': '0000c001',
            'lstick_down': '00008001',
            'lstick_right': '00008000',
            'lstick_left': '0000c000',
            'rstick_up': '0000c003',
            'rstick_down': '00008003',
            'rstick_left': '0000c002',
            'rstick_right': '00008002',
            'dpup': '0000c005',
            'dpdown': '00008005',
            'dpleft': '0000c004',
            'dpright': '00008004'
        }

        # Insert the button mapping number into the map_codes
        for button in mapping.keys:
            bttn_id = mapping.keys[button]
            if bttn_id[0] == 'b':  # it's a button
                map_code[button] = '000000' + bttn_id[1:].zfill(2)

        # Duplicate button names that are emulated in mednanfen
        map_code['up'] = map_code['dpup']       #
        map_code['down'] = map_code['dpdown']   #
        map_code['left'] = map_code['dpleft']   # Multiple systems
        map_code['right'] = map_code['dpright']
        map_code['select'] = map_code['back']   #
        map_code['shoulder_r'] = map_code['rightshoulder']  # GBA
        map_code['shoulder_l'] = map_code['leftshoulder']  #
        map_code['i'] = map_code['b']              #
        map_code['ii'] = map_code['a']             #
        map_code['iii'] = map_code['leftshoulder']
        map_code['iv'] = map_code['y']             # PCEngine and PCFX
        map_code['v'] = map_code['x']              #
        map_code['vi'] = map_code['rightshoulder']
        map_code['run'] = map_code['start']        #
        map_code['ls'] = map_code['leftshoulder']    #
        map_code['rs'] = map_code['rightshoulder']   # Saturn
        map_code['c'] = map_code['righttrigger']     #
        map_code['z'] = map_code['lefttrigger']      #
        map_code['fire1'] = map_code['a']    # Master System
        map_code['fire2'] = map_code['b']    #
        map_code['option_1'] = map_code['x']   # Lynx
        map_code['option_2'] = map_code['y']   #
        map_code['r1'] = map_code['rightshoulder']  #
        map_code['r2'] = map_code['righttrigger']   #
        map_code['l1'] = map_code['leftshoulder']   #
        map_code['l2'] = map_code['lefttrigger']    # PlayStation
        map_code['cross'] = map_code['a']           #
        map_code['circle'] = map_code['b']          #
        map_code['square'] = map_code['x']          #
        map_code['triangle'] = map_code['y']        #
        map_code['option'] = map_code['select']  # NeoGeo pocket
        map_code['l'] = map_code['leftshoulder']   # SNES
        map_code['r'] = map_code['rightshoulder']  #
        map_code['right-x'] = map_code['dpright']      #
        map_code['left-x'] = map_code['dpleft']        #
        map_code['up-x'] = map_code['dpup']            #
        map_code['down-x'] = map_code['dpdown']        # Wonder Swan
        map_code['right-y'] = map_code['lstick_right']
        map_code['left-y'] = map_code['lstick_left']   #
        map_code['up-y'] = map_code['lstick_up']       #
        map_code['down-y'] = map_code['lstick_down']   #
        map_code['up-l'] = map_code['dpup']              #
        map_code['down-l'] = map_code['dpdown']          #
        map_code['left-l'] = map_code['dpleft']          #
        map_code['right-l'] = map_code['dpright']        #
        map_code['up-r'] = map_code['rstick_up']         #
        map_code['down-r'] = map_code['rstick_down']     # Virtual boy
        map_code['left-r'] = map_code['rstick_left']     #
        map_code['right-r'] = map_code['rstick_right']   #
        map_code['lt'] = map_code['leftshoulder']        #
        map_code['rt'] = map_code['rightshoulder']       #

        # Define which buttons to use for each machine
        layout = {
            'nes': ['a', 'b', 'start', 'select', 'up', 'down', 'left', 'right'],
            'gb': ['a', 'b', 'start', 'select', 'up', 'down', 'left', 'right'],
            'gba': ['a', 'b', 'shoulder_r', 'shoulder_l', 'start', 'select',
                    'up', 'down', 'left', 'right'],
            'pce': ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'run', 'select', 'up', 'down',
                    'left', 'right'],
            'ss': ['a', 'b', 'c', 'x', 'y', 'z', 'ls', 'rs', 'start', 'up', 'down',
                   'left', 'right'],
            'gg': ['button1', 'button2', 'start', 'up', 'down', 'left', 'right'],
            'md': ['a', 'b', 'c', 'x', 'y', 'z', 'start', 'up', 'down', 'left',
                   'right'],
            'sms': ['fire1', 'fire2', 'up', 'down', 'left', 'right'],
            'lynx': ['a', 'b', 'option_1', 'option_2', 'up', 'down', 'left',
                     'right'],
            'psx': ['cross', 'circle', 'square', 'triangle', 'l1', 'l2', 'r1', 'r2',
                    'start', 'select', 'lstick_up', 'lstick_down', 'lstick_right',
                    'lstick_left', 'rstick_up', 'rstick_down', 'rstick_left',
                    'rstick_right', 'up', 'down', 'left', 'right'],
            'pcfx': ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'run', 'select', 'up', 'down',
                     'left', 'right'],
            'ngp': ['a', 'b', 'option', 'up', 'down', 'left', 'right'],
            'snes': ['a', 'b', 'x', 'y', 'l', 'r', 'start', 'select', 'up', 'down',
                     'left', 'right'],
            'wswan': ['a', 'b', 'right-x', 'right-y', 'left-x', 'left-y', 'up-x',
                      'up-y', 'down-x', 'down-y', 'start'],
            'vb': ['up-l', 'down-l', 'left-l', 'right-l', 'up-r', 'down-r',
                   'left-r', 'right-r', 'a', 'b', 'lt', 'rt']
        }
        # Select a the gamepad type
        controls = []
        if machine in ['gg', 'lynx', 'wswan', 'gb', 'gba', 'vb']:
            gamepad = 'builtin.gamepad'
        elif machine in ['md']:
            gamepad = 'port1.gamepad6'
            controls.append('-md.input.port1')
            controls.append('gamepad6')
        elif machine in ['psx']:
            gamepad = 'port1.dualshock'
            controls.append('-psx.input.port1')
            controls.append('dualshock')
        else:
            gamepad = 'port1.gamepad'

        # Construct the controlls options
        for button in layout[machine]:
            controls.append("-{}.input.{}.{}".format(machine, gamepad, button))
            controls.append("joystick {} {}".format(joy_ids[0], map_code[button]))
        return controls

    def play(self):
        """Runs the game"""
        rom = self.game_config.get('main_file') or ''
        machine = self.game_config.get('machine') or ''

        fullscreen = self.runner_config.get("fs") or "0"
        if fullscreen is True:
            fullscreen = "1"
        elif fullscreen is False:
            fullscreen = "0"

        stretch = self.runner_config.get('stretch') or "0"
        scaler = self.runner_config.get('scaler') or "hq4x"

        resolution = get_current_resolution()
        (resolutionx, resolutiony) = resolution.split("x")
        xres = str(resolutionx)
        yres = str(resolutiony)
        options = ["-fs", fullscreen,
                   "-" + machine + ".xres", xres,
                   "-" + machine + ".yres", yres,
                   "-" + machine + ".stretch", stretch,
                   "-" + machine + ".special", scaler,
                   "-" + machine + ".videoip", "1"]
        joy_ids = self.find_joysticks()
        dont_map_controllers = self.runner_config.get('dont_map_controllers')
        if (len(joy_ids) > 0) and not dont_map_controllers:
            controls = self.set_joystick_controls(joy_ids, machine)
            for control in controls:
                options.append(control)

        if not os.path.exists(rom):
            return {'error': 'FILE_NOT_FOUND', 'file': rom}

        command = [self.get_executable()]
        for option in options:
            command.append(option)
        command.append(rom)
        return {'command': command}
