#!/usr/bin/env python
# onegenplot - runs genhstplot with one plot
#
# Author: David Mastronarde
#
# $Id: onegenplot,v 937343107256 2023/02/19 22:44:16 mast $
#

progname = 'onegenplot'
prefix = 'ERROR: ' + progname + ' - '
NUM_KEY_LIMIT = 10    # LIM_KEYS in flib/subrs/graphics/plotvars.f90

# Exit and remove data file if option given
def cleanupError(strn):
   if cleanup:
      cleanupFiles([dataName])
   exitError(strn)

   
#### MAIN PROGRAM  ####
#
# load System Libraries
import os, sys

#
# Setup runtime environment
if os.getenv('IMOD_DIR') != None:
   IMOD_DIR = os.environ['IMOD_DIR']
   if sys.platform == 'cygwin' and sys.version_info[0] > 2:
      IMOD_DIR = IMOD_DIR.replace('\\', '/')
      if IMOD_DIR[1] == ':' and IMOD_DIR[2] == '/':
         IMOD_DIR = '/cygdrive/' + IMOD_DIR[0].lower() + IMOD_DIR[2:]
   sys.path.insert(0, os.path.join(IMOD_DIR, 'pylib'))
   from imodpy import *
   addIMODbinIgnoreSIGHUP()
else:
   sys.stdout.write(prefix + " IMOD_DIR is not defined!\n")
   sys.exit(1)

#
# load IMOD Libraries
from pip import *

defSymbols = [9, 7, 5, 8, 13, 14, 1, 11, 3, 10, 6, 2, 12, 4]
stockColors = {'aqua' : (0, 255, 255),
               'blue' : (0, 0, 255),
               'cyan' : (0, 255, 255),
               'darkblue' : (0, 0, 139),
               'darkgreen' : (0, 100, 0),
               'darkmagenta' : (139, 0, 139),
               'darkorange' : (255, 140, 0),
               'darkred' : (139, 0, 0),
               'darkviolet' : (148, 0, 211),
               'fuchsia' : (255, 0, 255),
               'green' : (0, 128, 0),
               'lime' : (0, 255, 0),
               'magenta' : (255, 0, 255),
               'maroon' : (128, 0, 0),
               'navy' : (0, 0, 128),
               'olive' : (128, 128, 0),
               'orange' : (255, 165, 0),
               'purple' : (128, 0, 128),
               'red' : (255, 0, 0),
               'teal' : (0, 128, 128),
               'yellow' : (255, 255, 0),
               'black' : (0, 0, 0)}

defColors = ['darkblue', 'darkgreen', 'darkred', 'darkviolet', 'darkorange', 'teal', 
             'olive', 'black', 'red', 'green', 'magenta', 'cyan']

# Fallbacks from ../manpages/autodoc2man 3 1 onegenplot
options = ["input:InputDataFile:FN:", "ncol:NumberOfColumns:I:",
           "skip:SkipLinesAtStart:I:", "columns:ColumnsToPlot:IA:",
           "types:TypesToPlot:IA:", "symbols:SymbolsForTypes:IA:",
           "connect:ConnectWithLines:B:", "ordinal:OrdinalsForXvalues:B:",
           "xlog:XLogOrRootAndBase:FP:", "ylog:YLogOrRootAndBase:FP:",
           "hue:HueOfGroup:CHM:", "defhue:DefaultHues:B:", "stock:StockColorList:B:",
           "axis:XaxisLabel:CH:", "keys:KeyLabels:CHM:", "message:MessageBoxLine:CHM:",
           "tooltip:ToolTipLine:CHM:", "size:SizeOfPlot:IP:",
           "position:PositionOfPlot:IP:", "yminmax:YRangeMinAndMax:FP:",
           "png:SavePNGandExit:FN:", "remove:RemoveDataFile:B:", "help:usage:B:"]

(opts, nonopts) = PipReadOrParseOptions(sys.argv, options, progname, 1, 0, 0)

if PipGetBoolean('StockColorList', 0):
   prnstr('Standard colors available by name:')
   for col in stockColors:
      longer = col + (13 - len(col)) * ' '
      prnstr(fmtstr('  {} {:3d}  {:3d}  {:3d}', longer, stockColors[col][0],
                    stockColors[col][1], stockColors[col][2]))
   sys.exit(0)

dataName = PipGetInOutFile('InputDataFile', 0)
if not dataName:
   exitError('The data file name must be entered')

cleanup = PipGetBoolean('RemoveDataFile', 0)
skipLines = PipGetInteger('SkipLinesAtStart', -1)
numCol = PipGetInteger('NumberOfColumns', -1)
columnList = PipGetIntegerArray('ColumnsToPlot', 0)
typeList = PipGetIntegerArray('TypesToPlot', 0)
ifTypes = 0
if typeList:
   ifTypes = 1
symbols = PipGetIntegerArray('SymbolsForTypes', 0)
(xlog, xbase) = PipGetTwoFloats('XLogOrRootAndBase', 0., 0.)
(ylog, ybase) = PipGetTwoFloats('YLogOrRootAndBase', 0., 0.)
ordinals = PipGetBoolean('OrdinalsForXvalues', 0)
connect = PipGetBoolean('ConnectWithLines', 0)
axisLabel = PipGetString('XaxisLabel', '')
numKeys = PipNumberOfEntries('KeyLabels')
keys = []
for ind in range(numKeys):
   oneKey = PipGetString('KeyLabels', '')
   keys.append(oneKey)

savePNGname = PipGetString('SavePNGandExit', '')
(ymin, ymax) = PipGetTwoFloats('YRangeMinAndMax', 0., 0.)
if PipGetErrNo() == 0 and ymin >= ymax:
   exitError('The minimum of the Y range must be less than the maximum')
(xmin, xmax) = PipGetTwoFloats('XRangeMinAndMax', 0., 0.)
if PipGetErrNo() == 0 and xmin >= xmax:
   exitError('The minimum of the X range must be less than the maximum')
useDfltHues = PipGetBoolean('DefaultHues', 0)
numColors = PipNumberOfEntries('HueOfGroup')
colors = []
coloredGroups = []
for ind in range(numColors):
   colorStr = PipGetString('HueOfGroup', '')
   csplit = colorStr.split(',')
   if len(csplit) != 2 and len(csplit) != 4:
      cleanupError('The color entry ' + colorStr + ' does not have 2 or 4 components')
   group = convertToInteger(csplit[0], 'group number of color entry')
   coloredGroups.append(group)
   if len(csplit) == 2:
      col = csplit[1]
      if col not in stockColors:
         cleanupError(col + ' is not in the list of stock colors')
      colors.append(fmtstr('{},{},{},{}', group, stockColors[col][0], stockColors[col][1],
                           stockColors[col][2]))
   else:
      red = convertToInteger(csplit[1], 'second component of color entry')
      green = convertToInteger(csplit[2], 'third component of color entry')
      blue = convertToInteger(csplit[3], 'fourth component of color entry')
      colors.append(fmtstr('{},{},{},{}', group, red, green, blue))

message = ''
tooltip = ''
posOption = ''
sizeOption = ''
num = PipNumberOfEntries('MessageBoxLine')
for ind in range(num):
   oneLine = PipGetString('MessageBoxLine', '')
   if ind and not message.endswith('\n'):
      message += '\n'
   message += oneLine

num = PipNumberOfEntries('ToolTipLine')
for ind in range(num):
   oneLine = PipGetString('ToolTipLine', '')
   if ind and not tooltip.endswith('\n'):
      tooltip += '\n'
   tooltip += oneLine

(xsize, ysize) = PipGetTwoIntegers('SizeOfPlot', 0, 0)
if xsize > 0 and ysize > 0:
   sizeOption = fmtstr(' -s {},{}', xsize, ysize)
(xpos, ypos) = PipGetTwoIntegers('PositionOfPlot', 0, 0)
if not PipGetErrNo():
   posOption = fmtstr(' -p {},{}', xpos, ypos)

if not os.path.exists(dataName):
   exitError('Data file ' + dataName + ' does not exist')

trueCol = numCol
if skipLines < 0 or numCol <= 0:
   try:
      errString = "Opening"
      dataFile = open(dataName, 'r')
      errString = "Reading"
      lineNum = 0

      # set number of numeric fields needed if numCol is not zero
      numNeeded = 1
      if numCol > 0:
         numNeeded = numCol
      if ifTypes:
         numNeeded += 1
      while skipLines < 0 or trueCol <= 0:

         # Read a line, strip, and split on valid table separators
         line = dataFile.readline()
         if not line:
            cleanupError('Reached the end of the file before finding any data in ' +\
                            dataName)
         lineNum += 1
         if skipLines >= lineNum:
            continue
         line = line.strip()
         if line == '':
            continue
         lsplit = re.split('[, \t]+', line)

         # Determine if there is an integer at start, number of leading numeric fields,
         # and if there is any non-numeric stuff
         try:
            firstInt = int(lsplit[0])
         except ValueError:
            firstInt = None
         numFields = 0
         nonNumeric = False
         for field in lsplit:
            try:
               val = float(field)
               numFields += 1
            except ValueError:
               nonNumeric = True
               break

         # If trying to determine number of lines to skip, see if this line is a data line
         if skipLines < 0:
            if (numCol == 0 and firstInt != None) or \
                   (numCol < 0 and numFields >= numNeeded) or \
                   (numCol > 0 and (not nonNumeric or numFields >= numNeeded)):
               skipLines = lineNum - 1
            else:
               continue

         # This is the column number or data line
         if not numFields:
            cleanupError('First line of data file does not have expected numeric text')
         if numCol < 0:
            trueCol = numFields - ifTypes
            if not trueCol:
               cleanupError('There seem to be only types in the file, not data')
         elif numCol == 0:
            if firstInt == None or firstInt <= 0:
               cleanupError('Entry on line just before data is not an integer > 1')
            trueCol = firstInt

   except IOError:
      cleanupError(fmtstr("{} data file {}: {}", errString, dataName, \
                          str(sys.exc_info()[1])))
      
   dataFile.close()
   
# Leave numCol as original value, it is passed to genhstplt; but use trueCol instead
# from here on for number of columns
# Check the column list for validity
if columnList:
   for ind in range(len(columnList)):
      col = columnList[ind]
      if col < 1 or col > trueCol:
         cleanupError(str(col) + ' is an invalid column number')
      if (ind > 0 and col in columnList[0:ind - 1]) or \
             (ind < len(columnList) - 1 and col in columnList[ind + 1:]):
         cleanupError('Column ' + str(col) + ' is in the column list more than once')
else:

   # Or take care of default column, 1 and 2 unless there is only 1 or doing ordinals
   columnList = [1]
   if trueCol > 1 and not ordinals:
      columnList.append(2)

if len(columnList) == 1:
   ordinals = 1

# Check the column/type entry, set up type conversion if needed
if ifTypes:
   defKeyBase = 'Type '
   if len(columnList) > 2:
      cleanupError('When plotting by types, you cannot enter more than two columns')

else:
   defKeyBase = 'Column '
   if len(columnList) > 2 - ordinals:
      ifTypes = -1
      typeList = columnList[1 - ordinals:]
      xcol = columnList[0]
      if ordinals:
         columnList = [2]
      else:
         columnList = [1, 2]

# Take care of axis labels, keys and symbols if they haven't been entered
if not axisLabel:
   if ordinals:
      axisLabel = 'Data number'
   else:
      axisLabel = 'Column ' + str(columnList[0])

# Set default colors for ones that weren't specified
if useDfltHues and typeList:
   indHue = 0;
   for ind in range(len(typeList)):
      if ind + 1 not in coloredGroups:
         stockHue = stockColors[defColors[indHue]]
         colors.append(fmtstr('{},{},{},{}', ind + 1, stockHue[0], stockHue[1], 
                              stockHue[2]))
         indHue = (indHue + 1) % len(defColors)

numCurves = 1
if ifTypes:
   numCurves = len(typeList)
if numCurves > NUM_KEY_LIMIT:
   prnstr(fmtstr('You will only see keys for {} curves', NUM_KEY_LIMIT))
   
for ind in range(numKeys, min(numCurves, NUM_KEY_LIMIT)):
   if ifTypes:
      keys.append(defKeyBase + str(typeList[ind]))
   else:
      keys.append(defKeyBase + str(columnList[ind + 1 - ordinals]))

if not symbols:
   symbols = []
   if numCurves == 1 and ordinals:
      symbols = [0]
for ind in range(len(symbols), numCurves):
   for sym in defSymbols:
      if sym not in symbols:
         symbols.append(sym)
         break
   else:
      cleanupError('There are too many types or columns being plotted for the symbols ' +\
                   'available')

# Ready to build the awful input list
inlist = ['-1',
          str(ifTypes),
          str(numCol),
          str(skipLines),
          dataName]
if ifTypes < 0:
   inlist += [str(xcol)]
if ifTypes:
   inlist += [str(-len(typeList))]
   for ind in range(len(typeList)):
      inlist += [fmtstr('{},{}', typeList[ind], symbols[ind])]
else:
   inlist += [str(symbols[0])]

# columns
if trueCol > 1:
   inlist.append(str(columnList[0]))
inlist += [fmtstr('{},{}', int(xlog), xbase),
           '0']
if ordinals:
   inlist += ['16']
inlist.append('1')
if trueCol > 1:
   inlist.append(str(columnList[1 - ordinals]))
inlist += [fmtstr('{},{}', int(ylog), ybase),
           '0',
           '-2',
           axisLabel,
           str(numCurves)]
inlist += keys
if colors:
   inlist += ['-4', (str(len(colors)))] + colors

if ymin != 0. or ymax != 0.:
   inlist += ['-9',
              fmtstr('{},{}', ymin, ymax)]
if xmin != 0. or xmax != 0.:
   inlist += ['-10',
              fmtstr('{},{}', xmin, xmax)]
   
if connect:
   inlist += ['17']
else:
   inlist += ['2']
inlist += ['0',
           '0']
if savePNGname:
   inlist += ['-7',
              savePNGname,
              '8']
else:
   inlist.append('-8')

try:
   if not savePNGname:
      prnstr('Close graphing window to exit', flush=True)
   cmd = 'genhstplt' + posOption + sizeOption
   if message:
      cmd += ' -message """' + message + '"""'
   if tooltip:
      cmd += ' -tooltip """' + tooltip + '"""'
   runcmd(cmd, inlist)
except ImodpyError:
   if cleanup:
      cleanupFiles([dataName])
   exitFromImodError(progname)

if cleanup:
   cleanupFiles([dataName])
sys.exit(0)
