import os, time, sys, subprocess, getpass
from multiprocessing import Queue, JoinableQueue, Semaphore, Process, cpu_count
from contextlib import closing

import cardsharp as cs
import help
import MySQLdb

from mapping import cmd_map, run_map
from ..configuration import config as c
from ..configuration import db_info
from ..util import RUNDATETIME, capture_input

class Cmd(object):
    """Cmd class is called when a user invokes a system or project command through the cchrrd.py module. 
    Cmd class handles the overhead involved with initiating a command which includes; user authentication, creating log file objects, 
    and creating public methods for command status logging and process management.
    """
    def __init__(self, args):
        """A Cmd object is created in the cchrrd.py module.  
        When a cmd object is initialized the system will prompt the user for account credentials.
        After user authentication is complete the log folders/files/objects will be created and process management objects will be created.
        
        @param args: A command line arguement parser object containing all the command line arguements from the cchrrd.py command.
        """
        #turn on cardsharp debugging based on verbose option
        cs.config.debug = True if args.verbose or c.debug_verbose else False
        self.args = args
        self.name = self.args.cmd
        
        print '\n'*100
        #prompt the user for account credentials
        self.args.user = capture_input('Please enter your username\n>>> ')
        self.args.pwd = getpass.getpass('Please enter your password\n>>> ')
        
        #and the username and password to the db_info objects
        db_info['stand'].update({'user':self.args.user, 'pass':self.args.pwd})
        db_info['recid'].update({'user':self.args.user, 'pass':self.args.pwd})
        self.args.db_info = db_info
        
        #authenticate the user against the CCHRRD database server
        try:
            with closing(MySQLdb.connect(host=c.db_host, user=self.args.user, passwd=self.args.pwd, port=c.db_port)) as conn:
                with closing(conn.cursor()) as cur:
                    cur.execute('show databases')
        except Exception as e:
            print 'Unable to Connect/validate user: %s' % e
            if self.args.verbose: raise
            sys.exit()
        
        #setup the complete and error logging
        if c.tmp_out_dir not in ['None', None, 0]:
            self.complete_dir = os.path.join(c.tmp_out_dir, self.name, 'complete', RUNDATETIME)
            self.error_dir = os.path.join(c.tmp_out_dir, self.name, 'error', RUNDATETIME)
        else:
            self.complete_dir = os.path.join(__file__, '..', '..', '..', 'tmp_logs', self.name, 'complete', RUNDATETIME)
            self.error_dir = os.path.join(__file__, '..', '..', '..', 'tmp_logs', self.name, 'error', RUNDATETIME)
        
        #create process management objects
        self.complete_cnt = 0
        self.error_cnt = 0
        self.task_queue = JoinableQueue()
        self.done_queue = Queue()
        try:
            self.semaphore = Semaphore(int(args.cores)*2)
        except AttributeError, TypeError:
            self.semaphore = Semaphore(cpu_count()*2)
            
        #start Pyro for distributed computing
        #Pyro4.config.HMAC_KEY = config.security_HMAC_KEY #set the HMAC key for secure processing
        #self.get_ns()
        
    def __str__(self):
        print self.name
        
    def _makedirs(self):
        """Helper function to create log file directories"""
        for dir in [self.complete_dir, self.error_dir]:
            if os.path.isdir(dir):
                for file in os.listdir(dir):
                    os.remove(os.path.join(dir,file))
            else:
                os.makedirs(dir) 
    
    def get_ns(self):
        """Lookup the Pyro nameserver. Start the nameserver if not already started"""
        import Pyro4 
        try:
            self.ns = Pyro4.locateNS()
        except Pyro4.errors.NamingError:
            p = subprocess.Popen([sys.executable, '-m', 'Pyro4.naming'])
            if args.verbose:
                print 'Pyro NameServer started'
            self.get_ns()

    def worker(self, tasks, done, semaphore):
        """Create process worker object. The worker is bound to a sempaphore which only allows a set number
        of processes to run concurrently.  The worker will invoke the task, 
        wait for a completion status response (task failure of success), and then signal that the task has completed.
        
        @param tasks: A queue of command tasks
        @param done: A queue of task completion responses
        @param semaphore: A semphare object that control the number of tasks which can be run concurrently.
        
        """
        with semaphore:
            done.put(run_map[self.name](**tasks.get()))
            tasks.task_done()
            
    def flush(self):
        """Flush the task completion status to the system output logs.  
        If the command failed to complete inform the user that an error occured and the location of the system output error log file. 
        """
        i = 0
        while self.done_queue.qsize() > 0:
            i += 1
            result = self.done_queue.get()
            if isinstance(result, int):
                out_path = os.path.join(self.complete_dir, '%i.txt' % i)
            else:
                out_path = os.path.join(self.error_dir, '%i.txt' % i)
                
                if isinstance(result[1], list):
                    result = result[0] + '\r\n'.join(result[1])
                
                    
                print 'An error was detected. Check %s for details.' % out_path
                
            with closing(open(out_path, 'wb')) as file:
                file.write('\t'.join([self.name, str(result)]))
     
    def start(self):
        """Initiate each command task in a subprocess using the Cmd.worker method to control task process management."""
        for i in xrange(self.task_queue.qsize()):
            Process(target=self.worker, args=(self.task_queue, self.done_queue, self.semaphore)).start()       
                
    def run(self):
        """Create the cmd control logging directories and run the command."""
        self._makedirs()
        cmd_map[self.name](self)
