class RandomMapWidget(object):
	"""Create a random map, influence map generation with multiple sliders."""

	map_sizes = [50, 100, 150, 200, 250]
	water_percents = [20, 30, 40, 50, 60, 70, 80]
	island_sizes = [30, 40, 50, 60, 70]
	island_size_deviations = [5, 10, 20, 30, 40]

	def __init__(self, windows, singleplayer_menu, aidata):
		self._windows = windows
		self._singleplayer_menu = singleplayer_menu
		self._aidata = aidata

		self._gui = load_uh_widget('sp_random.xml')
		self._map_parameters = {}  # stores the current values from the sliders
		self._game_settings = GameSettingsWidget()

		# Map preview
		self._last_map_parameters = None
		self._preview_process = None
		self._preview_output = None
		self._map_preview = None

	def end(self):
		if self._preview_process:
			self._preview_process.kill()
			self._preview_process = None
		ExtScheduler().rem_all_classinst_calls(self)

	def get_widget(self):
		return self._gui

	def act(self, player_name, player_color):
		self.end()

		map_file = generate_random_map(*self._get_map_parameters())

		options = StartGameOptions.create_start_map(map_file)
		options.set_human_data(player_name, player_color)
		options.ai_players = self._aidata.get_ai_players()
		options.trader_enabled = self._game_settings.free_trader
		options.pirate_enabled = self._game_settings.pirates
		options.disasters_enabled = self._game_settings.disasters
		options.natural_resource_multiplier = self._game_settings.natural_resource_multiplier
		horizons.main.start_singleplayer(options)

	def show(self):
		seed_string_field = self._gui.findChild(name='seed_string_field')
		seed_string_field.capture(self._on_random_parameter_changed)
		seed_string_field.text = generate_random_seed(seed_string_field.text)

		parameters = (
			('map_size', self.map_sizes, _('Map size:'), 'RandomMapSize'),
			('water_percent', self.water_percents, _('Water:'), 'RandomMapWaterPercent'),
			('max_island_size', self.island_sizes, _('Max island size:'), 'RandomMapMaxIslandSize'),
			('preferred_island_size', self.island_sizes, _('Preferred island size:'), 'RandomMapPreferredIslandSize'),
			('island_size_deviation', self.island_size_deviations, _('Island size deviation:'), 'RandomMapIslandSizeDeviation'),
		)

		for param, __, __, setting_name in parameters:
			self._map_parameters[param] = horizons.globals.fife.get_uh_setting(setting_name)

		def make_on_change(param, values, text, setting_name):
			# When a slider is changed, update the value displayed in the label, save the value
			# in the settings and store the value in self._map_parameters
			def on_change():
				slider = self._gui.findChild(name=param + '_slider')
				self._gui.findChild(name=param + '_lbl').text = text + u' ' + unicode(values[int(slider.value)])
				horizons.globals.fife.set_uh_setting(setting_name, slider.value)
				horizons.globals.fife.save_settings()
				self._on_random_parameter_changed()
				self._map_parameters[param] = values[int(slider.value)]
			return on_change

		for param, values, text, setting_name in parameters:
			slider = self._gui.findChild(name=param + '_slider')
			on_change = make_on_change(param, values, text, setting_name)
			slider.capture(on_change)
			slider.value = horizons.globals.fife.get_uh_setting(setting_name)
			on_change()

		self._gui.findChild(name='game_settings_box').addChild(self._game_settings.get_widget())
		self._game_settings.show()
		self._aidata.show()

	def _get_map_parameters(self):
		return (
			self._gui.findChild(name='seed_string_field').text,
			self._map_parameters['map_size'],
			self._map_parameters['water_percent'],
			self._map_parameters['max_island_size'],
			self._map_parameters['preferred_island_size'],
			self._map_parameters['island_size_deviation']
		)

	def _on_random_parameter_changed(self):
		self._update_map_preview()

	# Map preview

	def _on_preview_click(self, event, drag):
		seed_string_field = self._gui.findChild(name='seed_string_field')
		seed_string_field.text = generate_random_seed(seed_string_field.text)
		self._on_random_parameter_changed()

	def _update_map_preview(self):
		"""Start a new process to generate a map preview."""
		current_parameters = self._get_map_parameters()
		if self._last_map_parameters == current_parameters:
			# nothing changed, don't generate a new preview
			return

		self._last_map_parameters = current_parameters

		if self._preview_process:
			self._preview_process.kill() # process exists, therefore up is scheduled already

		# launch process in background to calculate minimap data
		minimap_icon = self._gui.findChild(name='map_preview_minimap')
		params = json.dumps(((minimap_icon.width, minimap_icon.height), current_parameters))

		args = [sys.executable, sys.argv[0], "--generate-minimap", params]
		# We're running UH in a new process, make sure fife is setup correctly
		if horizons.main.command_line_arguments.fife_path:
			args.extend(["--fife-path", horizons.main.command_line_arguments.fife_path])

		handle, self._preview_output = tempfile.mkstemp()
		os.close(handle)
		self._preview_process = subprocess.Popen(args=args, stdout=open(self._preview_output, "w"))
		self._set_map_preview_status(u"Generating preview…")

		ExtScheduler().add_new_object(self._poll_preview_process, self, 0.5)

	def _poll_preview_process(self):
		"""This will be called regularly to see if the process ended.

		If the process has not yet finished, schedule a new callback to this function.
		Otherwise use the data to update the minimap.
		"""
		if not self._preview_process:
			return

		self._preview_process.poll()

		if self._preview_process.returncode is None: # not finished
			ExtScheduler().add_new_object(self._poll_preview_process, self, 0.1)
			return
		elif self._preview_process.returncode != 0:
			self._preview_process = None
			self._set_map_preview_status(u"An unknown error occurred while generating the map preview")
			return

		with open(self._preview_output, 'r') as f:
			data = f.read()

		os.unlink(self._preview_output)
		self._preview_process = None

		if self._map_preview:
			self._map_preview.end()

		self._map_preview = Minimap(
			self._gui.findChild(name='map_preview_minimap'),
			session=None,
			view=None,
			world=None,
			targetrenderer=horizons.globals.fife.targetrenderer,
			imagemanager=horizons.globals.fife.imagemanager,
			cam_border=False,
			use_rotation=False,
			tooltip=_("Click to generate a different random map"),
			on_click=self._on_preview_click,
			preview=True)

		self._map_preview.draw_data(data)
		self._set_map_preview_status(u"")

	def _set_map_preview_status(self, text):
		self._gui.findChild(name="map_preview_status_label").text = text
class MapPreview(object):
	"""Semiprivate class dealing with the map preview icon"""
	def __init__(self, get_widget):
		"""
		@param get_widget: returns the current widget (self.current)
		"""
		self.minimap = None
		self.calc_proc = None # handle to background calculation process
		self.get_widget = get_widget

	def update_map(self, map_file):
		"""Direct map preview update.
		Only use for existing maps, it's too slow for random maps"""
		if self.minimap is not None:
			self.minimap.end()
		world = self._load_raw_world(map_file)
		self.minimap = Minimap(self._get_map_preview_icon(),
			                     session=None,
			                     view=None,
			                     world=world,
			                     targetrenderer=horizons.main.fife.targetrenderer,
			                     imagemanager=horizons.main.fife.imagemanager,
			                     cam_border=False,
			                     use_rotation=False,
			                     tooltip=None,
			                     on_click=None,
			                     preview=True)
		self.minimap.draw()

	def update_random_map(self, map_params, on_click):
		"""Called when a random map parameter has changed.
		@param map_params: _get_random_map() output
		@param on_click: handler for clicks"""
		def check_calc_process():
			# checks up on calc process (see below)
			if self.calc_proc is not None:
				state = self.calc_proc.poll()
				if state is None: # not finished
					ExtScheduler().add_new_object(check_calc_process, self, 0.1)
				elif state != 0:
					self._set_map_preview_status(u"An unknown error occured while generating the map preview")
				else: # done

					data = open(self.calc_proc.output_filename, "r").read()
					os.unlink(self.calc_proc.output_filename)
					self.calc_proc = None

					icon = self._get_map_preview_icon()
					if icon is None:
						return # dialog already gone

					tooltip = _("Click to generate a different random map")

					if self.minimap is not None:
						self.minimap.end()
					self.minimap = Minimap(icon,
																 session=None,
																 view=None,
																 world=None,
																 targetrenderer=horizons.main.fife.targetrenderer,
																 imagemanager=horizons.main.fife.imagemanager,
																 cam_border=False,
																 use_rotation=False,
																 tooltip=tooltip,
																 on_click=on_click,
																 preview=True)
					self.minimap.draw_data( data )
					icon.show()
					self._set_map_preview_status(u"")

		if self.calc_proc is not None:
			self.calc_proc.kill() # process exists, therefore up is scheduled already
		else:
			ExtScheduler().add_new_object(check_calc_process, self, 0.5)

		# launch process in background to calculate minimap data
		minimap_icon = self._get_map_preview_icon()

		params =  json.dumps(((minimap_icon.width, minimap_icon.height), map_params))

		args = (sys.executable, sys.argv[0], "--generate-minimap", params)
		handle, outfilename = tempfile.mkstemp()
		os.close(handle)
		self.calc_proc = subprocess.Popen(args=args,
								                      stdout=open(outfilename, "w"))
		self.calc_proc.output_filename = outfilename # attach extra info
		self._set_map_preview_status(u"Generating preview...")


	@classmethod
	def generate_minimap(cls, size, parameters):
		"""Called as subprocess, calculates minimap data and passes it via string via stdout"""
		# called as standalone basically, so init everything we need
		from horizons.main import _create_main_db
		from horizons.entities import Entities
		from horizons.ext.dummy import Dummy
		db = _create_main_db()
		Entities.load_grounds(db, load_now=False) # create all references
		map_file = SingleplayerMenu._generate_random_map( parameters )
		world = cls._load_raw_world(map_file)
		location = Rect.init_from_topleft_and_size_tuples( (0, 0), size)
		minimap = Minimap(location,
											session=None,
											view=None,
											world=world,
											targetrenderer=Dummy(),
											imagemanager=Dummy(),
											cam_border=False,
											use_rotation=False,
											preview=True)
		# communicate via stdout
		print minimap.dump_data()

	@classmethod
	def _load_raw_world(cls, map_file):
		WorldObject.reset()
		world = World(session=None)
		world.inited = True
		world.load_raw_map( SavegameAccessor( map_file ), preview=True )
		return world

	def _get_map_preview_icon(self):
		"""Returns pychan icon for map preview"""
		return self.get_widget().findChild(name="map_preview_minimap")

	def _set_map_preview_status(self, text):
		"""Sets small status label next to map preview"""
		wdg = self.get_widget().findChild(name="map_preview_status_label")
		if wdg: # might show next dialog already
			wdg.text = text
class RandomMapWidget:
    """Create a random map, influence map generation with multiple sliders."""
    def __init__(self, windows, singleplayer_menu, aidata):
        self._windows = windows
        self._singleplayer_menu = singleplayer_menu
        self._aidata = aidata

        self._gui = load_uh_widget('sp_random.xml')
        self._map_parameters = {}  # stores the current values from the sliders
        self._game_settings = GameSettingsWidget()

        # Map preview
        self._last_map_parameters = None
        self._preview_process = None
        self._preview_output = None
        self._map_preview = None

    def end(self):
        if self._preview_process:
            self._preview_process.kill()
            self._preview_process = None
        ExtScheduler().rem_all_classinst_calls(self)

    def get_widget(self):
        return self._gui

    def act(self, player_name, player_color):
        self.end()

        map_file = generate_random_map(*self._get_map_parameters())

        options = StartGameOptions.create_start_map(map_file)
        options.set_human_data(player_name, player_color)
        options.ai_players = self._aidata.get_ai_players()
        options.trader_enabled = self._game_settings.free_trader
        options.pirate_enabled = self._game_settings.pirates
        options.disasters_enabled = self._game_settings.disasters
        options.natural_resource_multiplier = self._game_settings.natural_resource_multiplier
        horizons.main.start_singleplayer(options)

    def show(self):
        seed_string_field = self._gui.findChild(name='seed_string_field')
        seed_string_field.capture(self._on_random_parameter_changed)
        seed_string_field.text = generate_random_seed(seed_string_field.text)

        parameters = (
            ('map_size', T('Map size:'), 'RandomMapSize'),
            ('water_percent', T('Water:'), 'RandomMapWaterPercent'),
            ('max_island_size', T('Max island size:'),
             'RandomMapMaxIslandSize'),
            ('preferred_island_size', T('Preferred island size:'),
             'RandomMapPreferredIslandSize'),
            ('island_size_deviation', T('Island size deviation:'),
             'RandomMapIslandSizeDeviation'),
        )

        for param, __, setting_name in parameters:
            self._map_parameters[param] = int(
                horizons.globals.fife.get_uh_setting(setting_name))

        def make_on_change(param, text, setting_name):
            # When a slider is changed, update the value displayed in the label, save the value
            # in the settings and store the value in self._map_parameters
            def on_change():
                slider = self._gui.findChild(name=param + '_slider')
                self._gui.findChild(
                    name=param +
                    '_lbl').text = text + ' ' + str(int(slider.value))
                horizons.globals.fife.set_uh_setting(setting_name,
                                                     slider.value)
                horizons.globals.fife.save_settings()
                self._map_parameters[param] = int(slider.value)
                self._on_random_parameter_changed()

            return on_change

        for param, text, setting_name in parameters:
            slider = self._gui.findChild(name=param + '_slider')
            on_change = make_on_change(param, text, setting_name)
            slider.capture(on_change)
            slider.value = horizons.globals.fife.get_uh_setting(setting_name)
            on_change()

        self._gui.findChild(name='game_settings_box').addChild(
            self._game_settings.get_widget())
        self._game_settings.show()
        self._aidata.show()

    def _get_map_parameters(self):
        return (self._gui.findChild(name='seed_string_field').text,
                self._map_parameters['map_size'],
                self._map_parameters['water_percent'],
                self._map_parameters['max_island_size'],
                self._map_parameters['preferred_island_size'],
                self._map_parameters['island_size_deviation'])

    def _on_random_parameter_changed(self):
        self._update_map_preview()

    # Map preview

    def _on_preview_click(self, event, drag):
        seed_string_field = self._gui.findChild(name='seed_string_field')
        seed_string_field.text = generate_random_seed(seed_string_field.text)
        self._on_random_parameter_changed()

    def _update_map_preview(self):
        """Start a new process to generate a map preview."""
        current_parameters = self._get_map_parameters()
        if self._last_map_parameters == current_parameters:
            # nothing changed, don't generate a new preview
            return

        self._last_map_parameters = current_parameters

        if self._preview_process:
            self._preview_process.kill(
            )  # process exists, therefore up is scheduled already

        # launch process in background to calculate minimap data
        minimap_icon = self._gui.findChild(name='map_preview_minimap')
        params = json.dumps(
            ((minimap_icon.width, minimap_icon.height), current_parameters))

        args = [sys.executable, sys.argv[0], "--generate-minimap", params]
        # We're running UH in a new process, make sure fife is setup correctly
        if horizons.main.command_line_arguments.fife_path:
            args.extend([
                "--fife-path", horizons.main.command_line_arguments.fife_path
            ])

        handle, self._preview_output = tempfile.mkstemp()
        os.close(handle)
        self._preview_process = subprocess.Popen(args=args,
                                                 stdout=open(
                                                     self._preview_output,
                                                     "w"))
        self._set_map_preview_status("Generating preview…")

        ExtScheduler().add_new_object(self._poll_preview_process, self, 0.5)

    def _poll_preview_process(self):
        """This will be called regularly to see if the process ended.

		If the process has not yet finished, schedule a new callback to this function.
		Otherwise use the data to update the minimap.
		"""
        if not self._preview_process:
            return

        self._preview_process.poll()

        if self._preview_process.returncode is None:  # not finished
            ExtScheduler().add_new_object(self._poll_preview_process, self,
                                          0.1)
            return
        elif self._preview_process.returncode != 0:
            self._preview_process = None
            self._set_map_preview_status(
                "An unknown error occurred while generating the map preview")
            return

        with open(self._preview_output, 'r') as f:
            data = f.read()
            # Sometimes the subprocess outputs more then the minimap data, e.g. debug
            # information. Since we just read from its stdout, parse out the data that
            # is relevant to us.
            data = re.findall(r'^DATA (\[\[.*\]\]) ENDDATA$', data,
                              re.MULTILINE)[0]
            data = json.loads(data)

        os.unlink(self._preview_output)
        self._preview_process = None

        if self._map_preview:
            self._map_preview.end()

        self._map_preview = Minimap(
            self._gui.findChild(name='map_preview_minimap'),
            session=None,
            view=None,
            world=None,
            targetrenderer=horizons.globals.fife.targetrenderer,
            imagemanager=horizons.globals.fife.imagemanager,
            cam_border=False,
            use_rotation=False,
            tooltip=T("Click to generate a different random map"),
            on_click=self._on_preview_click,
            preview=True)

        self._map_preview.draw_data(data)
        self._set_map_preview_status("")

    def _set_map_preview_status(self, text):
        self._gui.findChild(name="map_preview_status_label").text = text
class MapPreview(object):
    """Semiprivate class dealing with the map preview icon"""
    def __init__(self, get_widget):
        """
		@param get_widget: returns the current widget (self.current)
		"""
        self.minimap = None
        self.calc_proc = None  # handle to background calculation process
        self.get_widget = get_widget
        self._last_random_map_params = None

    def update_map(self, map_file):
        """Direct map preview update.
		Only use for existing maps, it's too slow for random maps"""
        if self.minimap is not None:
            self.minimap.end()
        world = self._load_raw_world(map_file)
        self.minimap = Minimap(
            self._get_map_preview_icon(),
            session=None,
            view=None,
            world=world,
            targetrenderer=horizons.globals.fife.targetrenderer,
            imagemanager=horizons.globals.fife.imagemanager,
            cam_border=False,
            use_rotation=False,
            tooltip=None,
            on_click=None,
            preview=True)
        self.minimap.draw()

    def update_random_map(self, map_params, on_click):
        """Called when a random map parameter has changed.
		@param map_params: _get_random_map() output
		@param on_click: handler for clicks"""
        if self._last_random_map_params == map_params:
            return  # we already display this, happens on spurious slider events such as hover
        self._last_random_map_params = map_params

        def check_calc_process():
            # checks up on calc process (see below)
            if self.calc_proc is not None:
                state = self.calc_proc.poll()
                if state is None:  # not finished
                    ExtScheduler().add_new_object(check_calc_process, self,
                                                  0.1)
                elif state != 0:
                    self._set_map_preview_status(
                        u"An unknown error occured while generating the map preview"
                    )
                else:  # done

                    data = open(self.calc_proc.output_filename, "r").read()
                    os.unlink(self.calc_proc.output_filename)
                    self.calc_proc = None

                    icon = self._get_map_preview_icon()
                    if icon is None:
                        return  # dialog already gone

                    tooltip = _("Click to generate a different random map")

                    if self.minimap is not None:
                        self.minimap.end()
                    self.minimap = Minimap(
                        icon,
                        session=None,
                        view=None,
                        world=None,
                        targetrenderer=horizons.globals.fife.targetrenderer,
                        imagemanager=horizons.globals.fife.imagemanager,
                        cam_border=False,
                        use_rotation=False,
                        tooltip=tooltip,
                        on_click=on_click,
                        preview=True)
                    self.minimap.draw_data(data)
                    icon.show()
                    self._set_map_preview_status(u"")

        if self.calc_proc is not None:
            self.calc_proc.kill(
            )  # process exists, therefore up is scheduled already
        else:
            ExtScheduler().add_new_object(check_calc_process, self, 0.5)

        # launch process in background to calculate minimap data
        minimap_icon = self._get_map_preview_icon()

        params = json.dumps(
            ((minimap_icon.width, minimap_icon.height), map_params))

        args = (sys.executable, sys.argv[0], "--generate-minimap", params)
        handle, outfilename = tempfile.mkstemp()
        os.close(handle)
        self.calc_proc = subprocess.Popen(args=args,
                                          stdout=open(outfilename, "w"))
        self.calc_proc.output_filename = outfilename  # attach extra info
        self._set_map_preview_status(u"Generating preview...")

    @classmethod
    def generate_minimap(cls, size, parameters):
        """Called as subprocess, calculates minimap data and passes it via string via stdout"""
        # called as standalone basically, so init everything we need
        from horizons.main import _create_main_db
        from horizons.entities import Entities
        from horizons.ext.dummy import Dummy
        db = _create_main_db()
        Entities.load_grounds(db, load_now=False)  # create all references
        map_file = SingleplayerMenu._generate_random_map(parameters)
        world = cls._load_raw_world(map_file)
        location = Rect.init_from_topleft_and_size_tuples((0, 0), size)
        minimap = Minimap(location,
                          session=None,
                          view=None,
                          world=world,
                          targetrenderer=Dummy(),
                          imagemanager=Dummy(),
                          cam_border=False,
                          use_rotation=False,
                          preview=True)
        # communicate via stdout
        print minimap.dump_data()

    @classmethod
    def _load_raw_world(cls, map_file):
        WorldObject.reset()
        world = World(session=None)
        world.inited = True
        world.load_raw_map(SavegameAccessor(map_file, True), preview=True)
        return world

    def _get_map_preview_icon(self):
        """Returns pychan icon for map preview"""
        return self.get_widget().findChild(name="map_preview_minimap")

    def _set_map_preview_status(self, text):
        """Sets small status label next to map preview"""
        wdg = self.get_widget().findChild(name="map_preview_status_label")
        if wdg:  # might show next dialog already
            wdg.text = text