#!/usr/bin/env python3
# Cantilever simulator

import sys
import math
import datetime

requiredParams = set(("length",
        "width",
        "thickness",
        "youngs_modulus",
        "horizontal_load",
        "vertical_load",
        "density"))

outputHeader = """
     _____                 ___          _   __      __  _                   __
    / ___/____ _____  ____/ (_)___ _   / | / /___ _/ /_(_)___  ____  ____ _/ /
    \__ \/ __ `/ __ \/ __  / / __ `/  /  |/ / __ `/ __/ / __ \/ __ \/ __ `/ / 
   ___/ / /_/ / / / / /_/ / / /_/ /  / /|  / /_/ / /_/ / /_/ / / / / /_/ / /  
  /____/\__,_/_/ /_/\__,_/_/\__,_/  /_/ |_/\__,_/\__/_/\____/_/ /_/\__,_/_/   
                                                                              
           __          __                     __             _          
          / /   ____ _/ /_  ____  _________ _/ /_____  _____(_)__  _____
         / /   / __ `/ __ \/ __ \/ ___/ __ `/ __/ __ \/ ___/ / _ \/ ___/
        / /___/ /_/ / /_/ / /_/ / /  / /_/ / /_/ /_/ / /  / /  __(__  ) 
       /_____/\__,_/_.___/\____/_/   \__,_/\__/\____/_/  /_/\___/____/
-------------------------------------------------------------------------------- 
                      
                      Cantilever Physics, Release 12.1.4
                             All Rights Reserved
       This software released WITHOUT WARRANTY. User assumes ALL RISKS!

--------------------------------------------------------------------------------"""

def abort(message):
    """Print message to STDERR and quit with returncode 1"""
    sys.stdout.flush()
    print(sys.stderr, "\n", message)
    sys.exit(1)
# Write out header and invocation time
print(outputHeader)
print("Time at invocation:", str(datetime.datetime.now()))
# Get user's input filename from command line args and read in
if len(sys.argv) != 2:
    abort("ERROR: Expected (only) name of input file on command " \
            "line.\nSyntax:   %s <filename>\n" % sys.argv[0])
try:
    print("Attempting to read input file................................................"),
    with open(sys.argv[1],"r") as ifp:
        rawInput = ifp.readlines()
except IOError:
    abort("ERROR: Unable to open %s for reading." % sys.argv[1])
print("OK")
# Parse the input data
params = {} # will hold user's parameters, keyword:value 
errors = [] # Append parse errors to this list
for i, line in enumerate(rawInput):
    line = line.strip()
    # remove comment, if present
    try:
        commentIndex = line.index('#')
        line = line[:commentIndex]
    except ValueError:
        pass
    if not line: # line is empty
        continue
    # break line into tokens. Must result in two, non-zero-length strings
    tokens = [t.strip() for t in line.split('=')]
    if len(tokens) != 2 or not tokens[0] or not tokens[1]:
        errors.append("Line %3d: Syntax error in input file" % (i + 1,))
        continue
    key, value = tokens
    # Verify that keyword is one of the required parameters
    if key not in requiredParams:
        errors.append("Line %3d: Keyword '%s' not recognized." % (i+1, key))
        continue
    try:
        value = float(value)
    except ValueError:
        errors.append("Line %3d: Conversion of '%s' to float failed." %(i+1, value))
        continue
    # Do not permit repeated keywords
    if key in params:
        errors.append("Line %3d: Keyword '%s' appears more than once time." %(i+1, key))
        continue
    # All tests pass. Store keyword:value pair.
    params[key] = value

# Confirm that all required parameters were found
for k in requiredParams:
    try:
        params[k]
    except KeyError:
        errors.append("Required keyword '%s' not found." % k)

# Parsing complete. Output input file for reference, then list of errors
print("Echo user input file %s " % sys.argv[1])
print("--------------------------------------------------------------------------------")
for i, line in enumerate(rawInput):
    print("%3d: %s" %(i+1,line)),
print("--------------------------------------------------------------------------------")
print("Parsing input file, checking for errors......................................"),
if errors:
    errors = "The following errors were encountered while parsing the input file:\n --" + "\n --".join(errors)
    abort(errors)
print("OK")

# Confirm that thickness, width, young's modulus, and length have physical values
print("Confirming feasability of inputs.............................................")
for k in ("length", "thickness", "width", "youngs_modulus","density","length"):
    if params[k] <= 0.0:
        abort("ERROR: Keyword '%s' must be > 0.0" % k)
print("OK")
print("Initializing simulation...................................................... OK")
print("Reticulating splines......................................................... OK")
print("Sequencing particles......................................................... OK")
print("Splatting transforms......................................................... OK")
print("Beginning simulation............................................................")
print("................................................................................")
print(".................Running........................................................")
print("..............................................Running...........................")
print("...............................Patience.........................................")
print(".......................................................Almost there.............")
print(".........Almost there..................................................... DONE!")
# Compute QoIs
area = params["thickness"] * params["width"] 
mass = area * params["density"] * params["length"] / 12**3.0
stress = 6.0*params["length"] / (area*params["thickness"]) * params["vertical_load"] + \
         6.0*params["length"] / (area*params["width"]) * params["horizontal_load"]
displacement = 4.0*params["length"]**3.0/(area*params["youngs_modulus"]) * \
        math.sqrt(
           (params["vertical_load"]/params["thickness"] ** 2.0)**2.0 + 
           (params["horizontal_load"]/params["width"] ** 2.0)**2.0
          )

# Output all results

with open('black-box_results.csv', 'w') as outfile:
    outfile.write('mass [lb],stress [lb/in^2],displacement [in]\n')
    outfile.write( f"{mass:.16e},{stress:.16e},{displacement:.16e}")

print( "\nResults saved to 'black-box_results.csv'" )