# -*- 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.

'''
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, d.h / 3, 'menu.txt')
    t = d.init_text(pt, 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 in 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']
