# -*- coding: utf-8 -*-
#
#    Copyright © 2019 Simon Forman
#
#    This file is part of Thun
#
#    Thun 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.
#
#    Thun 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 Thun.  If not see <http://www.gnu.org/licenses/>.
#
'''

Main Module
======================================

Pulls everything together.

'''
from __future__ import print_function
from __future__ import division
from past.builtins import execfile
from builtins import object
from past.utils import old_div
import os, sys, traceback
import pygame
from joy.library import initialize, DefinitionWrapper, SimpleFunctionWrapper
from joy.vui import core, display, persist_task


FULLSCREEN = '-f' in sys.argv


JOY_HOME = os.environ.get('JOY_HOME')
if JOY_HOME is None:
	JOY_HOME = os.path.expanduser('~/.thun')
	if not os.path.isabs(JOY_HOME):
		raise ValueError('what directory?')


def load_definitions(pt, dictionary):
	'''Load definitions from ``definitions.txt``.'''
	lines = pt.open('definitions.txt')[1]
	for line in lines:
		if '==' in line:
			DefinitionWrapper.add_def(line, dictionary)


def load_primitives(home, name_space):
	'''Load primitives from ``library.py``.'''
	fn = os.path.join(home, 'library.py')
	if os.path.exists(fn):
		execfile(fn, name_space)


def init():
	'''
	Initialize the system.
	
	* Init PyGame
	* Create main window
	* Start the PyGame clock
	* Set the event mask
	* Create the PersistTask

	'''
	print('Initializing Pygame...')
	pygame.init()
	print('Creating window...')
	if FULLSCREEN:
		screen = pygame.display.set_mode()
	else:
		screen = pygame.display.set_mode((1024, 768))
	clock = pygame.time.Clock()
	pygame.event.set_allowed(None)
	pygame.event.set_allowed(core.ALLOWED_EVENTS)
	pt = persist_task.PersistTask(JOY_HOME)
	return screen, clock, pt


def init_context(screen, clock, pt):
	'''
	More initialization

	* Create the Joy dictionary
	* Create the Display
	* Open the log, menu, and scratch text viewers, and the stack pickle
	* Start the main loop
	* Create the World object
	* Register PersistTask and World message handlers with the Display
	* Load user function definitions.

	'''
	D = initialize()
	d = display.Display(
		screen,
		D.__contains__,
		*((144 - 89, 144, 89) if FULLSCREEN else (89, 144))
		)
	log = d.init_text(pt, 0, 0, 'log.txt')
	tho = d.init_text(pt, 0, old_div(d.h, 3), 'menu.txt')
	t = d.init_text(pt, old_div(d.w, 2), 0, 'scratch.txt')
	loop = core.TheLoop(d, clock)
	stack_id, stack_holder = pt.open('stack.pickle')
	world = core.World(stack_id, stack_holder, D, d.broadcast, log)
	loop.install_task(pt.task_run, 10000)  # save files every ten seconds
	d.handlers.append(pt.handle)
	d.handlers.append(world.handle)
	load_definitions(pt, D)
	return locals()


def error_guard(loop, n=10):
	'''
	Run a loop function, retry for ``n`` exceptions.
	Prints tracebacks on ``sys.stderr``.
	'''
	error_count = 0
	while error_count < n:
		try:
			loop()
			break
		except:
			traceback.print_exc(file=sys.stderr)
			error_count += 1


class FileFaker(object):
	'''Pretends to be a file object but writes to log instead.'''

	def __init__(self, log):
		self.log = log

	def write(self, text):
		'''Write text to log.'''
		self.log.append(text)

	def flush(self):
		pass


def main(screen, clock, pt):
	'''
	Main function.

	* Call ``init_context()``
	* Load primitives
	* Create an ``evaluate`` function that lets you just eval some Python code
	* Redirect ``stdout`` to the log using a ``FileFaker`` object, and...
	* Start the main loop.
	'''
	name_space = init_context(screen, clock, pt)
	load_primitives(pt.home, name_space.copy())

	@SimpleFunctionWrapper
	def evaluate(stack):
		'''Evaluate the Python code text on the top of the stack.'''
		code, stack = stack
		exec(code, name_space.copy())
		return stack

	name_space['D']['evaluate'] = evaluate


	sys.stdout, old_stdout = FileFaker(name_space['log']), sys.stdout
	try:
		error_guard(name_space['loop'].loop)
	finally:
		sys.stdout = old_stdout

	return name_space['d']
