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

#	Copyright 2012-2013, 2017, Marten de Vries
#
#	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/>.

import sys
import os
import datetime
import subprocess
import glob

# too general to claim. See for more info:
# https://bugs.launchpad.net/openteacher/+bug/1556510
BLACKLISTED_EXTENSIONS = ['xml.gz']

class SourceWithSetupSaverModule:
	def __init__(self, moduleManager, *args, **kwargs):
		super().__init__(*args, **kwargs)
		self._mm = moduleManager

		self.type = "sourceWithSetupSaver"
		self.requires = (
			self._mm.mods(type="metadata"),
			self._mm.mods(type="sourceSaver"),
			self._mm.mods(type="translator"),
		)
		self.uses = (
			self._mm.mods(type="profileDescription"),
			self._mm.mods(type="authors"),
			self._mm.mods(type="load"),
			self._mm.mods(type="dataTypeIcons"),
		)
		self.filesWithTranslations = ("manpage.templ",)

	def _moveIntoPythonPackage(self):
		os.mkdir(self._packagePath)

		#move into python package
		for item in os.listdir(self._sourcePath):
			if item != self._packageName:
				os.rename(
					os.path.join(self._sourcePath, item),
					os.path.join(self._packagePath, item)
				)

		#write __init__.py
		open(os.path.join(self._packagePath, "__init__.py"), "a").close()

	def _findPackageData(self):
		#find all files for package_data
		modulePath = os.path.join(self._packagePath, "modules")
		def getDifference(root):
			return root[len(os.path.commonprefix([self._packagePath, root])) +1:]

		for root, dirs, files in os.walk(modulePath):
			if len(files) == 0:
				continue
			root = getDifference(root)
			for file in files:
				yield os.path.join(root, file)

	def _findMimetypes(self):
		for mod in self._modules.sort("active", type="load"):
			if not hasattr(mod, "mimetype"):
				continue
			for ext in mod.loads.keys():
				if ext in BLACKLISTED_EXTENSIONS:
					continue
				yield ext, mod.name, mod.mimetype

	def _findImagePaths(self, mimetypes):
		imagePaths = {}
		for ext, name, mimetype in mimetypes:
			imagePaths[mimetype] = "linux/" + mimetype.replace("/", "-") + ".png"
		return imagePaths

	def _buildStartupScript(self):
		#bin/package
		os.mkdir(os.path.join(self._sourcePath, "bin"))
		with open(os.path.join(self._sourcePath, "bin", self._packageName), "w", encoding='ascii') as f:
			templ = pyratemp.Template(filename=self._mm.resourcePath("runner.templ"))
			#ascii since the file doesn't have a encoding directive
			f.write(templ(name=self._packageName))

	def _buildDesktopFile(self, linuxDir, mimetypes):
		#linux/package.desktop
		with open(os.path.join(linuxDir, self._packageName + ".desktop"), "w", encoding='UTF-8') as f:
			templ = pyratemp.Template(filename=self._mm.resourcePath("desktop.templ"))

			mimetypeList = sorted(set((m[2] for m in mimetypes)))
			f.write(templ(package=self._packageName, mimetypes=";".join(mimetypeList), **self._metadata))

	def _makeLinuxDir(self):
		path = os.path.join(self._sourcePath, "linux")
		os.mkdir(path)
		return path

	def _buildMenuFile(self, linuxDir):
		#linux/package.menu
		with open(os.path.join(linuxDir, self._packageName), "w", encoding='UTF-8') as f:
			templ = pyratemp.Template(filename=self._mm.resourcePath("menu.templ"))
			f.write(templ(package=self._packageName, **self._metadata))

	def _buildManPages(self, linuxDir):
		#english man page
		self._buildManPage(linuxDir, "C")
		#other languages
		for file in glob.glob(self._mm.resourcePath("translations/*.mo")):
			lang = os.path.splitext(os.path.basename(file))[0]
			self._buildManPage(linuxDir, lang)

	def _buildMimetypeFile(self, linuxDir, mimetypes):
		#linux/package.xml
		with open(os.path.join(linuxDir, self._packageName + ".xml"), "w", encoding='UTF-8') as f:
			templ = pyratemp.Template(filename=self._mm.resourcePath("mimetypes.xml"))
			f.write(templ(data=mimetypes))

	def _getLogoAsQImage(self):
		return QtGui.QImage(self._metadata["iconPath"])

	def _buildPngIcon(self, linuxDir, qImage):
		#generate png icon
		image128 = qImage.scaled(128, 128, QtCore.Qt.KeepAspectRatio)
		image128.save(os.path.join(self._sourcePath, os.path.join(linuxDir, self._packageName + ".png")))

	def _buildXpmIcon(self, linuxDir, qImage):
		#generate openteacher.xpm
		image32 = qImage.scaled(32, 32, QtCore.Qt.KeepAspectRatio)
		image32.save(os.path.join(linuxDir, self._packageName + ".xpm"))

	def _buildFileIcons(self, imagePaths, qImage):
		#generate file icons

		for mimeType, imagePath in imagePaths.items():
			copy = qImage.scaled(128, 128, QtCore.Qt.KeepAspectRatio)
			otherImage = self._findSubIcon({
				"application/x-openteachingwords": "words",
				"application/x-openteachingtopography": "topo",
				"application/x-openteachingmedia": "media",
			}.get(mimeType))
			if not otherImage.isNull():
				otherImage = otherImage.scaled(64, 64, QtCore.Qt.KeepAspectRatio)

			p = QtGui.QPainter(copy)
			p.drawImage(64, 64, otherImage)
			p.end()

			copy.save(os.path.join(self._sourcePath, imagePath))

	def _findSubIcon(self, type):
		try:
			return QtGui.QImage(self._modules.default("active", type="dataTypeIcons").findIcon(type))
		except (KeyError, IndexError):
			return QtGui.QImage()

	def _buildLinuxFilesCopying(self, linuxDir):
		#make a COPYING file in place for the generated files
		with open(os.path.join(linuxDir, "COPYING"), "w", encoding='ascii') as f:
			f.write("application-x-openteachingwords.png, application-x-openteachingtopo.png and application-x-openteachingmedia.png are based on the files words.png, topo.png and media.png of which the licenses are described elsewhere in this package.\n")

	def _findCExtensions(self):
		#gather extensions to compile
		for f in os.listdir(self._packagePath):
			if f.endswith(".c"):
				yield os.path.splitext(f)[0], f

	def _findPackages(self):
		yield self._packageName

		for root, dirs, files in os.walk(self._packagePath):
			if root == self._packagePath:
				continue
			relativeName = os.path.relpath(root, self._packagePath)
			if "__init__.py" in files:
				yield self._packageName + "." + relativeName.replace(os.sep, ".")

	def _buildSetupPy(self, imagePaths):
		packageData = self._findPackageData()
		exts = self._findCExtensions()
		packages = self._findPackages()

		data = self._metadata.copy()
		data.update({
			"packageName": self._packageName,
			"packages": packages,
			"package_data": packageData,
			"image_paths": repr(list(imagePaths.values())),
			"extensions": exts,
		})

		#setup.py
		with open(os.path.join(self._sourcePath, "setup.py"), "w", encoding='UTF-8') as f:
			templ = pyratemp.Template(filename=self._mm.resourcePath("setup.py.templ"))
			f.write(templ(**data))

	def _addSetupAndOtherFiles(self):
		#set main path variables
		self._packageName = os.path.basename(sys.argv[0])
		if self._packageName.endswith(".py"):
			self._packageName = self._packageName[:-3]
		self._packagePath = os.path.join(self._sourcePath, self._packageName)

		self._moveIntoPythonPackage()
		self._buildStartupScript()

		linuxDir = self._makeLinuxDir()
		self._buildMenuFile(linuxDir)
		self._buildLinuxFilesCopying(linuxDir)
		self._buildManPages(linuxDir)

		qIcon = self._getLogoAsQImage()
		self._buildPngIcon(linuxDir, qIcon)
		self._buildXpmIcon(linuxDir, qIcon)

		mimetypes = list(self._findMimetypes())
		self._buildMimetypeFile(linuxDir, mimetypes)
		self._buildDesktopFile(linuxDir, mimetypes)

		imagePaths = self._findImagePaths(mimetypes)
		self._buildFileIcons(imagePaths, qIcon)

		self._buildSetupPy(imagePaths)

	def saveSourceWithCExtensions(self):
		self._sourcePath = self._sourceSaver.saveSourceWithCExtensions()
		self._addSetupAndOtherFiles()
		return self._sourcePath

	def saveSource(self):
		self._sourcePath = self._sourceSaver.saveSource()
		self._addSetupAndOtherFiles()
		return self._sourcePath

	_sourceSaver = property(lambda self: self._modules.default("active", type="sourceSaver"))

	def _buildManPage(self, linuxDir, lang):
		translator = self._modules.default("active", type="translator")
		#temporarily switch the application language
		oldLanguage = translator.language
		translator.language = lang

		rstPath = os.path.join(linuxDir, "manpage.rst")
		with open(rstPath, "w", encoding='UTF-8') as f:
			_, ngettext = translator.gettextFunctions(self._mm.resourcePath("translations"))

			templ = pyratemp.Template(filename=self._mm.resourcePath("manpage.templ"))
			authors = self._modules.default("active", type="authors").registeredAuthors
			profileMods = self._modules.sort("active", type="profileDescription")
			args = {
				"package": self._packageName,
				"nowDate": datetime.datetime.now().strftime("%Y-%m-%d"),
				#someone may appear in multiple categories, so set.
				"otAuthors": set([a[1] for a in authors]),
				"profiles": [m.desc for m in profileMods],
				"tr": _,
				#constructs a valid restructured text title.
				"titleify": lambda title: title + "\n" + (len(title) + 1) * "="
			}
			args.update(self._metadata)
			f.write(templ(**args))

		if lang == "C":
			manName = self._packageName + ".1"
		else:
			manName = self._packageName + "." + lang + ".1"
		manPath = os.path.join(linuxDir, manName)

		subprocess.check_call(["rst2man", rstPath, manPath])
		os.remove(rstPath)

		#restore the default application language again.
		translator.language = oldLanguage

	def enable(self):
		global pyratemp
		global QtCore, QtGui
		try:
			import pyratemp
			from PyQt5 import QtCore, QtGui
		except ImportError:
			return #fail silently: stay inactive
		self._modules = set(self._mm.mods(type="modules")).pop()
		self._metadata = self._modules.default("active", type="metadata").metadata

		self.active = True

	def disable(self):
		self.active = False

		del self._modules
		del self._metadata

def init(moduleManager):
	return SourceWithSetupSaverModule(moduleManager)
