import sys
import os
from multiprocessing import Pool, cpu_count, Process
import json

def _except_hook(cls, exception, traceback):
    '''
    Function used by :py:func:`pqt5_exception_workaround`. Don't call this directly.
    '''
    sys.__excepthook__(cls, exception, traceback)
    

    
def pqt5_exception_workaround():
    '''
    Workaround to prevent pyqt5 from silently eating exceptions.
    Call this at the beginning of your progame and you will experience relieve.
    '''
    sys.excepthook = _except_hook


class Persival():
    '''
    Keeps values persistent between program runs. 
    
    '''
    
    def __init__(self, persival_file='persival.json'):
        '''
        Will try to load parameters from the persival_file. 
        If the file does not exists this will be silently ignored and the file will be created when :py:meth:`save` will be called. 
    
        '''

        self.persival_file=persival_file        
        if os.path.exists(persival_file):
            with open(self.persival_file, 'r') as file:
                self.data=json.load(file)
        else:
            self.data={}
        
    def setDefault(self, name, value):
        '''
        Sets the given value only if no information was present in the persival file or if the persival file was not found.
        '''
        if not name in self.data.keys():
            self.data[name]=value
    
    def get(self, name):
        '''
        Returns the saved value for the given parameter.
        '''
        return self.data[name]
    
    
    def set(self, name, value):
        '''
        Store the given value in memory. You have to call save to dump the value to the disk. 
        '''
        self.data[name] = value
       # print('Break')
    
        
    def save(self):
        '''
        Saves the content to a file.
        '''
        with open(self.persival_file, 'w') as file: 
            json.dump(self.data, file)
    
    
    
    
class ProcessingHelper():
    '''
    Helper working around the limitations of Pool.
    
    '''
    def __init__(self, parallel=False):
        self.funcs=[]
        self.parallel=parallel
    
    def add(self, function, args, kwargs={}):
        '''
        Add a function to the processing pool.
        
        :param function: function to be executed
        :param args: list. Provice the positional arguments
        :param kwargs: dict. Optional, provide the kwargs
        '''
        self.funcs.append({'func': function, 'args': args, 'kwargs': kwargs})
        
    def clear(self):
        self.funcs=[]
    
    def run(self):
        '''
        Start processing. 
        
        :returns: list of results. The orgdering matches the sequence of add calls.
        '''
        processes=cpu_count()
        # Windows does not handle full cpu loads well, so leave one CPU unutilized for Windows.
        if os.name == 'nt':
            processes -=1
            
        if self.parallel:
            with Pool(processes=processes) as pool:
                result=pool.map(self._myrun, range(len(self.funcs)))
        else:
            result=[]
            for i, f in enumerate(self.funcs):
                r=self._myrun(i)
                result.append(r)
                
        return result
    
    
    def _myrun(self, paramNo):
        '''
        wrapper around the function for pool
        
        :param paramNo:
        '''
        param=self.funcs[paramNo]
        func=param['func']
        args= param['args']
        kwargs= param['kwargs']
        retval=func(*args, **kwargs)
        return retval
        