#! /usr/bin/env python3.7
# -*- coding: utf-8 -*-
# vim:fileencoding=utf-8

import math, argparse

class CGError(Exception):
	def __init__(self, value):
		self.value = value
	def __str__(self):
		return repr(self.value)

class CGBadArg(CGError): pass
class CGInternal(CGError): pass

def mbuild(width, height):
	"""Build a NxN matrix filled with 0."""
	result = list()
	for i in range(height):
		result.append(list())
		for j in range(width):
			result[i].append(0.0)
	return result

def mdump(matrix):
	"""Dump a matrix in natural format."""
	for col in matrix:
		print("[ ", end = '');
		for ele in col:
			print(format(ele, "13.6g") + ", ", end = " ")
		print("],")

def mdumpcompton(matrix):
	"""Dump a matrix in compton's format."""
	width = len(matrix[0])
	height = len(matrix)
	print("{},{},".format(width, height), end = '')
	for i in range(height):
		for j in range(width):
			if int(height / 2) == i and int(width / 2) == j:
				continue;
			print(format(matrix[i][j], ".6f"), end = ",")
	print()

def mnormalize(matrix):
	"""Scale a matrix according to the value in the center."""
	width = len(matrix[0])
	height = len(matrix)
	factor = 1.0 / matrix[int(height / 2)][int(width / 2)]
	if 1.0 == factor: return matrix
	for i in range(height):
		for j in range(width):
			matrix[i][j] *= factor
	return matrix

def mmirror4(matrix):
	"""Do a 4-way mirroring on a matrix from top-left corner."""
	width = len(matrix[0])
	height = len(matrix)
	for i in range(height):
		for j in range(width):
			x = min(i, height - 1 - i)
			y = min(j, width - 1 - j)
			matrix[i][j] = matrix[x][y]
	return matrix

def gen_gaussian(width, height, factors):
	"""Build a Gaussian blur kernel."""

	if width != height:
		raise CGBadArg("Cannot build an uneven Gaussian blur kernel.")

	size = width
	sigma = float(factors.get('sigma', 0.84089642))

	result = mbuild(size, size)
	for i in range(int(size / 2) + 1):
		for j in range(int(size / 2) + 1):
			diffx = i - int(size / 2);
			diffy = j - int(size / 2);
			result[i][j] = 1.0 / (2 * math.pi * sigma) * pow(math.e, - (diffx * diffx + diffy * diffy) / (2 * sigma * sigma))
	mnormalize(result)
	mmirror4(result)

	return result

def gen_box(width, height, factors):
	"""Build a box blur kernel."""
	result = mbuild(width, height)
	for i in range(height):
		for j in range(width):
			result[i][j] = 1.0
	return result

def gen_invalid(width, height, factors):
	raise CGBadArg("Unknown kernel type.")

def args_readfactors(lst):
	"""Parse the factor arguments."""
	factors = dict()
	if lst:
		for s in lst:
			res = s.partition('=')
			if not res[0]:
				raise CGBadArg("Factor has no key.")
			if not res[2]:
				raise CGBadArg("Factor has no value.")
			factors[res[0]] = float(res[2])
	return factors

parser = argparse.ArgumentParser(description='Build a convolution kernel.')
parser.add_argument('type', help='Type of convolution kernel. May be "gaussian" (factor sigma = 0.84089642) or "box".')
parser.add_argument('width', type=int, help='Width of convolution kernel. Must be an odd number.')
parser.add_argument('height', nargs='?', type=int, help='Height of convolution kernel. Must be an odd number. Equals to width if omitted.')
parser.add_argument('-f', '--factor', nargs='+', help='Factors of the convolution kernel, in name=value format.')
parser.add_argument('--dump-compton', action='store_true', help='Dump in compton format.')
args = parser.parse_args()

width = args.width
height = args.height
if not height:
	height = width
if not (width > 0 and height > 0):
	raise CGBadArg("Invalid width/height.")
factors = args_readfactors(args.factor)

funcs = dict(gaussian = gen_gaussian, box = gen_box)
matrix = (funcs.get(args.type, gen_invalid))(width, height, factors)
if args.dump_compton:
	mdumpcompton(matrix)
else:
	mdump(matrix)
