# NOTE: follow these steps https://mstevetodd.com/anyrail-export-jmri for setup details 
# AnyRailExportAdditions.py - SteveT - 05/15/2025
# Intended to be run ONCE after opening a file generated by the AnyRail export
# The AnyRail export outputs a background .jpg, and a set of internal Block objects
# for all AnyRail "Sections" which have been assigned a "Name".
# This script does some cleanup of the generated Block and Turnout definitions, creates Sensors
# and assigns them to the corresponding Blocks
# Both Sensors and Turnouts are created to match your default hardware Connection.
# Requirements:
#   AnyRail "Section" names must contain a unique Block number, this number is used to generate 
#     the JMRI Block and Sensor systemNames.
#   AnyRail Turnouts must have "Label" names entered with a unique Turnout number or numbers.
#   If desired, you can include a unique "User Name" after the unique number, such as "186 Yard Track 1"
#     The number will be used for the SystemName, and the trailing text for the UserName
#   Be sure to check the JMRI System Console for any WARN or ERROR messages after running this script.
# Pre-export suggestions: set AnyRail Zoom to 1:6, disable "Show Visible track"
#
# Next steps: Run CreateSectionsFromBlocks.py and AddOccupancyIconsToPanel.py
#  
# Thanks to Cliff Anderson's AnyRailBuildSensorList.py and Bill Fitch's CreateSignalLogicAndSections.py
#   for some of the code and much of the inspiration
# TODO: handle underscore in AR SectionName
# TODO: "rename" the Block systemName to match (Block "move" currently causes NPE in JMRI) 
#
# NOTE: to enable jython logging, see https://www.jmri.org/help/en/html/apps/Debug.shtml
# Add the Logger Category name "jmri.jmrit.jython.exec" at DEBUG Level.

import jmri
import java
import org.slf4j.LoggerFactory
import re
import sys
from java.awt import Color

log = org.slf4j.LoggerFactory.getLogger("jmri.jmrit.jython.exec.script.AnyRailExportAdditions")
connectionPrefix = turnouts.getSystemPrefix() # get  the "Connection Prefix" in use for the default JMRI connection

if (connectionPrefix == 'I') :
    log.error('AnyRailExportAdditions.py Error - Default system cannot be Internal, correct and rerun.')
    exit()

log.info( "AnyRailExportAdditions.py Tweaking LayoutEditor appearance" )
panels = jmri.InstanceManager.getDefault(jmri.jmrit.display.EditorManager)
layoutPanels = panels.getList(jmri.jmrit.display.layoutEditor.LayoutEditor)
if (len(layoutPanels) != 1) :
    log.error('AnyRailExportAdditions.py - Error finding single LayoutEditor panel, terminating.')
    exit()
    
layoutEditor = layoutPanels[0]
log.debug("tweaking some display values for panel '{}'".format(layoutEditor.getLayoutName()))
ltdo = layoutEditor.getLayoutTrackDrawingOptions()    
ltdo.setMainRailWidth(2) 
ltdo.setMainBlockLineWidth(3)
layoutEditor.setTurnoutDrawUnselectedLeg(False)
layoutEditor.setTurnoutCircleSize(3)
layoutEditor.setTurnoutCircleColor(Color.LIGHT_GRAY)
layoutEditor.setTurnoutCircleThrownColor(Color.LIGHT_GRAY)
layoutEditor.redrawPanel()

log.info( "AnyRailExportAdditions.py Create and Assign Sensors to blocks" )
blocksUpdated = 0 #count successes
blocksSkipped = 0 # and failures
sensorsCreated = 0

blocksSet = set(blocks.getNamedBeanSet()) # use a copy for the loop
for blockBean in blocksSet :

    #if (blocksUpdated > 0) : continue #debugging, comment out before committing
    
    blockSystemName = str ( blockBean )
    block = blocks.getBlock(blockSystemName)
    anyRailUserName = block.getUserName()

    if (anyRailUserName == None) :
         log.warn('AnyRail SectionName "{}" is empty for block {}, skipped.'.format(blockSystemName, anyRailUserName))
         blocksSkipped += 1
         continue

    ra = anyRailUserName.split("_"); #break B_15_LS223 South Staging Lead_mainline into parts on underscore
    if (len(ra)<3 or ra[0]!="B") :
         log.warn("Unexpected syntax '{}', skipping.".format(anyRailUserName))
         blocksSkipped += 1
         continue     
    
    anyRailDescription = None
    if (len(ra) == 4) : # user defined, "Unspecified", "mainline", "secondary", etc.
        anyRailDescription = ra[3]
        
    userName = ra[2] # e.g. LS223 South Staging Lead
    
    # extract the block number from this username, e.g. LS223 South Staging Lead -> 223
    result = re.search("(\d+)", userName)
    if (result == None) :
         log.warn('BlockNumber not found in "{}", skipping.'.format(userName))
         blocksSkipped += 1
         continue
    blockNumber = result.group(1)
    blockName = "B" + blockNumber # block Name will be Bnnn

    result = blocks.getByUserName(blockName) # prevent duplicates in username and systemname
    if (result != None) :
         log.warn('Block "{}" already exists, skipping.'.format(blockName))
         blocksSkipped += 1
         continue
    
    result = re.search("[\d]+[\s]*(\D+.*)*", userName) # whatever follows the first number is userName
    if (result != None) :
        userName = result.group(1)   

    result = blocks.getByUserName(userName) # prevent duplicate userNames
    if (result != None) :
         log.warn('Block userName "{}" already exists, skipping.'.format(userName))
         blocksSkipped += 1
         continue

    log.debug('Processing blockSystemName={}, anyRailUserName={}, blockName={}, userName={}'.format(blockSystemName, anyRailUserName, blockName, userName))
    
    block.setUserName(userName if userName else blockName)
        
    blocksUpdated += 1
    
    # create and attach sensor for this block
    sensorName = connectionPrefix + "S" + blockNumber
    check = sensors.getBySystemName(sensorName) # insure no duplicate systemnames
    if (check) :
        log.warn("Sensor systemName '{}' is duplicate, skipping".format(sensorName))
        continue   

    newSensor = None
    try:
        newSensor = sensors.newSensor(sensorName, None) # create the hardware sensor using systemName
    except:
        log.error("Failed to create sensor '{}', {}", sensorName, sys.exc_info()[1]);
        continue
                
    if (userName) :
        newSensor.setUserName(userName)
    
    newSensor.setUseDefaultTimerSettings(True) # not sure why this isn't the default           
    
    block.setSensor(sensorName) #set the Sensor for the block

    sensorsCreated += 1

    log.debug("Created Sensor={} and assigned it to Block {} (renamed from {})".format(sensorName, blockName, anyRailUserName))   

log.info("Created {} Sensors, updated {} Blocks, skipped {} invalid Blocks".format(sensorsCreated, blocksUpdated, blocksSkipped))
log.info("AnyRailExportAdditions.py Converting Internal Turnouts to Hardware ({})".format(connectionPrefix))

turnoutsCreated = 0 #count successes
turnoutsSkipped = 0 # and failures

turnoutsSet = set(turnouts.getNamedBeanSet()) # use a copy for the loop
for turnoutBean in turnoutsSet :

    # if (turnoutsCreated > 5) : continue

    # get the Turnout systemName entered as AnyRail Label, and get reference to that turnout
    oldSystemName = str ( turnoutBean )
    oldTurnout = turnouts.getTurnout(oldSystemName)
    anyRailUserName = oldTurnout.getUserName() # AnyRail stores entered name as userName
    if (anyRailUserName == None) :
         log.warn('Turnout "{}" has no userName, skipping.'.format(oldSystemName))
         turnoutsSkipped += 1
         continue


    result = re.search("(\d+).*", anyRailUserName) # get the first number sequence
    if (result == None) :
        log.warn('Turnout Number not found in "{}", skipping.'.format(anyRailUserName))
        turnoutsSkipped += 1
        continue
    turnoutNumber  = result.group(1)   
    turnoutNumber2 = None   

    result = re.search("\d+\+(\d+).*", anyRailUserName) # check for nn+nn for multi-address turnouts
    if (result != None) :
        turnoutNumber2 = result.group(1)   

    oldTurnout.setUserName(None) # clear old username to avoid duplicates
    
    newSystemName =  connectionPrefix + "T" + turnoutNumber # systemName is xTnnn
    checkTurnout = turnouts.getTurnout(newSystemName) # insure systemname is not already used
    if (checkTurnout) :
        if (turnoutNumber2 == None) :
            log.warn("Turnout systemName '{}' would be duplicate, not replaced".format(newSystemName))
            oldTurnout.setUserName(anyRailUserName) # restore username
            turnoutsSkipped += 1
            continue   
        else : # check again with 2nd turnout if found
            newSystemName =  connectionPrefix + "T" + turnoutNumber2 # systemName is xTnnn
            checkTurnout = turnouts.getTurnout(newSystemName) # insure systemname is not already used
            if (checkTurnout) :
                log.warn("Turnout2 systemName '{}' would be duplicate, not replaced".format(newSystemName))
                oldTurnout.setUserName(anyRailUserName) # restore username
                turnoutsSkipped += 1
                continue   
            
    newUserName = None
    result = re.search("[\d|\+]+\s*(\S.*)", anyRailUserName) # new username is the rest of the string after address(es)
    if (result != None) :
        newUserName = result.group(1)   
        checkTurnout = turnouts.getByUserName(newUserName)
        if (checkTurnout) :
            log.warn("Turnout '{}' would duplicate userName '{}', clearing".format(newSystemName, newUserName))
            newUserName = None   
    
    log.debug("Replacing Turnout {} with {} {}".format(oldSystemName, newSystemName, '('+newUserName+')' if newUserName else ''))

    newTurnout = turnouts.provideTurnout(newSystemName) # create new hardware turnout
    newTurnout.setUserName(anyRailUserName)      # move the old username to new turnout  
    beans.moveBean(oldTurnout, newTurnout, anyRailUserName) # move references from old to new   
    beans.updateBeanFromUserToSystem(newTurnout) # move refs to sysname, so we can change userName
    newTurnout.setUserName(newUserName)          # set new userName, can be None
    turnouts.deleteBean(oldTurnout, 'DoDelete')  # delete old turnout           

    turnoutsCreated += 1

log.info('Replaced {} Turnouts, skipped {} Turnouts'.format(turnoutsCreated, turnoutsSkipped))
