#! /usr/bin/env python3
# -*- coding: utf-8 -*-

#	Copyright 2012-2014, 2017, Marten de Vries
#	Copyright 2012, Milan Boers
#
#	This file is part of OpenTeacher.
#
#	OpenTeacher 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 3 of the License, or
#	(at your option) any later version.
#
#	OpenTeacher 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 OpenTeacher.  If not, see <http://www.gnu.org/licenses/>.

#ATTENTION: fixmes/todos in this module won't be shown in the overview,
#since the word 'fixme' is used multiple times for other reasons.

import pydle
from launchpadlib import launchpad
import re
import urllib.parse
import urllib.error
import urllib.request
import json
import os

class OpenTeacherBot(pydle.Client):
	factoids = {
		".downloads": "http://openteacher.org/en/download.html",
		".sf": "http://sourceforge.net/projects/openteacher",
		".stats": "http://sourceforge.net/projects/openteacher/files/stats",
		".lp": "https://launchpad.net/openteacher",
		".github": "https://github.com/openteacher",
		".blueprints": "https://blueprints.launchpad.net/openteacher",
		".code": "https://code.launchpad.net/openteacher",
		".bugs": "https://bugs.launchpad.net/openteacher",
		".answers": "https://answers.launchpad.net/openteacher",
		".translations": "https://translations.launchpad.net/openteacher",
		".website": "http://openteacher.org/",
		".about": "http://openteacher.org/en/about.html",
		".documentation": "http://openteacher.org/en/documentation.html",
		".codedocs": "https://web.openteacher.org/code-documentation/",
		".contribute": "http://openteacher.org/en/contribute.html",
		".forum": "http://forum.ubuntu-nl.org/etalage/openteacher-overhoorprogramma-voor-linux/new/#new",
		".ogd": "http://opengamedesign.sourceforge.net/",
		".ogd-lp": "https://launchpad.net/opengamedesigner",
		".ogd-sf": "http://sourceforge.net/projects/opengamedesign",
		".twitter": "https://twitter.com/openteacher",
		".facebook": "http://www.facebook.com/OpenTeacher",
		".openhub": "https://www.openhub.net/p/openteacher",
		".mailarchive": "https://lists.launchpad.net/openteachermaintainers/",
		".paste": "http://paste.ubuntu.com/",
		".etherpad": "https://public.etherpad-mozilla.org/",
		".jfl": "https://launchpad.net/justforlearning/",
		".ot-mobile": "https://web.openteacher.org/mobile/",
		".ot-test-coverage": "https://web.openteacher.org/builds/linux/coverage/",
		".priorities": "https://web.openteacher.org/code-documentation/priorities.html",
		".fixmes": "https://web.openteacher.org/code-documentation/fixmes.html",
		".modulemap": "https://web.openteacher.org/code-documentation/module_graph.svg",
		".peps": "http://www.python.org/dev/peps/",
		".api": "https://web.openteacher.org/services/",
		".profiling": "https://web.openteacher.org/builds/linux/profiling",
		".website-preview": "https://web.openteacher.org/builds/linux/website/en/",
		".developer-documentation": "https://web.openteacher.org/code-documentation/dev_docs/",
		".code-complexity": "https://web.openteacher.org/builds/linux/code-complexity.txt",
		".dependencies": "https://web.openteacher.org/code-documentation/dev_docs/dependencies.rst",
		".bitesize": "https://bugs.launchpad.net/openteacher/+bugs?field.tag=bitesize",
		".travis": "https://travis-ci.org/openteacher/openteacher-ci",
		".web": "https://web.openteacher.org/",
		".appveyor": "https://ci.appveyor.com/project/openteacher/openteacher-ci",
		".openteacher-ci": "https://github.com/openteacher/openteacher-ci",
		".builds": "https://web.openteacher.org/builds/",
	}
	factoids[".launchpad"] = factoids[".lp"]
	factoids[".gh"] = factoids[".github"]
	factoids[".download"] = factoids[".downloads"]
	factoids[".homepage"] = factoids[".website"]
	factoids[".codedocumentation"] = factoids[".codedocs"]
	factoids[".code-documentation"] = factoids[".codedocs"]
	factoids[".code-docs"] = factoids[".codedocs"]
	factoids[".forumtopic"] = factoids[".forum"]
	factoids[".mailinglist"] = factoids[".mailarchive"]
	factoids[".pastebin"] = factoids[".paste"]
	factoids[".openteacher-mobile"] = factoids[".ot-mobile"]
	factoids[".mobile"] = factoids[".ot-mobile"]
	factoids[".ot-coverage"] = factoids[".ot-test-coverage"]
	factoids[".coverage"] = factoids[".ot-test-coverage"]
	factoids[".fixme"] = factoids[".fixmes"]
	factoids[".todos"] = factoids[".fixmes"]
	factoids[".todo"] = factoids[".fixmes"]
	factoids[".priority"] = factoids[".priorities"]
	factoids[".module-map"] = factoids[".modulemap"]
	factoids[".modulegraph"] = factoids[".modulemap"]
	factoids[".module-graph"] = factoids[".modulemap"]
	factoids[".profile"] = factoids[".profiling"]
	factoids[".cProfile"] = factoids[".profiling"]
	factoids[".performance"] = factoids[".profiling"]
	factoids[".website-dev"] = factoids[".website-preview"]
	factoids[".dev-docs"] = factoids[".developer-documentation"]
	factoids[".devdocs"] = factoids[".developer-documentation"]
	factoids[".developer-docs"] = factoids[".developer-documentation"]
	factoids[".dev-documentation"] = factoids[".developer-documentation"]
	factoids[".wiki"] = factoids[".developer-documentation"]
	factoids[".complexity"] = factoids[".code-complexity"]
	factoids[".cyclomatic-complexity"] = factoids[".code-complexity"]
	factoids[".mccabe"] = factoids[".code-complexity"]
	factoids[".requirements"] = factoids[".dependencies"]
	factoids[".simplebugs"] = factoids[".bitesize"]
	factoids[".bitesizebugs"] = factoids[".bitesize"]
	factoids['.ohloh'] = factoids['.openhub']
	factoids['.tests'] = factoids['.travis']
	factoids['.ci'] = factoids['.openteacher-ci']
	factoids['.otweb'] = factoids['.web']
	factoids['.build'] = factoids['.builds']

	def __init__(self, channels, nickname, realname, admins, **kwargs):
		self.allowedChannels = channels
		self.nickname = nickname
		self.admins = admins

		self.launchpad = launchpad.Launchpad.login_anonymously("OpenTeacher bot", "production", "~/.config/launchpadlib", version="devel")

		super().__init__(self.nickname, realname=realname)

	def on_connect(self):
		super().on_connect()

		print("Signed on. Now joining channels.")

		for channel in self.allowedChannels:
			self.join(channel)

	def on_join(self, channel, username):
		if username == self.nickname:
			print("Joined %s." % channel)

	def _buildBugResponse(self, msg, user):
		#bugs
		match = re.search("(?:bugs? ?/?|#|lp:)([0-9]+)", msg)
		if match:
			number = match.group(1)
			bug = self.launchpad.bugs[number]
			if bug:
				try:
					task = bug.bug_tasks[0] #should not crash, but to be sure
				except IndexError:
					pass
				else:
					return "bug #%s: %s (status: %s, importance: %s) - %s" % (
						bug.id,
						bug.title,
						task.status,
						task.importance,
						bug.web_link
					)

	def _buildBranchResponse(self, msg, user):
		#branches
		match = re.search("lp:[^ ]*(?=[^,.?!])[^ ]", msg)
		if match:
			branch = self.launchpad.branches.getByUrl(url=match.group(0))
			if branch:
				text = "branch %s (status: %s, %s revisions) - %s" % (
					branch.bzr_identity,
					branch.lifecycle_status,
					branch.revision_count,
					branch.web_link,
				)
				return text

	def _buildPyModResponse(self, msg, user):
		if msg.startswith(".pymod "):
			mod = msg.split(" ")[1]
			url = u"http://docs.python.org/library/%s.html" % mod
			try:
				urllib.request.urlopen(url)
			except urllib.error.HTTPError:
				return u"Can't find documentation for that module."
			else:
				return url

	def _buildGoogleResponse(self, msg, user):
		if msg.startswith(".google "):
			q = msg.split(" ", 1)[1]
			q = urllib.parse.quote_plus(q)
			return u"http://google.com/search?q=%s" % q

	def _buildQtResponse(self, msg, user):
		if msg.startswith(".qt "):
			cls = msg.split(" ")[1]
			url = u"http://qt-project.org/doc/qt-5/%s.html" % cls
			try:
				urllib.request.urlopen(url)
			except urllib.error.HTTPError:
				return u"Can't find documentation for that class."
			else:
				return url

	def _buildAnswerResponse(self, msg, user):
		if msg.startswith(".answer ") or msg.startswith(".ask ") or msg.startswith(".question "):
			q = msg.split(" ", 1)[1].replace(self.nickname, "")
			q = urllib.parse.quote_plus(q)
			return u"http://www.wolframalpha.com/input/?i=%s" % q

	def _buildPepResponse(self, msg, user):
		if msg.startswith(".pep "):
			try:
				number = int(msg.split(" ", 1)[1].replace(self.nickname, ""))
			except ValueError:
				return u"Couldn't parse pep number."
			else:
				url = u"http://www.python.org/dev/peps/pep-%04d/" % number
				try:
					urllib.request.urlopen(url)
				except urllib.error.HTTPError:
					return u"No pep found for that number."
				else:
					return url

	def _buildBlueprintResponse(self, msg, user):
		#blueprints
		match = re.search("(?:specs?[ /]+|blueprints? )([^ ,.?!/]+)", msg)
		if match:
			spec = self.launchpad.projects["openteacher"].getSpecification(name=match.group(1))
			if spec:
				return u"blueprint %s: %s (def. status: %s, impl. status: %s, priority: %s) - %s" % (
					spec.name,
					spec.title,
					spec.definition_status,
					spec.implementation_status,
					spec.priority,
					spec.web_link,
				)

	def _buildFactoidResponse(self, msg, user):
		#factoids
		#sorted on key so the longest factoid keys are tried first.
		#Needed in this situation: .codedocumentation while there's a
		#factoid .code.
		for key, factoid in reversed(sorted(self.factoids.items(), key=lambda t: len(t[0]))):
			if key + " " in msg + " ":
				return factoid

	@pydle.coroutine
	def isAdmin(self, user):
		return user in self.admins and (yield self.whois(user))['identified']

	@pydle.coroutine
	def on_message(self, channel, user, msg):
		print("%s: %s: %s" % (user, channel, msg))
		target = channel if channel in self.allowedChannels else user

		#quit client
		if msg == ".quit" and (yield self.isAdmin(user)):
			self.quit()

		builders = [
			self._buildAnswerResponse,
			self._buildBlueprintResponse,
			self._buildBranchResponse,
			self._buildBugResponse,
			self._buildFactoidResponse,
			self._buildGoogleResponse,
			self._buildPepResponse,
			self._buildPyModResponse,
			self._buildQtResponse,
		]

		for buildResponse in builders:
			resp = buildResponse(msg, user)
			if resp is not None:
				#found a message
				break
		else:
			#no message found
			return

		#send the message
		self.message(target, resp)

def run():
	print("For a nice quit, tell the bot to .quit on irc.\n")

	with open(os.path.join(os.path.dirname(__file__), 'config.json'), encoding='UTF-8') as f:
		config = json.load(f)
	client = OpenTeacherBot(**config)
	client.connect('irc.freenode.net', tls=True, password=config['password'])
	client.handle_forever()

if __name__ == "__main__":
	run()
