# Sample script showing how to persist the state of turnouts between sessions.
#
# State is saved to a .csv file at shutdown and re-instated from said .csv file
# when script is launched.
#
# This shows how .csv files can be both written and read-back complete with
# a header row using the Apache Commons CSV library
#
# This also shows how entries can be added to the log as opposed to using
# 'print' commands
#
# Author: Matthew Harris, copyright 2011
# Author: Randall Wood, copyright 2020
# Part of the JMRI distribution
#

import jmri

import java
import java.io
import java.util
import org.apache.commons.csv
from org.slf4j import LoggerFactory

# Define turnout state file
# Default is 'TurnoutState.csv' stored in the preferences directory
turnoutFile = jmri.util.FileUtil.getUserFilesPath() + "TurnoutState.csv"

# Define task to persist turnout state at shutdown
class PersistTurnoutStateTask(jmri.implementation.AbstractShutDownTask):

    # Get reference to the logger
    #
    # This reference is unique to instances of this class, hence the use of
    # 'self.log' whenever it needs to be used
    #
    # The logger has been instantiated within the pseudo package:
    #   'jmri.jmrit.jython.exec'
    # This allows for easy identification and configuration of logging.
    #
    # NOTE: to enable logging, see https://www.jmri.org/help/en/html/apps/Debug.shtml
    # Add the Logger Category name "jmri.jmrit.jython.exec" at DEBUG Level.

    log = LoggerFactory.getLogger("jmri.jmrit.jython.exec.TurnoutStatePersistence.PersistTurnoutStateTask")

    # Define task to run at ShutDown
    def run(self):

        # Write an info entry to the log
        self.log.info("Write turnout state to file: '%s'" % turnoutFile)

        # Open file
        
        csvFormat = org.apache.commons.csv.CSVFormat.Builder.create(org.apache.commons.csv.CSVFormat.DEFAULT).setCommentMarker('#').build()
        csvFile = org.apache.commons.csv.CSVPrinter(java.io.FileWriter(turnoutFile), csvFormat)

        # Initialise counter
        turnoutCount = 0

        # Write header
        csvFile.print("System Name")
        csvFile.print("User Name")
        csvFile.print("Comment")
        csvFile.print("Is Inverted")
        csvFile.print("Saved State")
        csvFile.println()

        # Loop through all known turnouts
        for to in turnouts.getNamedBeanSet():

            # Write a debug entry to the log
            if (self.log.isDebugEnabled()):
                self.log.debug("Storing turnout: {}", to.getSystemName())

            # Retrieve details to persist
            csvFile.print(to.getSystemName())
            csvFile.print(to.getUserName())
            csvFile.print(to.getComment())
            csvFile.print(self.booleanName(to.getInverted()))
            csvFile.print(self.stateName(to.getState()))

            # Notify end of record
            csvFile.println()

            # Increment counter
            turnoutCount +=1

        # Write an info entry to the log
        self.log.info("Stored state of %d turnouts" % turnoutCount)

        # Append a comment to the end of the file
        csvFile.printComment("Written by JMRI version %s on %s" % (jmri.Version.name(), (java.util.Date()).toString()))

        # Flush the write buffer and close the file
        csvFile.flush()
        csvFile.close()

        # All done
        return

    # Function to convert state values to names
    def stateName(self, state):
        if (state == CLOSED):
            return "CLOSED"
        if (state == THROWN):
            return "THROWN"
        if (state == INCONSISTENT):
            return "INCONSISTENT"
        # Anything else is UNKNOWN
        return "UNKNOWN"

    # Function to convert boolean values to names
    def booleanName(self, value):
        if (value == True):
            return "Yes"
        # Anything else is No
        return "No"

# Define task to load turnout state at script start
#
# This is implemented as a separate class so that it can run on a
# different thread in the background rather than holding up the main
# thread while executing
class LoadTurnoutState(jmri.jmrit.automat.AbstractAutomaton):

    # Get reference to the logger
    log = LoggerFactory.getLogger("jmri.jmrit.jython.exec.TurnoutStatePersistence.LoadTurnoutState")

    # Perform any initialisation
    def init(self):
        return

    # Define task to run
    def handle(self):

        # Retrieve the state file as a File object
        inFile = java.io.File(turnoutFile)

        # Check if state file exists
        if inFile.exists():

            # It does, so load it
            csvFormat = org.apache.commons.csv.CSVFormat.Builder.create(org.apache.commons.csv.CSVFormat.DEFAULT).setHeader().setCommentMarker('#').build()
            csvFile = org.apache.commons.csv.CSVParser.parse(inFile, java.nio.charset.StandardCharsets.UTF_8, csvFormat)

            # Write an info entry to the log
            self.log.info("Loading turnout state file: %s" % turnoutFile)

            # Initialise counter
            turnoutCount = 0

            # Loop through each record
            for record in csvFile.getRecords():

                # Read the record details
                systemName = record.get("System Name")
                userName = record.get("User Name")
                comment = record.get("Comment")
                inverted = self.booleanName(record.get("Is Inverted"))
                savedState = self.stateValue(record.get("Saved State"))
                
                # Get reference to the turnout
                turnout = turnouts.provideTurnout(systemName)

                # Write a debug entry to the log
                if (self.log.isDebugEnabled()):
                    self.log.debug("Setting state of turnout: %s" % systemName)

                # Set other parameters if specified
                if (userName != ""):
                    turnout.setUserName(userName)
                if (comment != ""):
                    turnout.setComment(comment)
                if (turnout.canInvert()):
                    turnout.setInverted(inverted)

                # Finally, set the state
                turnout.setState(savedState)

                # Increment counter
                turnoutCount +=1

            # Close the file
            csvFile.close()

            # Write an info entry to the log
            self.log.info("Loaded state of %d turnouts" % turnoutCount)

        else:
            # It doesn't, so log this fact and carry on
            self.log.warn("Turnout state file '%s' does not exist" % turnoutFile)

        # All done
        return False    # Only need to run once

    # Function to convert state names to values
    def stateValue(self, state):
        if (state == "CLOSED"):
            return CLOSED
        if (state == "THROWN"):
            return THROWN
        if (state == "INCONSISTENT"):
            return INCONSISTENT
        # Anything else is UNKNOWN
        return UNKNOWN

    # Function to convert boolean names to values
    def booleanName(self, value):
        if (value == "Yes"):
            return True
        # Anything else is False
        return False

# Register the turnout persistence shutdown task
shutdown.register(PersistTurnoutStateTask("PersistTurnoutState"))

# Launch the load task
LoadTurnoutState().start()
