#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# AWL simulator - LinuxCNC HAL module
#
# Copyright 2013 Michael Buesch <m@bues.ch>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

from __future__ import division, absolute_import, print_function, unicode_literals
from awlsim.core.compat import *

import sys
import os
import getopt

from awlsim.core.main import AwlSim
from awlsim.core.parser import AwlParser
from awlsim.core.util import AwlSimError, AwlParserError, printInfo, printWarning, printError, str2bool
from awlsim.core.version import VERSION_MAJOR, VERSION_MINOR

try:
	import hal as LinuxCNC_HAL
except ImportError as e:
	printError("Failed to import LinuxCNC HAL module: %s" % str(e))
	sys.exit(1)


# Check presence of LinuxCNC. Returns True, if LinuxCNC is detected.
# Raises KeyboardInterrupt, is LinuxCNC is not detected.
def checkLinuxCNC():
	for lockname in ("/tmp/linuxcnc.lock", "/tmp/emc.lock"):
		try:
			os.stat(lockname)
			return True
		except OSError:
			pass
	if not opt_checkcnc:
		# The check is disabled. Return success.
		return True
	printError("LinuxCNC: doesn't seem to be running")
	raise KeyboardInterrupt

# Create the LinuxCNC HAL pins
def createHalPins(hal,
		  inputBase, inputSize,
		  outputBase, outputSize):
	HAL_BIT, HAL_U32, HAL_S32, HAL_FLOAT = \
		LinuxCNC_HAL.HAL_BIT, LinuxCNC_HAL.HAL_U32, \
		LinuxCNC_HAL.HAL_S32, LinuxCNC_HAL.HAL_FLOAT
	HAL_IN, HAL_OUT, HAL_RO, HAL_RW = \
		LinuxCNC_HAL.HAL_IN, LinuxCNC_HAL.HAL_OUT, \
		LinuxCNC_HAL.HAL_RO, LinuxCNC_HAL.HAL_RW

	# Create the input pins
	for i in range(inputBase, inputBase + inputSize):
		for bit in range(8):
			hal.newpin("input.bit.%d.%d" % (i, bit), HAL_BIT, HAL_IN)
			hal.newparam("input.bit.%d.%d.active" % (i, bit), HAL_BIT, HAL_RW)
		if inputBase + inputSize - i < 4:
			continue
		hal.newpin("input.u32.%d" % i, HAL_U32, HAL_IN)
		hal.newparam("input.u32.%d.active" % i, HAL_BIT, HAL_RW)
		hal.newpin("input.s32.%d" % i, HAL_S32, HAL_IN)
		hal.newparam("input.s32.%d.active" % i, HAL_BIT, HAL_RW)
		hal.newpin("input.float.%d" % i, HAL_FLOAT, HAL_IN)
		hal.newparam("input.float.%d.active" % i, HAL_BIT, HAL_RW)

	# Create the output pins
	for i in range(outputBase, outputBase + outputSize):
		for bit in range(8):
			hal.newpin("output.bit.%d.%d" % (i, bit), HAL_BIT, HAL_OUT)
		if outputBase + outputSize - i < 4:
			continue
		hal.newpin("output.u32.%d" % i, HAL_U32, HAL_OUT)
		hal.newpin("output.s32.%d" % i, HAL_S32, HAL_OUT)
		hal.newpin("output.float.%d" % i, HAL_FLOAT, HAL_OUT)

def loadHardwareModule(awlSim, hal,
		       inputBase, outputBase,
		       inputSize, outputSize):
	# Load the hardware interface layer
	parameters = {
		"hal"			: hal,
		"inputAddressBase"	: str(inputBase),
		"outputAddressBase"	: str(outputBase),
		"inputSize"		: str(inputSize),
		"outputSize"		: str(outputSize),
	}
	hwClass = awlSim.loadHardwareModule("linuxcnc")
	awlSim.registerHardwareClass(hwClass = hwClass,
				     parameters = parameters)

def usage():
	printInfo("awlsim-linuxcnc-hal version %d.%d" % (VERSION_MAJOR, VERSION_MINOR))
	printInfo("")
	printInfo("%s [OPTIONS] AWL-source" % sys.argv[0])
	printInfo("")
	printInfo("Options:")
	printInfo(" -i|--input-size 32      The input area size, in bytes.")
	printInfo(" -I|--input-base 0       The AWL input address base.")
	printInfo(" -o|--output-size 32     The output area size, in bytes.")
	printInfo(" -O|--output-base 0      The AWL output address base.")
	printInfo(" -g|--gui 1/0            Enable/disable the GUI (default off).")
	printInfo("")
	printInfo(" -1|--onecycle           Only run one cycle")
	printInfo(" -C|--check-cnc 1/0      Enable/disable LinuxCNC runtime check (default on).")

def main():
	global opt_checkcnc

	opt_inputSize = 32
	opt_inputBase = 0
	opt_outputSize = 32
	opt_outputBase = 0
	opt_gui = False
	opt_onecycle = False
	opt_checkcnc = True

	try:
		(opts, args) = getopt.getopt(sys.argv[1:],
			"hi:I:o:O:g:C:1",
			[ "help", "input-size=", "input-base=",
			  "output-size=", "output-base=", "gui=",
			  "check-cnc=", "onecycle", ])
	except getopt.GetoptError as e:
		printError(str(e))
		usage()
		return 1
	for (o, v) in opts:
		if o in ("-h", "--help"):
			usage()
			return 0
		if o in ("-i", "--input-size"):
			try:
				opt_inputSize = int(v)
			except ValueError:
				printError("-i|--input-size: Invalid argument")
				return 1
		if o in ("-I", "--input-base"):
			try:
				opt_inputBase = int(v)
			except ValueError:
				printError("-I|--input-base: Invalid argument")
				return 1
		if o in ("-o", "--output-size"):
			try:
				opt_outputSize = int(v)
			except ValueError:
				printError("-o|--output-size: Invalid argument")
				return 1
		if o in ("-O", "--output-base"):
			try:
				opt_outputBase = int(v)
			except ValueError:
				printError("-O|--output-base: Invalid argument")
				return 1
		if o in ("-g", "--gui"):
			opt_gui = str2bool(v)
		if o in ("-1", "--onecycle"):
			opt_onecycle = True
		if o in ("-C", "--check-cnc"):
			opt_checkcnc = str2bool(v)
	if len(args) != 1:
		usage()
		return 1
	awlSource = args[0]

#	try:
#		os.nice(-5)
#	except OSError as e:
#		printWarning("WARNING: Failed to renice LinuxCNC "
#			"awlsim HAL module: %s" % str(e))

	try:
		hal = LinuxCNC_HAL.component("awlsim")
		createHalPins(hal,
			      opt_inputBase, opt_inputSize,
			      opt_outputBase, opt_outputSize)

		if opt_gui:
			from awlsim.gui import MainWindow

			mainwnd = MainWindow.start(initialAwlSource = awlSource)
			s = mainwnd.getSim()
		else:
			p = AwlParser()
			p.parseFile(awlSource)
			s = AwlSim()

		loadHardwareModule(awlSim = s, hal = hal,
				   inputBase = opt_inputBase,
				   outputBase = opt_outputBase,
				   inputSize = opt_inputSize,
				   outputSize = opt_outputSize)

		if opt_gui:
			mainwnd.cpuRun()
			#TODO checkLinuxCNC() is not called.
			#TODO --onecycle is not honored
			mainwnd.runEventLoop()
		else:
			# Load the program
			s.load(p.getParseTree())
			s.startup()

			# Run cyclic execution
			while checkLinuxCNC():
				s.runCycle()
				if opt_onecycle:
					break
	except KeyboardInterrupt as e:
		printInfo("Awlsim LinuxCNC HAL module shutdown")
	except (AwlParserError, AwlSimError) as e:
		printError(e.getReport())
		return 1
	return 0

if __name__ == "__main__":
	sys.exit(main())
