# ###################################################
# Copyright (C) 2008-2013 The Unknown Horizons Team
# team@unknown-horizons.org
# This file is part of Unknown Horizons.
#
# Unknown Horizons is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# ###################################################

from fife import fife
import logging
import functools

from horizons.gui.tabs.tabinterface import TabInterface
from horizons.extscheduler import ExtScheduler
from horizons.command.uioptions import SetTradeSlot, ClearTradeSlot
from horizons.gui.widgets.tradehistoryitem import TradeHistoryItem
from horizons.gui.util import load_uh_widget, get_res_icon_path, create_resource_selection_dialog
from horizons.util.python.callback import Callback
from horizons.util.worldobject import WorldObject
from horizons.component.tradepostcomponent import TradePostComponent
from horizons.constants import TRADER

class BuySellTab(TabInterface):
	"""
	Allows players to tell settlements which resources to buy or sell by adding
	slots in either buy or sell mode and introducing a limit per such slot.
	Also contains the trade history.
	"""
	log = logging.getLogger("gui")

	buy_button_path = "content/gui/images/tabwidget/ship_to_warehouse.png"
	buy_hover_button_path = "content/gui/images/tabwidget/buysell_toggle.png"
	sell_button_path = "content/gui/images/tabwidget/warehouse_to_ship.png"
	sell_hover_button_path = "content/gui/images/tabwidget/buysell_toggle.png"

	dummy_icon_path = "icons/resources/none_gray"

	def __init__(self, instance, widget='buysellmenu.xml',
	             icon_path='icons/tabwidget/warehouse/buysell'):
		"""Set up the GUI and game logic for the buyselltab."""
		super(BuySellTab, self).__init__(widget=widget, icon_path=icon_path)
		self.inited = False # prevents execution of commands during init
		# this makes sharing code easier
		self.session = instance.session
		self.trade_post = instance.settlement.get_component(TradePostComponent)
		assert isinstance(self.trade_post, TradePostComponent)
		# don't access instance beyond this point, only components
		self.init_values()

		# add the buy/sell slot widgets
		self.slot_widgets = {}
		self.resources = None # Placeholder for resource gui
		self.add_slots(len(self.trade_post.slots))

		for slot_id in xrange(len(self.trade_post.slots)):
			if self.trade_post.slots[slot_id] is not None:
				trade_slot_info = self.trade_post.slots[slot_id]
				self.slot_widgets[slot_id].action = 'sell' if trade_slot_info.selling else 'buy'
				self.add_resource(trade_slot_info.resource_id, slot_id, trade_slot_info.limit)

				if trade_slot_info.selling:
					self._show_sell(self.slot_widgets[slot_id])
				else:
					self._show_buy(self.slot_widgets[slot_id])

		# init the trade history
		self.trade_history = self.widget.findChild(name='trade_history')
		self.trade_history_widget_cache = {} # {(tick, player_id, resource_id, amount, gold): widget, ...}

		self.hide()
		self.helptext = _("Trade")
		self.inited = True

	def hide(self):
		"""Hide the tab and all widgets we may have added at runtime."""
		ExtScheduler().rem_all_classinst_calls(self)
		self.widget.hide()
		if self.resources is not None:
			self.resources.hide()

	def show(self):
		"""Display the tab's content, start the refresher."""
		self.widget.show()
		self.session.ingame_gui.minimap_to_front()
		self.refresh()
		ExtScheduler().add_new_object(self.refresh, self, run_in=0.4, loops=-1)

	def is_visible(self):
		# this tab sometimes is made up an extra widget, so it must also be considered
		# when checking for visibility
		return super(BuySellTab, self).is_visible() or \
		       (self.resources is not None and self.resources.isVisible())

	def _refresh_trade_history(self):
		self.trade_history.removeAllChildren()
		unused_rows = set(self.trade_history_widget_cache.keys())

		settlement_trade_history = self.trade_post.trade_history
		total_entries = len(settlement_trade_history)
		for i in xrange(min(4, total_entries)):
			row = settlement_trade_history[total_entries - i - 1]
			player = WorldObject.get_object_by_id(row[1])
			if row not in self.trade_history_widget_cache:
				self.trade_history_widget_cache[row] = TradeHistoryItem(player, row[2], row[3], row[4])
			widget = self.trade_history_widget_cache[row]
			self.trade_history.addChild(widget)
			unused_rows.discard(row)
		self.trade_history.adaptLayout()

		for row in unused_rows:
			del self.trade_history_widget_cache[row]

	def refresh(self):
		self._refresh_trade_history()
		# TODO: We don't refresh. Ticket #970
		if not self.trade_post.buy_list and not self.trade_post.sell_list:
			self._set_hint(_("Click on one of the resource slots to add a trade offer."))

	def add_slots(self, amount):
		"""
		Add `amount` slot widgets to the buysellmenu.
		@param amount: number of slot widgets that are to be added.
		"""
		content = self.widget.findChild(name="content")
		for i in xrange(amount):
			slot = load_uh_widget('trade_single_slot.xml')
			self.slot_widgets[i] = slot
			slot.id = i
			slot.action = 'buy'
			slot.res = None
			slot.name = "slot_%d" % i
			slot.findChild(name='button').capture(self.handle_click, event_name='mouseClicked')
			slot.findChild(name='button').path = self.dummy_icon_path
			slider = slot.findChild(name="slider")
			slider.scale_start = 0.0
			slider.scale_end = float(self.trade_post.get_inventory().limit)
			# Set scale according to the settlement inventory size
			slot.findChild(name="buysell").capture(Callback(self.toggle_buysell, i))
			fillbar = slot.findChild(name="fillbar")
			# hide fillbar by setting position
			icon = slot.findChild(name="icon")
			fillbar.position = (icon.width - fillbar.width - 1, icon.height)
			content.addChild(slot)
		self.widget.adaptLayout()


	def add_resource(self, resource_id, slot_id, value=None):
		"""
		Adds a resource to the specified slot
		@param resource_id: int - resource id
		@param slot_id: int - slot number of the slot that is to be set
		"""
		self.log.debug("BuySellTab add_resource() resid: %s; slot_id %s; value: %s", resource_id, slot_id, value)

		keep_hint = False
		if self.resources is not None: # Hide resource menu
			self.resources.hide()
			self.show()
			if resource_id != 0: # new res
				self._set_hint( _("Set to buy or sell by clicking on that label, then adjust the amount via the slider to the right.") )
			else:
				self._set_hint( u"" )
			keep_hint = True
		slot = self.slot_widgets[slot_id]
		slider = slot.findChild(name="slider")

		if value is None: # use current slider value if player provided no input
			value = int(slider.value)
		else: # set slider to value entered by the player
			slider.value = float(value)

		if slot.action is "sell":
			if slot.res is not None: # slot has been in use before, delete old value
				self.clear_slot(slot_id)
			if resource_id != 0:
				self.set_slot_info(slot.id, resource_id, True, value)
		elif slot.action is "buy":
			if slot.res is not None: # slot has been in use before, delete old value
				self.clear_slot(slot_id)
			if resource_id != 0:
				self.set_slot_info(slot.id, resource_id, False, value)

		button = slot.findChild(name="button")
		fillbar = slot.findChild(name="fillbar")
		# reset slot value for new res
		if resource_id == 0:
			button.path = self.dummy_icon_path
			button.helptext = u""
			slot.findChild(name="amount").text = u""
			slot.findChild(name="slider").value = 0.0
			slot.res = None
			slider.capture(None)
			# hide fillbar by setting position
			icon = slot.findChild(name="icon")
			fillbar.position = (icon.width - fillbar.width - 1, icon.height)
			button = slot.findChild(name="buysell")
			button.up_image = None
			button.hover_image = None
		else:
			icon = get_res_icon_path(resource_id)
			icon_disabled = get_res_icon_path(resource_id, greyscale=True)
			button.up_image = icon
			button.down_image = icon
			button.hover_image = icon_disabled
			button.helptext = self.session.db.get_res_name(resource_id)
			slot.res = resource_id
			# use some python magic to assign a res attribute to the slot to
			# save which resource_id it stores
			slider.capture(Callback(self.slider_adjust, resource_id, slot.id))
			slot.findChild(name="amount").text = u"{amount:-5d}t".format(amount=value)
			icon = slot.findChild(name="icon")
			inventory = self.trade_post.get_inventory()
			filled = (100 * inventory[resource_id]) // inventory.get_limit(resource_id)
			fillbar.position = (icon.width - fillbar.width - 1,
			                    icon.height - int(icon.height*filled))
			# reuse code from toggle to finish setup (must switch state before, it will reset it)
			slot.action = "sell" if slot.action is "buy" else "buy"
			self.toggle_buysell(slot_id, keep_hint=keep_hint)
		slot.adaptLayout()

	def toggle_buysell(self, slot_id, keep_hint=False):
		"""Switches modes of individual resource slots between 'buy' and 'sell'."""
		slot_widget = self.slot_widgets[slot_id]
		limit = int(slot_widget.findChild(name="slider").value)
		if slot_widget.action is "buy":
			# setting to sell
			self._show_sell(slot_widget)
			slot_widget.action = "sell"
		elif slot_widget.action is "sell":
			# setting to buy
			self._show_buy(slot_widget)
			slot_widget.action = "buy"

		if slot_widget.res is not None:
			selling = slot_widget.action == "sell"
			self.set_slot_info(slot_widget.id, slot_widget.res, selling, limit)

		if not keep_hint:
			self._update_hint(slot_id)

	def set_slot_info(self, slot_id, resource_id, selling, limit):
		assert resource_id is not None
		self.log.debug("BuySellTab: setting slot %d to resource %d, selling=%s, limit %d", slot_id, resource_id, selling, limit)
		self.slot_widgets[slot_id].action = "sell" if selling else "buy"
		if self.inited:
			SetTradeSlot(self.trade_post, slot_id, resource_id, selling, limit).execute(self.session)

	def clear_slot(self, slot_id):
		self.log.debug("BuySellTab: Removing resource in slot %d", slot_id)
		if self.inited:
			ClearTradeSlot(self.trade_post, slot_id).execute(self.session)

	def slider_adjust(self, resource_id, slot_id):
		"""Couples the displayed limit of this slot to the slider position."""
		slider = self.slot_widgets[slot_id].findChild(name="slider")
		limit = int(slider.value)
		self.set_slot_info(slot_id, resource_id, self.slot_widgets[slot_id].action == "sell", limit)
		self.slot_widgets[slot_id].findChild(name="amount").text = u"{amount:-5d}t".format(amount=limit)
		self.slot_widgets[slot_id].adaptLayout()
		self._update_hint(slot_id)

	def handle_click(self, widget, event):
		"""Handle clicks on resource slots. Left: change resource; Right: empty slot."""
		if event.getButton() == fife.MouseEvent.LEFT:
			self.show_resource_menu(widget.parent.id)
			self.session.ingame_gui.minimap_to_front()
		elif event.getButton() == fife.MouseEvent.RIGHT:
			# remove the buy/sell offer
			self.add_resource(0, widget.parent.id)

	def show_resource_menu(self, slot_id):
		"""
		Displays a menu where players can choose which resource to add in the
		selected slot. Available resources are all possible resources and a
		'None' resource which allows to delete slot actions.
		The resources are ordered by their res_id.
		"""
		# create dlg
		buy_list = self.trade_post.buy_list
		sell_list = self.trade_post.sell_list

		res_filter = lambda res_id : res_id not in buy_list and res_id not in sell_list
		on_click = functools.partial(self.add_resource, slot_id=slot_id)
		inventory = self.trade_post.get_inventory()

		self.resources = create_resource_selection_dialog(on_click, inventory,
		                                                  self.session.db,
		                                                  res_filter=res_filter)

		self.resources.position = self.widget.position
		self.hide() # hides tab that invoked the selection widget
		self.session.ingame_gui.minimap_to_front()

		self.resources.show() # show selection widget, still display old tab icons


	def _update_hint(self, slot_id):
		"""Sets default hint for last updated slot"""
		slot_widget = self.slot_widgets[slot_id]
		limit = int(slot_widget.findChild(name="slider").value)
		action = slot_widget.action
		price = self.session.db.get_res_value(slot_widget.res)
		if action == "buy":
			hint = _("Will buy {resource_name} for {price} gold/t whenever less than {limit}t are in stock.")
			price *= TRADER.PRICE_MODIFIER_SELL
		elif action == "sell":
			hint = _("Will sell {resource_name} for {price} gold/t whenever more than {limit}t are available.")
			price *= TRADER.PRICE_MODIFIER_BUY

		hint = hint.format(limit=unicode(limit),
		                   resource_name=self.session.db.get_res_name(slot_widget.res),
		                   price=int(price))
		# same price rounding as in tradepostcomponent
		self._set_hint(hint)

	def _set_hint(self, text):
		lbl = self.widget.findChild(name="hint_label")
		lbl.text = text
		lbl.adaptLayout()

	def _show_buy(self, slot):
		"""Make slot show buy button. Purely visual change"""
		button = slot.findChild(name="buysell")
		button.up_image = self.buy_button_path
		button.hover_image = self.buy_hover_button_path
		button.helptext = _("Buying")

	def _show_sell(self, slot):
		"""Make slot show sell button. Purely visual change"""
		button = slot.findChild(name="buysell")
		button.up_image = self.sell_button_path
		button.hover_image = self.sell_hover_button_path
		button.helptext = _("Selling")
