#!/usr/bin/python3

# break OAPATH target src_name
# a1 a2a a2b a3 b1b b2b  splits_folder splits_format_ext splits_printf_ext
# OACACHE from_diagnostics verbose main

# skip_orig origf_name mirror_name
# skip_src allow_untranslated
# skip_comp  launcher compiler web_compiler header  SERVER_PID server_extern server_compiler skip_connect_test sentinel  no_diagnostics only_diagnostics diag_json dump  no_confirm ffdec  no_clean

# a1 a2a a3 b1b b2b
# /  /   3
#    *      *   /
#    a2b

#import ctypes
#import difflib
#import itertools
#import json
#import os
from pathlib import Path
#import pdb
#import re
#import signal
#import shutil
#import socket
#import subprocess
import sys
#import time
#import xml.etree.ElementTree as ET

if len(sys.argv)!=3:
	print(f"Usage: {Path(sys.argv[0]).name} output_folder output_file")
	exit(1)

import os

OAPATH=""
if os.environ.get('OAPATH'):
	OAPATH=os.environ.get('OAPATH')
	#this is windows specific os.add_dll_directory(os.environ.get('OAPATH'))

sentinel=os.environ.get('sentinel')
import ctypes
try: #in LD_LIBRARY_PATH
	lib=ctypes.cdll.LoadLibrary(os.path.join(OAPATH,"liboaas.so"))
except Exception:
	try:
		lib=ctypes.cdll.LoadLibrary(os.path.join(OAPATH,"liboaas.dll"))
		if sentinel: #needed at wine
			import _winapi
			_orig_close_handle = _winapi.CloseHandle
			def _safe_close_handle(h):
				try:
					_orig_close_handle(h)
				except OSError:
					print('sentinel')  # Wine gives bogus thread handles for native Unix children
					pass
					#return _winapi.WAIT_OBJECT_0  # pretend it finished
			_winapi.CloseHandle = _safe_close_handle
	except Exception:
		lib = None
		# print('no liboaas support')
if lib:
	try:
		lib.line.argtypes = [ctypes.c_char_p]; lib.line.restype = ctypes.c_char_p
		lib.startline.argtypes = [ctypes.c_char_p,ctypes.c_char_p]; lib.startline.restype = ctypes.c_char_p
		lib.ratio.argtypes = [ctypes.c_char]; lib.ratio.restype = ctypes.c_char
		#lib.dualmode_reset

		lib.flash_to_openfl.argtypes = [ctypes.c_char]
		lib.mainmodule_boolset.argtypes = [ctypes.c_char]
		lib.verbose.argtypes = [ctypes.c_char]

		lib.solve1.argtypes = [ctypes.c_char_p]; lib.solve1.restype = ctypes.c_char_p
		lib.solve2.argtypes = [ctypes.c_char_p]; lib.solve2.restype = ctypes.c_char_p
		lib.solve4.argtypes = [ctypes.c_char_p]; lib.solve4.restype = ctypes.c_char_p
		lib.solve7.argtypes = [ctypes.c_char_p,ctypes.c_char_p]; lib.solve7.restype = ctypes.c_char_p

		lib.dualmode.restype = ctypes.c_char
		lib.singlemode.restype = ctypes.c_char
		lib.firstline.restype = ctypes.c_char
		lib.postpone_char.restype = ctypes.c_char
		lib.resolve.restype = ctypes.c_char
		lib.sep.restype = ctypes.c_char
	except Exception:
		print('lib oaas error')
		exit(1)

if os.environ.get('break'): import pdb; pdb.set_trace()

dest=sys.argv[1]
def get_module_insens(fl):
	f=os.path.basename(os.path.dirname(fl))
	f=f[0].upper() + f[1:]
	return f
def hx_exte(fl):
	return get_module_insens(fl)+'.hx'
def hx_ext(fl):
	return os.path.join(dest,hx_exte(fl))
out_file_name=sys.argv[2]

def changeable(a,b,c): globals()[a + b] = c if os.environ.get(a + b) == None else os.environ.get(a + b)
changeable('a','1','/')
changeable('a','2a','/')
changeable('a','2b','*')
changeable('a','3','3')
changeable('b','1b','*')
changeable('b','2b','/')
def premade_formats():
	#environ or defaults at edor and src

	splits_folder=os.environ.get('splits_folder')
	if not splits_folder:
		splits_folder='osrc'

	splits_format_ext=os.environ.get('splits_format_ext')
	if not splits_format_ext:
		splits_format_ext='split'

	#and here at src
	splits_printf_ext=os.environ.get('splits_printf_ext')
	if not splits_printf_ext:
		splits_printf_ext='format'

	def ext(a): return '' if a=='' else os.extsep+a     #notice: os.extsep is not directly connected with my edor and src, is like a guardian?
	a=('' if splits_folder=='' else splits_folder+os.sep)

	return (a+os.path.splitext(out_file_name)[0]+ext(splits_format_ext),a+out_file_name+ext(splits_printf_ext))
def splits_data(orig=False,dirs=''):
	splits_file, splits_mix = premade_formats()
	#for filename in os.listdir(src):
	with open(dirs+splits_file) as splits_file:
		splits_file_data=splits_file.read()
		files = splits_file_data.split('\x00')
		files.pop() # Remove the last empty string
		files_src = [str(Path(src_name)) + os.sep + str(Path(*Path(f).parts[1:])) for f in files]
		if not orig:
			return files_src
	files_mirror = [str(Path(mirror_name)) + os.sep + str(Path(*Path(f).parts[1:])) for f in files]
	with open(dirs+splits_mix) as splits_mix:
		splits_mix_data=splits_mix.read()
		texts = splits_mix_data.split('\x00')
		if texts[-1]!=texts[-2]: #when last two are \0\0 is not the same like text\0
			texts.pop()
	return (files,files_mirror,files_src,texts)

target=os.environ.get('target')
if target:
	if target!="html5":
		if target!="webassembly":
			print('target not available')
			exit(1)

src_name=os.environ.get('src_name')
if not src_name:
	src_name='src'

import shutil

skip_orig='skip_orig'
if not os.environ.get(skip_orig):
	origf_name=os.environ.get('origf')
	if not origf_name:
		origf_name='origf'
	mirror_name=os.environ.get('mirror_name')
	if not mirror_name:
		mirror_name='mirror'

	files,files_mirror,files_src,texts = splits_data(orig=True)

	import difflib

	for i, f in enumerate(files):
		txt=texts[i]
		if txt:
			origf = None
		else:
			origf = Path(f)
		mirror = Path(files_mirror[i])
		src = Path(files_src[i])
		if not mirror.exists():
			# create parent folders
			mirror.parent.mkdir(parents=True, exist_ok=True)
			src.parent.mkdir(parents=True, exist_ok=True)

			# create mirror/src copy
			if origf:
				shutil.copy(origf, mirror) # or copy2: Identical to copy() except that copy2() also attempts to preserve file metadata.
				shutil.copy(origf, src)
			else:
				with open(mirror,"w") as fl:
					fl.write(txt)
				with open(src,"w") as fl:
					fl.write(txt)
		else:
			old = mirror.read_text().splitlines(keepends=True)
			if origf:
				new = origf.read_text().splitlines(keepends=True)
			else:
				new = txt.splitlines(keepends=True)
			out = src.read_text().splitlines(keepends=True)
			matcher = difflib.SequenceMatcher(None, old, new)
			offset = 0
			#        old     new
			for tag, i1, i2, j1, j2 in matcher.get_opcodes():
				if tag == "equal":
					continue
				print(tag)
				print("old:", old[i1:i2])
				print("new:", new[j1:j2])

				i1 += offset
				i2 += offset
				if tag == "insert":
					out[i1:i1] = new[j1:j2]
					offset += (j2 - j1)
					continue
				if tag == "replace":
					out[i1:i2] = new[j1:j2]
					offset += (j2 - j1) - (i2 - i1)
					continue
				if tag == "delete":
					del out[i1:i2]
					offset -= (i2 - i1)
					continue
				print('?')
				exit(1)
			src.write_text(''.join(out))#os.linesep
			mirror.write_text(''.join(new))

terminations="\r\n"

OACACHE=os.environ.get('OACACHE')
if not OACACHE:
	OACACHE="__oacache__"
from_diagnostics='from_diagnostics'
f_d=os.environ.get(from_diagnostics)

import time
def stop_server(s):
	try:
		pid = int(s)
	except (TypeError,ValueError):  #TypeError at exiting without a server , ValueError at server_extern
		return
	if sys.platform == "win32":
		_stop_windows(pid)
	else:
		_stop_posix(pid)
def _stop_posix(pid):
	import signal
	try:
		os.kill(pid, signal.SIGTERM)
		os.kill(pid, 0)
		time.sleep(1)
		os.kill(pid, 0)
		os.kill(pid, signal.SIGKILL)
	except ProcessLookupError:
		return
	os.waitpid(pid, 0)
def _stop_windows(pid):
	PROCESS_TERMINATE = 0x0001
	SYNCHRONIZE = 0x00100000
	kernel32 = ctypes.windll.kernel32
	handle = kernel32.OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, False, pid)
	if not handle:
		return  # no such process
	try:
		time.sleep(1)
		kernel32.TerminateProcess(handle, 1)
		kernel32.WaitForSingleObject(handle, 5000)  # wait up to 5s, ms
	finally:
		kernel32.CloseHandle(handle)

def exiting_serv(s):
	stop_server(s)
	exit(1)
def exiting():
	exiting_serv(server_pid)

verbose=os.environ.get('verbose')
if verbose:
	print(sys.argv[1]+' '+sys.argv[2])
main=os.environ.get('main')
if not main:
	main='Main'

skip_src='skip_src'
if not os.environ.get(skip_src):
	if lib:
		if target:
			lib.flash_to_openfl(True)
		if verbose:
			lib.verbose(True)

	def exiting_verif():
		exiting_serv(os.environ.get('SERVER_PID'))
	allow_untranslated=os.environ.get('allow_untranslated')
	def make_line(bf):
		bf = bf.rstrip(terminations.encode()) #this is extra at inline comments
		byts = bytes(bf)
		if len(stored):
			bf=lib.startline(bytes(stored),byts)
			stored.clear()
		else:
			bf=lib.line(byts)
		if not allow_untranslated:
			if byts == bf:
				r = lib.ratio(False)
				if ord(r) !=  100: #can be blank closing comment
					print("Untranslated. 'if not os.environ.get('allow_untranslated') failed")
					exiting_verif()
		return bf+os.linesep.encode()
	def singleline():
		bf=chars[:pos]
		if lib:
			dfile.write(make_line(bf))
		else:
			dfile.write(bf)

	try:
		os.mkdir(dest)
	except FileExistsError:
		pass


	multiline_backjump=len(b1b.encode())+len(b2b.encode())

	stored=bytearray()
	multiline_backjump_start=len(a1.encode())+len(a3.encode())
	singleline_backjump_start=multiline_backjump_start
	multiline_backjump_start+=len(a2a.encode())
	singleline_backjump_start+=len(a2b.encode())

	was_multiline=False
	dual_mode=False
	single_mode=False

	files = splits_data()
	module_files = {}
	for f in files:
		module_files.setdefault(get_module_insens(f), []).append(f)
	module_text_cache = {}  # lazy: module -> concatenated raw lines of all its .as files, in Main.hx line order
	def get_module_text(mod):
		if mod not in module_text_cache:
			merged = []
			for cf in module_files[mod]:
				with open(cf) as sf:
					merged.extend(sf.readlines())
			module_text_cache[mod] = merged
		return module_text_cache[mod]
	for f in files:
		if lib:
			lib.mainmodule_boolset(get_module_insens(f)==main)
		with open(f) as sfile:
			text=sfile.readlines()
		if not f_d:
			with open(hx_ext(f),'ab') as dfile: #haxe will not do for .as
				mode=0
				for line in text:
					chars = bytearray(len(line.encode())) #'utf-8'
					pos = 0
					for c in line:
						ch=c.encode()
						positions=len(ch)
						for j in range(0,positions):
							chars[pos]=ch[j]
							pos+=1
						if mode==0:
							if c==a1:
								mode=1
							elif dual_mode:
								if c==b1b:
									mode=7
						elif mode==1:
							if c==a2a:
								mode=2
							elif c==a2b:
								mode=3
							else:
								mode=0
						elif mode==2 or mode==3:
							if c==a3:
								if mode==2:
									xpos=singleline_backjump_start
								else:
									xpos=multiline_backjump_start
									if lib:
										lib.dualmode_reset() #here is the less intensive place to reset a one line dual mode, can also signal at startline if is mode 4 or 6 but is intensive
								stored=chars[:pos-xpos]
								mode*=2
								pos=0    #notice: both //3 and /*3 will reset the line
							else:
								mode=0
						elif mode==6:
							if c==b1b:
								mode=7
						elif mode==7:
							if c==b2b:
								mode=0
								pos-=multiline_backjump
								singleline()
								pos=0
								was_multiline=True
								dual_mode=False
							elif dual_mode:
								mode=0
							else:
								mode=6
					if mode==4:
						mode=0
						singleline()
						if lib:
							single_mode=lib.singlemode()
							single_mode=single_mode[0]
					elif mode==6:
						singleline()
						if lib:
							dual_mode=lib.dualmode() #c_char
							dual_mode=dual_mode[0] #good at mode 7 to 6, else can be None/pointer
							if dual_mode:
								mode=0
					else:
						if single_mode:
							dfile.write(os.linesep.encode())
						else:
							bf=chars[:pos]
							if not was_multiline:
								dfile.write(bf)
							else:
								bf=bf.rstrip(terminations.encode()) #was already added one separator
								dfile.write(bf)
								was_multiline=False
				#here can test if multiline was closed
		else:
			mod = get_module_insens(f)
			folder = Path(OACACHE)
			file = folder / folder / mod
			if file.exists():
				lines = file.read_text().splitlines()
				with open(hx_ext(f),'rb') as dfile:
					out_text = dfile.readlines()
				module_text = get_module_text(mod)
				for line in lines:
					#lib is because from_diagnostics tested that
					line=int(line)
					in_text=module_text[line] #line is a Main.hx-global index, so pull from the merged module text, not this single file's text
					parts=in_text.split((a1+a2a+a3))
					if len(parts)!=2:
						print('error at rerun')
						exiting_verif()
					stored = bytearray(parts[0].encode())
					out_text[line]=make_line(parts[1].encode())
				with open(hx_ext(f),'wb') as dfile:
					dfile.writelines(out_text)
				os.remove(file)
	if lib:
		lib.ratio(True)

if not os.environ.get('skip_comp'):
	import subprocess
	def srun(a):
		r=subprocess.run(a)
		if r.returncode:
			print('error')
			exit(r.returncode)
	compiler=os.environ.get('compiler')
	if not compiler:
		compiler='haxe'

	c_dir=os.getcwd() #haxe says no to abs path
	dest_file=os.path.realpath(out_file_name)

	try:
		os.chdir(dest)
	except FileNotFoundError:
		print('cannot compile without dest') # if skip_src, and was not
		exit(1)
	server_pid=os.environ.get('SERVER_PID')
	if not server_pid:
		server_extern=os.environ.get('server_extern')
		if not server_extern:
			import socket
			server_compiler=os.environ.get('server_compiler')
			if not server_compiler:
				server_compiler=compiler
			server = subprocess.Popen([server_compiler, "--wait", "6000"])
			server_pid=str(server.pid)
			skip_connect_test=os.environ.get('skip_connect_test')
			if skip_connect_test:
				wait_some=int(skip_connect_test)
				time.sleep(wait_some)
			else:
				wait_some=10  #wine is longer
				while wait_some:
					try:
						with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
							s.settimeout(1)
							if s.connect_ex(("127.0.0.1", 6000)) == 0:
								break
						#with socket.create_connection(("127.0.0.1", 6000),timeout=1): #timeout is at success
						#	break
					except: # OSError:
						time.sleep(1)
						wait_some-=1
						continue
				if not wait_some:
					print('server error')
					exiting()

	baser=[compiler,'--connect','6000','-swf',dest_file,'-main',main,'-D','flash_strict']
	#                                                                   this flag for _x to be error
	launcher=os.environ.get('launcher')
	if launcher:
		baser.insert(0,launcher)

	hd=os.environ.get('header')
	if hd:
		baser.extend(['-D','swf-header='+hd])
		# -swf-header 960:640:60:f68712 -swf-version 15
		#w:h:fps:rgb

	import tempfile
	def prun(a, capture_output=False):
		if verbose:
			print(a)
		if not sentinel:
			kwargs = {}
			if capture_output:
				kwargs['stderr'] = subprocess.PIPE
			return subprocess.run(a,**kwargs)
		else: #wine always Invalid handle and never reaches return
			done_path = sentinel+".done"
			if capture_output:
				err_path = sentinel+".err"
			else:
				err_path = '0'
			# a = ['/bin/sh', '/home/bc/a/whaxe', ...actual haxe args...]
			# wrapper script expects: whaxe <done_path> <err_path> <haxe args...>
			full_cmd = a[:2] + [done_path, err_path] + a[2:]
			#whaxe, more at tests/as3/aw

			#kwargs = {'stdin': subprocess.DEVNULL, 'stdout': subprocess.DEVNULL, 'stderr': subprocess.DEVNULL}
			proc = subprocess.Popen(full_cmd) #, **kwargs)

			#start = time.monotonic()
			poll_interval=0.5
			while not os.path.exists(done_path):
				#if timeout is not None and time.monotonic() - start > timeout:
				#	break
				time.sleep(poll_interval)

			#returncode = 0
			#if os.path.exists(done_path):
			with open(done_path) as f:
				returncode = int(f.read()) #.strip() or "0")
			os.remove(done_path)

			result = subprocess.CompletedProcess(a, returncode)

			if capture_output:
				result.stderr = open(err_path).read() #if os.path.exists(err_path) else b""
				os.remove(err_path)
			return result
	def crun(a,capture_output=False):
		r=prun(a,capture_output=capture_output)
		if r.returncode:
			print('error')
			exiting()
		return r

	def build():
		#the real, output-producing build. diagnostics themselves (the '--display' queries below) always stay on haxe/baser regardless of target
		if target:
			web_compiler=os.environ.get('web_compiler')
			if not web_compiler:
				web_compiler='openfl'
			cur=os.getcwd()
			os.chdir(c_dir) #openfl expects project.xml at the original invocation dir, not dest
			try:
				return prun([web_compiler,'build',target])
			finally:
				os.chdir(cur)
		return prun(baser)
	def ifbuild():
		r=build()
		if r.returncode:
			exiting()

	o_d=os.environ.get('only_diagnostics')
	if os.environ.get('no_diagnostics'):
		ifbuild()
		os.chdir(c_dir) #same like to other plus for diagnostics all cases
	else:
		if f_d:
			f_d=int(f_d)
		if not o_d and not f_d:
			r=build()
		if o_d or f_d or r.returncode:
			if not lib:
				print('missing oaas lib')
				exiting()
			def diags():
				import json
				import itertools

				if target=='html5':
					baser.extend(['-D','html5']) #so #if html5 / #else / #end resolves correctly for diagnostics queries

				_id_counter = itertools.count(1)
				diag_json=os.environ.get('diag_json')

				def diag_request(file,mode,offset=0):
					if not diag_json:
						return file+'@'+str(offset)+'@'+mode
					# Legacy "file@offset[@mode]" suffix -> JSON-RPC "display/..." method.
					# Modes without an explicit @suffix (plain "file@offset") default to completion.
					#_MODE_TO_METHOD = {
					#    "":             "display/completion",     # file@offset          -> completion
					#    "position":     "display/definition",     # file@offset@position -> goto definition
					#    "usage":        "display/references",     # file@offset@usage    -> find references
					#    "type":         "display/hover",          # file@offset@type     -> hover/type info
					#    "signature":    "display/signatureHelp",
					#    "diagnostics":  "display/diagnostics",    # usually file-only, offset ignored/0
					#    "metadata":     "display/metadata",       # project-wide, no offset needed
					#    "module-symbols": "display/moduleSymbols",
					#}

					# Diagnostics and metadata are project/file scoped, not offset scoped.
					if mode == "diagnostics":
						method="display/diagnostics"
						params = {"file": file}
					#elif method == "display/metadata":
					#	params = {}
					else:
						method="display/definition"
						params = {"file": file, "offset": offset}
					request = {
						"jsonrpc": "2.0",
						"id": next(_id_counter),
						"method": method,
						"params": params,
					}
					return json.dumps(request)

				baser.append('--display')
				files=splits_data(dirs='../')
				module_files = {}
				for f in files:
					module_files.setdefault(get_module_insens(f), []).append(f)

				diagnostics=[]
				requested_modules=set()
				for f in files:
					mod = get_module_insens(f)
					if mod in requested_modules: #several .as files can share one Main.hx, ask haxe about it once
						continue
					requested_modules.add(mod)
					baser.append(diag_request(hx_exte(f),'diagnostics'))
					p=crun(baser,capture_output=True)
					baser.pop()
					try:
						result=json.loads(p.stderr)
					except json.decoder.JSONDecodeError:
						print('malformed json')
						exiting()
					if isinstance(result, dict):
						result = result.get("result")
						result = result["result"]  # dev build: unwrap the timestamp wrapper
					# stable build: already the bare list
					diagnostics.extend(result) #append wrong the type, error throw later
				os.chdir(c_dir)

				file_cache = {} #keyed by module: merged lines of ALL of that module's .as files, in Main.hx line order
				file_spans = {} #keyed by module: [(as_file, line_count), ...] to split file_cache[module] back apart on write-back
				hx_cache = {} #keyed by module: raw lines of the dest .hx file itself (read-only, never written back here)
				touched = set() #modules, not files
				line_cache = {}
				line_cache_last = {}

				def save_line(module, line):
					folder = Path(OACACHE)
					folder.mkdir(exist_ok=True)
					file = folder / module  # this is not + , on windows is backslash

					if module not in line_cache:
						if file.exists():
							line_cache[module] = set(file.read_text().splitlines())
						else:
							line_cache[module] = set()
						line_cache_last[module] = set()

					cache = line_cache[module]
					cache_last = line_cache_last[module]

					line = str(line)

					if not line in cache_last:
						cache_last.add(line)

						last = folder / folder
						last.mkdir(exist_ok=True)
						last_file = last / module
						with open(last_file, "a", newline="") as fp: #newline="" good on wine else will be \r\r\n
							fp.write(line + os.linesep)

					if line in cache:
						return #False

					cache.add(line)
					with open(file, "a", newline="") as fp:
						fp.write(line + os.linesep)
					return #True

				import re
				def diag():
					pp=''
					firstline=None
					if kind==7:
						# this is unstable: module=d["args"]["moduleType"]["name"]
						#field=d["args"]["entries"][0]["fields"][0]["field"]
						#module=os.path.splitext(field["pos"]["file"])[0]

						positions=d["range"]["end"]
						line=positions["line"]

						value=d["args"]["entries"][0]["fields"][0]["field"]["name"]
					else:
						positions=d["range"]["start"]
						line=positions["line"]
						args=d["args"]
						if kind==1:
							if not args:
								return None
							value=args[0]["name"]
						else:
							value=args.split("\n")[0] #JSON doesn't use the host OS's line-ending convention.
							if kind==2:
								character=positions["character"]

								# 3.13+ #src = Path(fullfilename).read_text(newline='') #encoding="utf-8" #newline for \r\n wine
								with open(fullfilename, 'r', newline='') as f:  # encoding="utf-8" #newline for \r\n wine
									src = f.read()

								lines = src.splitlines(keepends=True)
								# get text before the target line
								before = "".join(lines[:line])
								# add characters in the target line
								offset_chars = len(before) + character
								# convert character offset -> UTF-8 byte offset
								offset_bytes = len(src[:offset_chars].encode()) #"utf-8"

								pp='('+str(line)+':'+str(character)+' at '+str(offset_bytes)+') to '

								os.chdir(dest)
								baser.append(diag_request(filename,'position',offset_bytes))
								result=prun(baser,capture_output=True)
								baser.pop()
								os.chdir(c_dir)
								#if not result.returncode: # must match ok
								result = result.stderr
								if not diag_json:
									try:
										result = ET.fromstring(result).find('pos') #here is the except
										if result is not None: #else,example,TextFormatAlign
											result=result.text
											if result!='(unknown)': #jammy case
												#lineparts=result.split(':') #windows... F:...
												m = re.match(r'^(.+):(\d+):', result)
												if m.group(1)==fullfilename: #is another file line else, also at first case
													firstline=line
													line=int(m.group(2))-1 # from human
									except ET.ParseError: #jammy case
										pass
								else:
									result = json.loads(result) #here are situations like addChild(label) where label is not going to createTextField yet
									if not "error" in result:
										result = result.get("result")  #same as before
										result = result["result"]
										if isinstance(result,list): #stage.addChild(text); without createEmptyMovieClip
											if result: # 'Too many arguments' var arcon=new Array(1,2,3);
												firstline=line
												line=result[0]["range"]["start"]["line"]
					return value,line,positions,pp,firstline

				dump=os.environ.get('dump')
				unresolved=False;resolved=False
				tr_sep = a1 + a2a + a3

				import xml.etree.ElementTree as ET

				for file in diagnostics:
					if dump:
						print(json.dumps(file,indent=10))
					fullfilename=file["file"]
					if fullfilename: #at very bad code can: {'file': None,
						filename=os.path.basename(fullfilename)
						module=os.path.splitext(filename)[0]
						print(module)
						for d in file["diagnostics"]:
							if d["severity"] == 1:
								solved = None
								kind=d["kind"]
								if kind in [1,2,4,7]:
									values=diag()
									if values:
										value,line,positions,pp,firstline=values
										if verbose:
											print(str(kind)+' '+pp+str(line)+' '+value+('' if not firstline else (' '+str(firstline))))
										#now is like: fix one error will fix another 4
										if module in module_files:
											if module not in file_cache:
												merged = []
												spans = []
												for cf in module_files[module]:
													with open(cf) as fp:
														flines = fp.readlines()
													spans.append((cf, len(flines)))
													merged.extend(flines)
												file_cache[module] = merged
												file_spans[module] = spans
												with open(hx_ext(module_files[module][0])) as hf: #one dest .hx per module, several .as can share it
													hx_cache[module] = hf.readlines()
											lines = file_cache[module] #global line numbers index straight into this merged list

											if kind==4:
												solved = lib.solve4(value.encode())
											elif kind==2:
												solved = lib.solve2(value.encode())
												if solved: # it is no having ""
													if lib.firstline()[0]:
														if firstline: #at compare without exclamations is from if, not this case
															line=firstline
											elif kind==1:
												solved = lib.solve1(value.encode())

											linestr = lines[line]
											if linestr[-1] in terminations:
												linestr = linestr.rstrip(terminations)
												has_term=True
											else:
												has_term=False

											if kind==7:
												solved = lib.solve7(value.encode(),linestr[positions["character"]:].encode())

											if not solved and solved is not None: #solved==None means abandon this diagnostic entirely; otherwise try the postpone_char path
												#this case is with the postpone_char. or normal
												solved = lib.resolve()
												part1, delim, part2 = linestr.partition(tr_sep)
												if delim:
													comparator = solved.decode()
													sep=lib.sep().decode()
													tokens = part2.split(sep)
													n = lib.postpone_char().decode()
													nn = len(n)
													#c0 = comparator[0] # to compare less, like sw , sh
													for i,token in enumerate(tokens):
														token = token.lstrip()          # remove leading whitespace
														if isinstance(solved, bytes):
															against=token[0:nn] #token.startswith(n): # can be '_ val' , so, use startswith
															if against == n:
																solved = comparator
																token = solved + token[nn:]
															#elif against == c0: #this comparation was going with another case at next code
															#	break #example at rect, else will be r x+r+r
														tokens[i]=token
													if not isinstance(solved, bytes):
														linestr = part1 + delim + sep.join(tokens)
													#elif against == c0:
													#	break #example at rect, else will be r+r+r
											if solved is not None: #was the 'break' path when solved==None: nothing further to do for this diagnostic
												rslvd=True
												if isinstance(solved, bytes):
													solved = solved.decode()
													try:#still can be a line with markers from orig
														part1, part2 = linestr.split(tr_sep) #will throw here
														if not part1.strip(' \t'): #part1 is only whitespace: pull the real prefix from the dest .hx line, not this blank src part1
															hx_line = hx_cache[module][line]
															hx_line = hx_line.rstrip(terminations)
															linestr = hx_line + tr_sep + solved
														else:
															s1 = solved[1:]
															s0 = solved[0]
															sep=lib.sep().decode()
															tokens = part2.split(sep)
															token_level_solved=False
															for i,token in enumerate(tokens):
																if token[0] == s0:
																	if s1: # a case sw then sh, must combine them
																		if not s1 in token[1:]:
																			tokens[i] = token + s1
																			token_level_solved=True
																			break
																	rslvd=False
																	break
															if rslvd:
																if not token_level_solved:
																	#insert after postpone ?
																	#n = lib.postpone_char().decode()
																	#nn = len(n)
																	#insert_at = len(tokens)
																	#for i,token in enumerate(tokens):
																	#	if token[0:nn] == n: #postpone_char token must stay last: insert the new one before it
																	#		insert_at = i
																	#		break
																	#tokens.insert(insert_at,solved)
																	tokens.append(solved)
																linestr = part1 + tr_sep + sep.join(tokens)
													except:
														linestr = linestr + tr_sep + solved
												if rslvd:
													save_line(module,line)
													lines[line] = linestr + (os.linesep if has_term else "")
													touched.add(module) #was touched.add(f) - one module can span several .as files sharing one Main.hx
													resolved=True
											#no more 'break' here: previously ended the per-file search loop, which no longer exists
								if not solved:
									unresolved=True
				if resolved:
					for module in touched:
						merged = file_cache[module]
						offset = 0
						for cf, cnt in file_spans[module]: #split the merged module lines back to each of its .as files
							with open(cf, "w", newline="") as fp: # , newline="" was already explained
								fp.writelines(merged[offset:offset+cnt])
							offset += cnt
					if o_d:
						return
					if unresolved:
						os.environ[from_diagnostics] = '1'
					else:
						os.environ[from_diagnostics] = '0' #needed at src. Setting SetEnvironmentVariable(name, "") on Windows is documented to delete the variable
						#so an int() is required

					os.environ[skip_orig] = "1"
					os.environ["SERVER_PID"] = server_pid if server_pid else '' # server_extern, then here = None is exception

					if os.environ.get("no_execv"):
						result = subprocess.run([sys.executable] + sys.argv)
						sys.exit(result.returncode)
					os.execv(sys.executable, [sys.executable] + sys.argv)
					#print("never reached")
				if unresolved:
					print("diagnostics failed")
					exiting()
				if f_d: #still can be all ok after all diagnostics
					baser.pop()
					os.chdir(dest)
					ifbuild()
					os.chdir(c_dir)
			diags()
		else:
			os.chdir(c_dir) #back for clean, and maybe ffdec is relative, dest_file is realpath

	stop_server(server_pid)

	if not os.environ.get('no_confirm') and not o_d and not target: #only diagnostics is a dry run at haxe; ffdec/swf check doesn't apply to html5 output
		#confirm is as3

		dest_file_tmp=dest_file+'.tmp'
		ffdec=os.environ.get('ffdec')
		if not ffdec:
			ffdec='ffdec'
		srun([ffdec,'-decompress',dest_file,dest_file_tmp])
		with open(dest_file_tmp,'rb') as file:
			file.seek(8)
			#rect
			b=file.read(1)
			n=b[0]>>3 # 0xff >> 1 is 0x7f
			n=5+(n*4)
			if n%8:
				n=int(n/8)+1
			else:
				n=n/8
			file.seek(n+3,1)
			#tag
			tag=file.read(2)
			tg=tag[1]<<2
			tg_low=tag[0]>>6
			tg|=tg_low
			if tg!=69:
				print('no FileAttributes tag')
				exit(1)
			tg=tag[0]&0x3f
			if tg==0x3f:
				print('malformed FileAttributes tag')
				exit(1)
			#tag data
			tg=file.read(1)
			if (tg[0]&8)==0:
				print('ActionScript3 bit is not set')
				exit(1)

		os.remove(dest_file_tmp)

	if not os.environ.get('no_clean'):
		files=splits_data() #if not files , are also at: orig src diagnostics
		removed=set()
		for f in files:
			mdl=hx_ext(f)
			if mdl in removed: #several .as files can share one Main.hx
				continue
			removed.add(mdl)
			os.remove(mdl)
		os.rmdir(dest)
		shutil.rmtree(OACACHE, ignore_errors=True)
