# Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt

from __future__ import unicode_literals

import frappe, re, frappe.utils
from frappe.desk.notifications import get_notifications
from frappe import _

@frappe.whitelist()
def get_bot_reply(question):
	return BotReply().get_reply(question)

class BotParser(object):
	'''Base class for bot parser'''
	def __init__(self, reply, query):
		self.query = query
		self.reply = reply
		self.tables = reply.tables
		self.doctype_names = reply.doctype_names

	def has(self, *words):
		'''return True if any of the words is present int the query'''
		for word in words:
			if re.search(r'\b{0}\b'.format(word), self.query):
				return True

	def startswith(self, *words):
		'''return True if the query starts with any of the given words'''
		for w in words:
			if self.query.startswith(w):
				return True

	def strip_words(self, query, *words):
		'''Remove the given words from the query'''
		for word in words:
			query = re.sub(r'\b{0}\b'.format(word), '', query)

		return query.strip()

	def format_list(self, data):
		'''Format list as markdown'''
		return _('I found these: ') + ', '.join([' [{title}](#Form/{doctype}/{name})'.format(
			title = d.title or d.name,
			doctype=self.get_doctype(),
			name=d.name) for d in data])

	def get_doctype(self):
		'''returns the doctype name from self.tables'''
		return self.doctype_names[self.tables[0]]

class ShowNotificationBot(BotParser):
	'''Show open notifications'''
	def get_reply(self):
		if self.has("whatsup", "what's up", "wassup", "whats up", 'notifications', 'open tasks'):
			n = get_notifications()
			open_items = sorted(n.get('open_count_doctype').items())

			if open_items:
				return ("Following items need your attention:\n\n"
					+ "\n\n".join(["{0} [{1}](#List/{1})".format(d[1], d[0])
						for d in open_items if d[1] > 0]))
			else:
				return 'Take it easy, nothing urgent needs your attention'

class GetOpenListBot(BotParser):
	'''Get list of open items'''
	def get_reply(self):
		if self.startswith('open', 'show open', 'list open', 'get open'):
			if self.tables:
				doctype = self.get_doctype()
				from frappe.desk.notifications import get_notification_config
				filters = get_notification_config().get('for_doctype').get(doctype, None)
				if filters:
					if isinstance(filters, dict):
						data = frappe.get_list(doctype, filters=filters)
					else:
						data = [{'name':d[0], 'title':d[1]} for d in frappe.get_attr(filters)(as_list=True)]

					return ", ".join('[{title}](#Form/{doctype}/{name})'.format(doctype=doctype,
						name=d.get('name'), title=d.get('title') or d.get('name')) for d in data)
				else:
					return _("Can't identify open {0}. Try something else.").format(doctype)

class ListBot(BotParser):
	def get_reply(self):
		if self.query.endswith(' ' + _('list')) and self.startswith(_('list')):
			self.query = _('list') + ' ' + self.query.replace(' ' + _('list'), '')
		if self.startswith(_('list'), _('show')):
			like = None
			if ' ' + _('like') + ' ' in self.query:
				self.query, like = self.query.split(' ' + _('like') + ' ')

			self.tables = self.reply.identify_tables(self.query.split(None, 1)[1])
			if self.tables:
				doctype = self.get_doctype()
				meta = frappe.get_meta(doctype)
				fields = ['name']
				if meta.title_field:
					fields.append('`{0}` as title'.format(meta.title_field))

				filters = {}
				if like:
					filters={
						meta.title_field or 'name': ('like', '%' + like + '%')
					}
				return self.format_list(frappe.get_list(self.get_doctype(), fields=fields, filters=filters))

class CountBot(BotParser):
	def get_reply(self):
		if self.startswith('how many'):
			self.tables = self.reply.identify_tables(self.query.split(None, 1)[1])
			if self.tables:
				return str(frappe.db.sql('select count(*) from `tab{0}`'.format(self.get_doctype()))[0][0])

class FindBot(BotParser):
	def get_reply(self):
		if self.startswith('find', 'search'):
			query = self.query.split(None, 1)[1]

			if self.has('from'):
				text, table = query.split('from')

			if self.has('in'):
				text, table = query.split('in')

			if table:
				text = text.strip()
				self.tables = self.reply.identify_tables(table.strip())


				if self.tables:
					filters = {'name': ('like',  '%{0}%'.format(text))}
					or_filters = None

					title_field = frappe.get_meta(self.get_doctype()).title_field
					if title_field and title_field!='name':
						or_filters = {'title': ('like',  '%{0}%'.format(text))}

					data = frappe.get_list(self.get_doctype(),
						filters=filters, or_filters=or_filters)
					if data:
						return self.format_list(data)
					else:
						return _("Could not find {0} in {1}").format(text, self.get_doctype())

				else:
					self.out = _("Could not identify {0}").format(table)
			else:
				self.out = _("You can find things by asking 'find orange in customers'").format(table)

class BotReply(object):
	'''Build a reply for the bot by calling all parsers'''
	def __init__(self):
		self.tables = []

	def get_reply(self, query):
		self.query = query.lower()
		self.setup()
		self.pre_process()

		# basic replies
		if self.query.split()[0] in ("hello", "hi"):
			return _("Hello {0}").format(frappe.utils.get_fullname())

		if self.query == "help":
			return help_text.format(frappe.utils.get_fullname())

		# build using parsers
		replies = []
		for parser in frappe.get_hooks('bot_parsers'):
			reply = None
			try:
				reply = frappe.get_attr(parser)(self, query).get_reply()
			except frappe.PermissionError:
				reply = _("Oops, you are not allowed to know that")

			if reply:
				replies.append(reply)

		if replies:
			return '\n\n'.join(replies)

		if not reply:
			return _("Don't know, ask 'help'")

	def setup(self):
		self.setup_tables()
		self.identify_tables()

	def pre_process(self):
		if self.query.endswith("?"):
			self.query = self.query[:-1]

		if self.query in ("todo", "to do"):
			self.query = "open todo"

	def setup_tables(self):
		tables = frappe.get_all("DocType", {"istable": 0})
		self.all_tables = [d.name.lower() for d in tables]
		self.doctype_names = {d.name.lower():d.name for d in tables}

	def identify_tables(self, query=None):
		if not query:
			query = self.query
		self.tables = []
		for t in self.all_tables:
			if t in query or t[:-1] in query:
				self.tables.append(t)

		return self.tables



help_text = """Hello {0}, I am a K.I.S.S Bot, not AI, so be kind. I can try answering a few questions like,

- "todo": list my todos
- "show customers": list customers
- "show customers like giant": list customer containing giant
- "locate shirt": find where to find item "shirt"
- "open issues": find open issues, try "open sales orders"
- "how many users": count number of users
- "find asian in sales orders": find sales orders where name or title has "asian"

have fun!
"""
