#	Programmer:	limodou
#	E-mail:		limodou@gmail.com
#
#	Copyleft 2005 limodou
#
#	Distributed under the terms of the GPL (GNU Public License)
#
#   Casing 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 2 of the License, or
#   (at your option) any later version.
#
#   This program 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 this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#	$Id$
#   Version = 0.1
#   Update:
#     

import threading
import time
import Queue

class DUMP_CLASS:pass
class AbortException(Exception):pass

class SyncVar(object):
    def __init__(self):
        self.flag = False
        self.lock = threading.Lock()
        
    def set(self, flag=True):
        self.lock.acquire()
        self.flag = flag
        self.lock.release()
        
    def isset(self):
        return self.flag
    
    def get(self):
        return self.flag
    
    def clear(self):
        self.lock.acquire()
        self.flag = False
        self.lock.release()
        
    def __ne__(self, other):
        return self.flag != other
    
    def __eq__(self, other):
        return self.flag == other

    def __nonzero__(self):
        return bool(self.flag)

class FuncThread(threading.Thread):
    def __init__(self, casing, syncvar, sync=False, kws={}):
        threading.Thread.__init__(self)
        self.casing = casing
        self.syncvar = syncvar
        self.sync = sync
        self.kws = kws
        
    def run(self):
        if self.sync:
            self.casing.sync_start(self.syncvar, kws=self.kws)
        else:
            self.casing.start(**self.kws)
        self.syncvar.clear()
    
class ProcessThread(threading.Thread):
    def __init__(self, casing, syncvar, sync=False):
        threading.Thread.__init__(self)
        self.casing = casing
        self.syncvar = syncvar
        self.sync = sync
        
    def run(self):
        func, args, kwargs = self.casing.on_process
        if kwargs.has_key('timestep'):
            timestep = kwargs['timestep']
            del kwargs['timestep']
        else:
            timestep = 0.5
        while 1:
            if self.syncvar:
                if self.sync:
                    kwargs['syncvar'] = self.syncvar
                func(*args, **kwargs)
                time.sleep(timestep)
            else:
                break

class MultiCasing(object):
    def __init__(self, size=10, need_manual_stop=False, timestep=1):
        self.on_finish = None
        self.on_process = None
        self.on_abort = None
        self.size = size
        self.need_manual_stop = need_manual_stop
        self.queue = Queue.Queue()
        self.active = []
        self.event = threading.Event()
        self.event.set()
        self._exit_flag = False
        self.thread_d = None
        self.timestep = timestep
        
    def start_thread(self):
        self.thread_d = d = Casing(self._start)
        if self.on_process:
            d.onprocess(self.on_process[0], *self.on_process[1], **self.on_process[2])
        d.start_thread()
    
    def start_sync_thread(self):
        self.thread_d = d = Casing(self._start, sync=True)
        if self.on_process:
            d.onprocess(self.on_process[0], *self.on_process[1], **self.on_process[2])
        d.start_sync_thread()
    
    def append(self, casing_obj):
        self.queue.put(casing_obj, block=True, timeout=1)
        
    def stop_thread(self):
        for obj in self.active:
            obj.stop_thread()
        self._exit_flag = True
        
    def _start(self, syncvar=None, sync=False):
        self._exit_flag = False
        self.running = True
        while not self._exit_flag:
            self.event.wait(self.timestep)
            if not self.queue.empty() and len(self.active) < self.size:
                casing = self.queue.get()
                self.active.append(casing)
                casing.onsync(self._on_sync, casing)
                if not sync:
                    casing.start_thread()
                else:
                    casing.start_sync_thread()
            elif self.queue.empty() and not self.active: #not more thread obj need to run
                if not self.need_manual_stop:
                    break
            elif len(self.active) == self.size:
                self.event.clear()
        self.running = False
        if not self.active and self.queue.empty() and self.on_finish:
            self._run(self.on_finish)
        elif self.on_abort:
            self._run(self.on_abort)
                
    def _on_sync(self, obj):
        self.active.remove(obj)
        self.event.set()
  
    def onfinish(self, func, *args, **kwargs):
        self.on_finish = func, args, kwargs
        
    def onprocess(self, func, *args, **kwargs):
        self.on_process = func, args, kwargs

    def onabort(self, func, *args, **kwargs):
        self.on_abort = func, args, kwargs

    def _run(self, func):
        f, args, kwargs = func
        return f(*args, **kwargs)
    
    def isactive(self):
        return self.thread_d.isactive()

    def running_count(self):
        return len(self.active)

    def remaining_count(self):
        return self.queue.qsize()
        
class Casing(object):
    def __init__(self, func=None, *args, **kwargs):
        self.funcs = []
        if func:
            self.funcs.append((func, args, kwargs))
        self.on_success = None
        self.on_exception = None
        self.on_abort = None
        self.on_process = None
        self.on_sync = None     #used internal
        
        self.syncvar = None
        self.t_func = None
        self.p_func = None
        
    def __add__(self, obj):
        assert isinstance(obj, Casing)
        self.funcs.extend(obj.funcs)
        return self
        
    def __radd__(self, obj):
        assert isinstance(obj, Casing)
        self.funcs.extend(obj.funcs)
        return self
    
    def copy(self):
        obj = Casing()
        for name, value in vars(self).items():
            setattr(obj, name, self._deepcopy(value))
        return obj
    
    def _deepcopy(self, obj):
        if isinstance(obj, tuple):
            s = []
            for i in range(len(obj)):
                s.append(self._deepcopy(obj[i]))
            return tuple(s)
        elif isinstance(obj, list):
            s = []
            for i in range(len(obj)):
                s.append(self._deepcopy(obj[i]))
            return s
        elif isinstance(obj, dict):
            s = {}
            for k, v in obj.items():
                s[k] = self._deepcopy(v)
            return s
        else:
            return obj
        
    def _update(self, src, kdict):
        for k, v in src.items():
            if kdict.has_key(k):
                src[k] = kdict[k]

    def append(self, func, *args, **kwargs):
        self.funcs.append((func, args, kwargs))
        
    def onsuccess(self, func, *args, **kwargs):
        self.on_success = func, args, kwargs
        
    def onexception(self, func, *args, **kwargs):
        self.on_exception = func, args, kwargs
        
    def onabort(self, func, *args, **kwargs):
        self.on_abort = func, args, kwargs

    def onprocess(self, func, *args, **kwargs):
        self.on_process = func, args, kwargs
        
    def onsync(self, func, *args, **kwargs):
        self.on_sync = func, args, kwargs
        
    def start(self, **kws):
        try:
            for func, args, kwargs in self.funcs:
                self._update(kwargs, kws)
                ret = self._run((func, args, kwargs))
            if self.on_success:
                self._run(self.on_success)
            if self.on_sync:
                self._run(self.on_sync)
        except AbortException:
            if self.on_abort:
                self._run(self.on_abort)
            else:
                print 'Abort'
            return
        except:
            if self.on_exception:
                self._run(self.on_exception)
            else:
                import traceback
                traceback.print_exc()
            
    def start_thread(self, **kws):
        self.syncvar = syncvar = SyncVar()
        self.syncvar.set()
        self.t_func = t = FuncThread(self, syncvar, kws=kws)
        self.p_func = None
        t.setDaemon(True)
        t.start()
        if self.on_process:
            self.p_func = t1 = ProcessThread(self, syncvar)
            t1.setDaemon(True)
            t1.start()
            
    def sync_start(self, syncvar, kws={}):
        try:
            for func, args, kwargs in self.funcs:
                self._update(kwargs, kws)
                kwargs['syncvar'] = syncvar
                if not syncvar:
                    return
                self._run((func, args, kwargs))
            syncvar.clear()
            if self.on_success:
                self._run(self.on_success)
            if self.on_sync:
                self._run(self.on_sync)
        except AbortException:
            syncvar.clear()
            if self.on_abort:
                self._run(self.on_abort)
            else:
                print 'Abort'
            return
        except:
            syncvar.clear()
            if self.on_exception:
                self._run(self.on_exception)
            else:
                import traceback
                traceback.print_exc()

    def start_sync_thread(self, **kws):
        self.syncvar = syncvar = SyncVar()
        self.syncvar.set()
        self.t_func = t = FuncThread(self, syncvar, sync=True, kws=kws)
        self.p_func = None
        t.setDaemon(True)
        t.start()
        if self.on_process:
            self.p_func = t1 = ProcessThread(self, syncvar, sync=True)
            t1.setDaemon(True)
            t1.start()

    def stop_thread(self):
        if self.syncvar:
            self.syncvar.clear()
                        
    def _run(self, func):
        f, args, kwargs = func
        return f(*args, **kwargs)
    
    def isactive(self):
        return self.t_func.isAlive()
    
def new_obj():
    return DUMP_CLASS()
        
if __name__ == '__main__':
    def test(n, syncvar):
        for i in range(1, n): 
            if syncvar:
                syncvar.set(i)
                print "=",i
                time.sleep(1)
            else:
                break
            
    def process(syncvar):
        print 'process...', syncvar.get()

    d = Casing(test, 10) + Casing(test, 20)
    d.onprocess(process, timestep=2)
    d.start_sync_thread()
    time.sleep(10)
    print 'stop'
    d.stop_thread()
    
