import sys
import os
import re
import time
import json 
from py_autoflow.stack import Stack
from py_autoflow.queue_manager import QueueManager

def main_autoflow(options):
	options = vars(options)
	
	# Merge templates
	if options.get('workflow') == None:
		raise Exception('Workflow not especified')
	else:
		templates = options['workflow'].split(',')
		options['workflow'] = ''
		if options.get('identifier') == None: options['identifier'] = f"{os.path.basename(templates[0])}_{int(time.time())}" 
		for template in templates:
			if not os.path.exists(template):
				raise Exception(f"Workflow not found: {template}")
			else:
				with open(template) as f: 
					t_text = f.read()
					options['workflow'] = options['workflow'] + t_text+"\n"

	# Set local or remote execution
	if options.get('remote') != None:
		raise Exception('Not implemented')
		#main_path = options[:ssh].exec!('pwd').chomp
	else:
		main_path = os.getcwd()

	# Set output directory
	if options['output'] == 'exec':
		exec_folder = os.path.join(main_path,'exec')
	else:
		exec_folder = options['output']       
		if exec_folder[0] != '/' and exec_folder[0] != '~': exec_folder = os.path.join(main_path, options['output'])

	#--------------------------------------------------------------------------------
	# Flow parse
	#--------------------------------------------------------------------------------
	stack = Stack(exec_folder, options)
	stack.parse_resources(options)
	stack.parse()
	stack.get_jobs_relations()
	 
	#--------------------------------------------------------------------------------
	# Flow exec
	#--------------------------------------------------------------------------------
	if not options['graph']  == None:
		stack.draw(exec_folder, options['graph'])
	else:
		options['write_sh'] = True # Set in flow logger to FALSE, it is used for relaunch failed jobs 
		options['asign_job_folder'] = True # Set in flow logger to FALSE, it is used for relaunch failed jobs 
		manager = QueueManager.select_queue_manager(exec_folder, options, stack.jobs)
		manager.exec(wf_provider = stack.wf_provider)
		stack.clean() # Reset stack for testing and iterative management
		manager.clean() # Reset manager for testing and iterative management
		#options[:ssh].close if options[:remote]

def check_wf_output(wf_path, ignore_files):
	results = {}
	file_list_iterative(results, wf_path, wf_path)
	for file in ignore_files: results[file] = '-'
	return results

def file_list_iterative(results, path, mainpath):
	with os.scandir(path) as d:
		for e in d:
			if e.is_file():
				prefix = mainpath + '/' 
				relative_path = re.sub(prefix,'',e.path)
				file_data = read_file(e.path, prefix)
				results[relative_path] = file_data['content']
			elif not '.wf_log' in e.path and not '_file' in e.path:
				file_list_iterative(results, e.path, mainpath)

def read_file(path, prefix):
	file_data = {}
	content = None
	with open(path) as f: 
		lines = f.readlines()
		comments = []
		text = []
		flowlogger = []
		stat_code_task = []
		for line in lines:
			line = re.sub(prefix, '', line) # REmove absolute path to make fair comparisons with expected output
			line = re.sub("\n\n", "\n", line) #Remove empty lines
			if line == "\n":
				continue
			elif re.search(r"^hostname", line):
				continue
			elif re.search(r"^#", line):
				comments.append(line)
			elif re.search(r"^flow_logger", line):
				flowlogger.append(line)
			elif len(flowlogger) == 2: # FLow logger start and stop code so the next lines must be the code to measure the used resources.
				stat_code_task.append(line)
			else:
				text.append(line)
		#content = ''.join(text)
		file_data['content'] = ''.join(text)
		file_data['comments'] = ''.join(comments)
		file_data['flowlogger'] = ''.join(flowlogger)
		file_data['stat_code_task'] = ''.join(stat_code_task)
	return file_data

def main_flow_logger(opts):
	from py_autoflow.logging import add_timestamp, parse_log, report_log

	options = vars(opts)
	out = None

	if options.get('error') != None:
		add_timestamp(options['workflow_execution'],'error', options['error'])
	elif options.get('set') != None:
		add_timestamp(options['workflow_execution'],'set', options['set'])
	elif options.get('start') != None:
		add_timestamp(options['workflow_execution'],'start', options['start'])
	elif options.get('finish') != None:
		add_timestamp(options['workflow_execution'],'end', options['finish'])
	else:
		log_folder = os.path.join(options['workflow_execution'], '.wf_log')
		job_attribs_file = os.path.join(options['workflow_execution'], 'wf.json')

		if not os.path.exists(log_folder):
			sys.exit("Log folder not exists")

		if not os.path.exists(job_attribs_file):
			sys.exit("wf.json file not exists")

		with open(job_attribs_file) as file: attribs = json.load(file)
		log = parse_log(log_folder)
		if options.get('report') != None:
			out = report_log(log, attribs, options['report'], options['workflow_status'], options['no_size'], options['raw'])
		elif options['workflow_status'] and options['launch_failed_jobs']:
			out = launch_failed_jobs(log, attribs, options['workflow_execution'], options['batch'], options['sleep_time'], options['pending'])
	return out			


def launch_failed_jobs(log, initial_flow_attribs, exec_folder, batch, sleep_time, pending = False):
	options = {
		'verbose': False,
		'key_name': False, 
		'identifier': None,
		'remote': False,
		'ssh': None,
		'external_dependencies': [],
		'batch': batch,
		'write_sh': False,
		'asign_job_folder': False,
		'sleep_time': sleep_time
	}
	failed_jobs = get_failed_jobs(log)
	if pending: failed_jobs = failed_jobs + get_pending_jobs(log, failed_jobs, initial_flow_attribs)
	jobs = {}
	create_jobs(jobs, failed_jobs, initial_flow_attribs)
	get_all_dependencies(jobs, failed_jobs, initial_flow_attribs)
	manager = QueueManager.select_queue_manager(exec_folder, options, jobs)
	manager.exec()
	return jobs

def get_failed_jobs(log):
	position = 0
	fails = []
	for task, attribs in log.items():
		abort = find_failed(attribs['start'], attribs['end'])
		if abort != None:
			if abort > position: position = abort 
			fails.append([task, abort])
	failed_jobs = []
	for task, index in fails:
		if index == position: failed_jobs.append(task)
	return failed_jobs

def find_failed(ar_start, ar_end):
	position = None

	for i, st in enumerate(list(reversed(ar_start))):
		reverse_pos = len(ar_start) - i - 1
		stop = ar_end[reverse_pos]
		if st > 0 and stop == 0:
			next_executions = ar_end[reverse_pos:len(ar_end) - 1]
			if next_executions == None or next_executions.count(0) == len(next_executions):
				position = reverse_pos
				break 
	return position

def create_jobs(jobs, failed_jobs, initial_flow_attribs):
	from py_autoflow.program import Program
	for job in failed_jobs:
		folder, dependencies = initial_flow_attribs[job]
		job_attrib = {
			'done': False,
			'folder': True,
			'buffer': False,
			'exec_folder': folder,
			'cpu_asign': None
		}
		verified_dependencies = []
		for dep in dependencies:
			if jobs.get(dep) != None: verified_dependencies.append(dep)
		jobs[job] = Program(job, '', '', verified_dependencies, job_attrib)

def get_all_dependencies(jobs, failed_jobs, initial_flow_attribs):
	failed_dependecies = []
	for fj in failed_jobs:
		for job, attribs in initial_flow_attribs.items():
			folder, dependencies = attribs
			if fj in dependencies and not job in failed_dependecies: failed_dependecies.append(job) 
	if len(failed_dependecies) > 0:
		create_jobs(jobs, failed_dependecies, initial_flow_attribs)
		get_all_dependencies(jobs, failed_dependecies, initial_flow_attribs)

def get_pending_jobs(log, failed_jobs, initial_flow_attribs):
	pending = []
	for task, attribs in log.items():
		if attribs['start'][-1] == 0  and attribs['end'][-1] == 0: pending.append(task)
	pending_to_launch = [] #PENDING jobs that for some reason has not been launched although their depedencies has been executed succesful.
	for job in pending:
		folder, dependencies = initial_flow_attribs[job]
		if len(set(dependencies) & set(failed_jobs)) == 0 or len(set(dependencies) & set(pending)) == 0:
			pending_to_launch.append(job)
	return pending_to_launch
