"""
A grammar that compiles and runs arigfh "lies" subroutines (i.e., primitive
parsers for catalogs.

This uses a binary record def in the RD; this is an element with
the id liesfOutputFormat in about svn revision 2200.
"""

import os
import shutil
import signal
import struct
import sys
import subprocess
import tempfile

from gavo import base
from gavo import utils
from gavo.utils import misctricks

from gavo.grammars.customgrammar import CustomRowIterator


MAIN_CODE = """
      PROGRAM MAIN
      IMPLICIT NONE
      REAL*8        XSTAR8(20)
      REAL*4        XSTAR4(15)
      INTEGER       ISTAR4(10), KENNX4(15), KENNX8(20)
      INTEGER*2     ISTAR2(30)
      INTEGER       CATAN, RC, I
      LOGICAL       EX, DATA_READ

      CATAN = 0
C     open the reference catalogs
      CALL DATA(DATA_READ)
      CALL FILEOP(1, 'katalog' ,'F', EX)
C     cannot write to stdout unformatted, so we use a fifo created
C     by the python end.
      CALL FILEOPDA(330, 'iopipe', 'U', 320, EX)
   1  CALL LIES(1,XSTAR4,XSTAR8,KENNX4,KENNX8,ISTAR2,ISTAR4,RC)
      IF (RC .NE. 0) GOTO 2
      CATAN = CATAN + 1
      ISTAR4(1) = CATAN
      WRITE(330, REC=CATAN)(XSTAR8(I),I=1,14),
     & (KENNX8(I), I=1,12),
     & (XSTAR4(I),I=1,9),
     & (KENNX4(I),I=1,6),
     & (ISTAR4(I),I=1,2),
     & (KENNX8(I),I=13,14),
     & (KENNX4(I),I=7,12),
     & (ISTAR2(I),I=1,30)
      GOTO 1
    2 CONTINUE

      CLOSE(1)
      END
"""


MAKEFILE_CODE = """
%.o: %.f
	gfortran -std=legacy -finit-local-zero -g -std=legacy -c \$<

genrecs: lies.o main.o
	gfortran -std=legacy -o \$@ \$^ -L $(GFHROOT)/ident/identframe -lidfuncs
"""


class Timeout(base.Error):
	pass


def raiseTimeout(signo, frame):
	raise Timeout()


class RowIterator(CustomRowIterator):

	def _buildProgram(self):
		"""copies together the necessary code and builds a fortran binary.
		"""
		sourceDir = os.path.dirname(self.sourceToken)
		os.symlink(os.path.join(sourceDir, "lies.f"), "lies.f")
		os.symlink(os.path.join(sourceDir, "katalog"), "katalog")
		with open("main.f", "w") as f:
			f.write(MAIN_CODE)
		with open("Makefile", "w") as f:
			f.write(MAKEFILE_CODE)
		make = subprocess.Popen(["make"], stdout=subprocess.PIPE,
			stderr=subprocess.PIPE)
		_, errOut = make.communicate()
		if make.returncode:
			raise base.Error("make failed -- fix lies.f (or something else)",
				hint="Here's make's stderr: %s"%errOut)

	def _iterInRecords(self, recLen):
		# flush so genrecs diagnostics turn up in context
		sys.stderr.flush()
		sys.stdout.flush()
		os.mkfifo("iopipe")
		genrecs = subprocess.Popen(["./genrecs"])

		signal.alarm(20)
		try:
			with open("iopipe") as inF:
				while True:
					signal.alarm(20)
					rec = inF.read(recLen)
					if rec=='':
						break
					if len(rec)!=recLen:
						raise base.Error("short output from genrecs: %s"%repr(rec))
					yield rec
		except Timeout:
			sys.stderr.write("Timeout on %s\n"%os.path.basename(self.sourceToken))

		signal.alarm(0)
		if genrecs.returncode:
			raise base.Error("genrecs failed -- fix lies.f (or something else)")

	def _iterRows(self):
		signal.signal(signal.SIGALRM, raiseTimeout)

		# this basically reproduces code from binarygrammar, but I stipulate
		# it's not worth refactoring that to make it reusable here
		fieldDefs = self.grammar.rd.getById("liesfOutputFormat")

		with utils.sandbox(os.path.join(self.grammar.rd.resdir, "run")):
			self._buildProgram()
			fmtStr = fieldDefs.structFormat
			# libuse is made such that everything is written big-endian.
			# the id-files computed by arigfh are little-endian.  I fix
			# the endianess manually; that's easier than to replay
			# the whole definition
			fmtStr = ">"+fmtStr[1:]
			fieldNames = fieldDefs.fieldNames
			for rawRec in self._iterInRecords(fieldDefs.recordLength):
				yield dict(zip(fieldNames, struct.unpack(fmtStr, rawRec)))

		signal.signal(signal.SIGALRM, signal.SIG_DFL)


def makeDataPack(grammar):
	"""recompiles the libraries and sets the GFHROOT env variable.
	"""
	os.environ["GFHROOT"] = grammar.rd.resdir
	#return # uncomment when debugging
	with utils.in_dir(os.path.join(grammar.rd.resdir, "ident", "identframe")):
		subprocess.check_call(["make", "clean"])
		subprocess.check_call(["make"])
