'''

Functions for reading and writing data.

'''


from os import listdir as _listdir
from os import path as _path
import re as _re
import json as _json
from .fixation import FixationSequence as _FixationSequence
from .text import TextBlock as _TextBlock

def read(file_path):
	'''

	Read in a JSON file. `eyekit.fixation.FixationSequence` and
	`eyekit.text.TextBlock` objects are automatically decoded and
	instantiated.
	
	'''
	with open(file_path, encoding='utf-8') as file:
		data = _json.load(file, object_hook=_eyekit_decoder)
	return data

def write(data, file_path, compress=True):
	'''

	Write arbitrary data to a JSON file. If `compress` is `True`, the
	file is written in the most compact way; if `False`, the file will be
	larger but more human-readable. `eyekit.fixation.FixationSequence`
	and `eyekit.text.TextBlock` objects are automatically serialized.
	
	'''
	if compress:
		indent = None
		separators = (',', ':')
	else:
		indent = '\t'
		separators = (', ', ' : ')
	with open(file_path, 'w', encoding='utf-8') as file:
		_json.dump(data, file, default=_eyekit_encoder, ensure_ascii=False, indent=indent, separators=separators)

def import_asc(file_path, trial_begin_var, trial_begin_vals, extract_vars=[]):
	'''

	Import a single ASC file or a directory of ASC files. The importer
	looks for a `trial_begin_var` that is set to one of the
	`trial_begin_vals`, and then extracts all `EFIX` lines that occur
	within the subsequent `START`–`END` block. Optionally, you can
	specify other variables that you want to extract, resulting in
	imported data that looks like this:
	```
	{
	  "trial_0" : {
	    "trial_type" : "Experimental",
	    "passage_id" : "passage_a",
	    "response" : "yes",
	    "fixations" : FixationSequence[[368, 161, 208], ..., [562, 924, 115]]
	  }
	}
	```
	
	'''
	if _path.isfile(file_path):
		file_paths = [file_path]
	else:
		file_paths = [_path.join(file_path, filename) for filename in _listdir(file_path) if filename.endswith('.asc')]
	if isinstance(trial_begin_vals, str):
		trial_begin_vals = [trial_begin_vals]
	trial_line_regex = _re.compile(r'^.+?TRIAL_VAR\s(?P<var>(' + '|'.join([trial_begin_var] + extract_vars) + r')?)\s(?P<val>.+?)$')
	efix_line_regex = _re.compile(r'^EFIX R\s+(?P<stime>.+?)\s+(?P<etime>.+?)\s+(?P<duration>.+?)\s+(?P<x>.+?)\s+(?P<y>.+?)\s')
	data = {}
	for file_path in file_paths:
		curr_trial = {}
		start_flag = False
		with open(file_path) as file:
			for line in file:
				if start_flag:
					if line.startswith('EFIX'):
						line_match = efix_line_regex.match(line)
						if line_match:
							curr_trial['fixations'].append((int(round(float(line_match['x']), 0)), int(round(float(line_match['y']), 0)), int(line_match['duration'])))
					elif line.startswith('END'):
						curr_trial['fixations'] = _FixationSequence(curr_trial['fixations'])
						data['trial_%i'%len(data)] = curr_trial
						start_flag = False
						curr_trial = {}
				else:
					if line.startswith('MSG'):
						line_match = trial_line_regex.match(line)
						if line_match:
							if line_match['var'] == trial_begin_var and line_match['val'] not in trial_begin_vals:
								continue
							curr_trial[line_match['var']] = line_match['val']
					elif line.startswith('START') and trial_begin_var in curr_trial:
						start_flag = True
						curr_trial['fixations'] = []
	return data

def _eyekit_encoder(obj):
	'''
	Convert a `FixationSequence` or `TextBlock` object into something JSON
	serializable that can later be decoded by _eyekit_decoder().
	'''
	if isinstance(obj, _FixationSequence):
		return {'__FixationSequence__': obj._serialize()}
	if isinstance(obj, _TextBlock):
		return {'__TextBlock__': obj._serialize()}
	raise TypeError(f'Object of type {obj.__class__.__name__} is not JSON serializable')

def _eyekit_decoder(obj):
	'''
	Decode an object into a `FixationSequence` or `TextBlock` if the key
	implies that it is one of those types.
	'''
	if '__FixationSequence__' in obj:
		return _FixationSequence(obj['__FixationSequence__'])
	if '__TextBlock__' in obj:
		return _TextBlock(**obj['__TextBlock__'])
	return obj
