#!/usr/bin/python3

import datetime
import locale
import json

import argparse
import dateutil.parser
import requests

locale.setlocale(locale.LC_ALL, '')


class IPzzlCrossword:
	century = 20

	def __init__(self, puzzle_size:str, puzzle_date:datetime.date=None):
		if puzzle_date is not None:
			puzzle_date = puzzle_date.date()
			if puzzle_date > datetime.date.today():
				raise ValueError('Deze datum ligt in de toekomst, kies een andere datum.')
			if puzzle_date < datetime.date(2021, 6, 28):
				raise ValueError('Er bestaan geen puzzels van voor 2021-06-28, kies een andere datum.')
			if puzzle_size == 'large' and puzzle_date.weekday() != 5:
				raise ValueError('Dit type puzzel wordt alleen uitgebracht op zaterdagen, kies een andere datum.')
			puzzle_date_string = puzzle_date.strftime('%y%m%d')
		else:
			puzzle_date_string = ''

		puzzle_types = {
			'small': ('MH_mini', 'netmini'),
			'large': ('MH_kruis', 'netcrossword')
		}
		self.puzzle_type_mh, self.puzzle_type_net = puzzle_types[puzzle_size]

		request = requests.get(f'https://pzzl.net/servlet/{self.puzzle_type_mh}/{self.puzzle_type_net}?date={puzzle_date_string}')
		request.raise_for_status()
		self.load_source(request.text)


	def load_source(self, source:str):
		puzzle_properties = source.split('\n\n')
		if len(puzzle_properties) < 11:
			raise ValueError('Deze puzzel bestaat niet, kies een andere datum.')

		self.raw_date = puzzle_properties[1]
		self.date = datetime.datetime.strptime(self.raw_date, '%y%m%d').date()

		if self.puzzle_type_mh == 'MH_mini':
			self.title = puzzle_properties[2] + ' - ' + self.date.strftime('%A %-d %B %Y')
		elif self.puzzle_type_mh == 'MH_kruis':
			self.title = puzzle_properties[2].replace(': ', ' - ') + self.date.strftime(' %Y')
		else:
			self.title = puzzle_properties[2]

		if len(puzzle_properties[3]):
			self.intro = puzzle_properties[3].replace('<NOTEPAD>', '').replace('</NOTEPAD>', '')
		else:
			self.intro = None

		self.width = int(puzzle_properties[4])
		self.height = int(puzzle_properties[5])

		self.num_clues_across = puzzle_properties[6]
		self.num_clues_down = puzzle_properties[7]

		self.raw_grid = puzzle_properties[8]

		self.clue_texts_across = puzzle_properties[9].split('\n')
		self.clue_texts_down = puzzle_properties[10].split('\n')

		self.process_grid()


	def process_grid(self):
		# Initialize lists to write in
		self.puzzle = [[None]*self.width for _ in range(self.height)]
		self.solution = [['']*self.width for _ in range(self.height)]
		self.clue_numbers_across = []
		self.clue_numbers_down = []

		# Extract circles and solution from the raw grid
		circles = [[False]*self.width for _ in range(self.height)]
		for row, line in enumerate(self.raw_grid.split('\n')):
			col = 0
			for char in line:
				if char == '%':
					circles[row][col] = True
				elif char == ',':
					col -= 1
				elif char == '.':
					# Empty cell not encountered in any puzzle
					raise NotImplementedError
				else:
					self.solution[row][col] += char
					col += 1

		# Extract puzzle grid and clue numbers from solution
		blocks = [[cell == '#' for cell in row]+[True] for row in self.solution]+[[True]*self.width]
		label_counter = 0
		for row in range(self.height):
			for col in range(self.width):
				start_word_across = not blocks[row][col] and (blocks[row][col-1] and not blocks[row][col+1])
				start_word_down = not blocks[row][col] and (blocks[row-1][col] and not blocks[row+1][col])

				if blocks[row][col]:
					puzzle_cell = '#'
				elif start_word_across or start_word_down:
					label_counter += 1
					puzzle_cell = label_counter
				else:
					puzzle_cell = 0

				if circles[row][col]:
					self.puzzle[row][col] = {'cell': puzzle_cell, 'style': {'shapebg': 'circle'}}
				else:
					self.puzzle[row][col] = puzzle_cell

				if start_word_across:
					self.clue_numbers_across.append(label_counter)
				if start_word_down:
					self.clue_numbers_down.append(label_counter)


	def __str__(self):
		return json.dumps({
			'version': 'http://ipuz.org/v2',
			'kind': ['http://ipuz.org/crossword#1'],
			'copyright': f'{self.date.year} Mediahuis Nederland B.V., Amsterdam.',
			'publisher': 'De Telegraaf',
			# 'publication': None,
			'url': f'https://pzzl.net/app/{self.puzzle_type_mh}/?date={self.raw_date}',
			'uniqueid': f'net.pzzl.{self.puzzle_type_mh}.{self.raw_date}',
			'title': self.title,
			'intro': self.intro,
			# 'explanation': None,
			# 'annotation': None,
			# 'author': None,
			# 'editor': None,
			'date': self.date.strftime('%A %-d %B %Y'),
			# 'notes': None,
			# 'difficulty': None,
			'charset': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
			'origin': 'iPzzl parser - door Philip Goto',
			'dimensions': {
				'width': self.width,
				'height': self.height
			},
			'puzzle': self.puzzle,
			# 'saved': None,
			'solution': self.solution,
			'clues': {
				'Across': list(zip(self.clue_numbers_across, self.clue_texts_across)),
				'Down': list(zip(self.clue_numbers_down, self.clue_texts_down))
			},
			# 'showenumerations': None,
			# 'clueplacement': None,
			# 'answer': None,
			'org.gnome.libipuz:locale': 'nl_NL',
			'org.gnome.libipuz:charset': [
				'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
				'J', 'IJ', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
				'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
			]
		})


def main():
	parser = argparse.ArgumentParser(description='fetch .ipuz files from pzzl.net')
	parser.add_argument(
		'type', type=str,
		choices=['small', 'large'],
		help='the type of puzzle that should be downloaded'
	)
	parser.add_argument(
		'date', type=dateutil.parser.isoparse, nargs='?',
		help='the date of the puzzle to be downloaded in ISO-8601 format (YYYY-MM-DD), defaults to latest'
	)
	args = parser.parse_args()

	puzzle = IPzzlCrossword(args.type, args.date)
	print(puzzle)


if __name__ == '__main__':
	main()
