#!/bin/bash

# This should point to a SIMINFO-enabled SimulAVR executable:
SIMULAVR="../../simulavr/src/simulavr"
if [ ! -x "${SIMULAVR}" ]; then
  echo "SimulAVR executable not found, please adjust"
  echo "variables at the top of ${0}."
  exit 1;
fi

if [ $# -eq 0 ]; then
  BASE=$(basename $0)
  echo "Usage: ${BASE} <gcode file> ..."
  echo
  echo "${BASE} runs each G-code file in the simulator, limited to 60 seconds"
  echo "simulation time (can take much more wall clock time) and processes the"
  echo "results into a PNG picture and a .vcd file with calculated velocities."
  exit 1
fi


# General preparation.
PIPE_IN_FILE=$(mktemp -u)
PIPE_OUT_FILE=$(mktemp -u)
TRACEIN_FILE=$(mktemp)
STATISTICS_FILE=$(mktemp)

trap 'cat '${STATISTICS_FILE}'; rm -f '${PIPE_IN_FILE}' '${PIPE_OUT_FILE}' \
     '${TRACEIN_FILE}' '${STATISTICS_FILE} 0

mkfifo ${PIPE_IN_FILE} || exit 1
mkfifo ${PIPE_OUT_FILE} || exit 1


# Respect USER_CONFIG.
if [ "${USER_CONFIG}" ]; then
  CONFIG="${USER_CONFIG}"
  TEACUP_ELF="${USER_CONFIG%.h}/teacup.elf"
  TEACUP_ELF="../build/testcases/${TEACUP_ELF#../}"
else
  CONFIG="../config.h"
  TEACUP_ELF="../build/teacup.elf"
fi
echo "Taking configuration in ${CONFIG}."
echo "Taking Teacup binary ${TEACUP_ELF}."

# Prepare statistics.
echo                             > ${STATISTICS_FILE}
(cd .. && make USER_CONFIG="testcases/${CONFIG}" size) | \
  tail -5 | head -4             >> ${STATISTICS_FILE}


# Prepare a pin tracing file, assuming a Gen7-v1.4 configuration. See
# http://reprap.org/wiki/SimulAVR#Putting_things_together:_an_example
#
#   #define X_DIR_PIN        DIO28
#   #define X_STEP_PIN       DIO29
#   #define Y_DIR_PIN        DIO26
#   #define Y_STEP_PIN       DIO27
#   #define DEBUG_LED_PIN    DIO21
echo "# X Dir"              > ${TRACEIN_FILE}
echo "+ PORTA.A3-Out"      >> ${TRACEIN_FILE}
echo "# X Step"            >> ${TRACEIN_FILE}
echo "+ PORTA.A2-Out"      >> ${TRACEIN_FILE}
echo "# Y Dir"             >> ${TRACEIN_FILE}
echo "+ PORTA.A5-Out"      >> ${TRACEIN_FILE}
echo "# Y Step"            >> ${TRACEIN_FILE}
echo "+ PORTA.A4-Out"      >> ${TRACEIN_FILE}
echo "# DEBUG LED"         >> ${TRACEIN_FILE}
echo "+ PORTC.C5-Out"      >> ${TRACEIN_FILE}
echo "Assuming pin configuration for a Gen7-v1.4 + debug LED on DIO21."

STEPS_PER_M_X=$(grep STEPS_PER_M_X ${CONFIG} | \
                grep -v ^// | awk '{ print $3; }')
if [ "${STEPS_PER_M_X}"0 -eq 0 ]; then
  echo "STEPS_PER_M_X not found, assuming 80'000."
  STEPS_PER_M_X=80000
fi
STEPS_PER_M_Y=$(grep STEPS_PER_M_Y ${CONFIG} | \
                grep -v ^// | awk '{ print $3; }')
if [ "${STEPS_PER_M_Y}"0 -eq 0 ]; then
  echo "STEPS_PER_M_Y not found, assuming 80'000."
  STEPS_PER_M_Y=80000
fi
echo "Taking STEPS_PER_M_X = ${STEPS_PER_M_X} and"
echo "       STEPS_PER_M_Y = ${STEPS_PER_M_Y} for mm/min calculation."


for GCODE_FILE in $*; do

  if [ ! -r "${GCODE_FILE}" ]; then
    echo "${GCODE_FILE} not readable, skipping."
    continue
  fi
  echo                              >> ${STATISTICS_FILE}
  echo "${GCODE_FILE} statistics:"  >> ${STATISTICS_FILE}

  FILE="${GCODE_FILE%.gcode}"
  VCD_FILE="${FILE}.vcd"
  DATA_FILE="${FILE}.data"
  VEL_FILE="${FILE}.processed.vcd"


  # Start the simulator and send the file line by line.
  exec 3<>${PIPE_IN_FILE}
  exec 4<>${PIPE_OUT_FILE}

  "${SIMULAVR}" -c vcd:${TRACEIN_FILE}:"${VCD_FILE}" \
                -f "${TEACUP_ELF}" \
                -m 60000000000 -v <&3 >&4 2>&4 &

  while read -rs -u 4 LINE; do
    echo "${LINE}"
    case "${LINE}" in
      *"Ran too long"*)
        echo ">> SimulAVR ended."
        break
        ;;
      "ok"*)
        read LINE
        echo ">> Sending ${LINE}"
        echo "${LINE}" >&3
        ;;
      "stop")
        echo ">> Got \"stop\", killing SimulAVR."
        killall -INT $(basename "${SIMULAVR}") 2> /dev/null || \
          killall -INT "lt-"$(basename "${SIMULAVR}")
        ;;
    esac
  done < "${GCODE_FILE}"

  exec 3>&-
  exec 4>&-


  # Make plottable files from VCD files.
  # This is very rudimentary and does a lot of assumptions.
  # For examble, pin data is assumed to always be b0/b1, pin naming
  # is assumed to match the order in ${TRACEIN_FILE} and starting at "0".
  awk '
    BEGIN {
      print "0 0 0 0 0";
      xDir = yDir = 0;
      xPos = yPos = 0;
      xVel = yVel = 0;
      yAcc = yAcc = 0;
      lastxTime = lastyTime = 0;
    }
    /^#/ {
      time = substr($0, 2);
      next;
    }
    {
      bit = substr($1, 2);
      if ($2 == "0") { # X Dir
        if (bit == 0) xDir = -1;
        else if (bit == 1) xDir = 1;
        else xDir = 0;
      }
      if ($2 == "2") { # Y Dir
        if (bit == 0) yDir = -1;
        else if (bit == 1) yDir = 1;
        else yDir = 0;
      }
      if ($2 == "1") { # X Step, count only raising flanges
        if (bit == 1) {
          xPos += xDir;
          xVel = 1000000000 / (time - lastxTime);
          print time " " xPos " " yPos " " xVel " " yVel;
          lastxTime = time;
        }
      }
      if ($2 == "3") { # Y Step, count only raising flanges
        if (bit == 1) {
          yPos += yDir;
          yVel = 1000000000 / (time - lastyTime);
          print time " " xPos " " yPos " " xVel " " yVel;
          lastyTime = time;
        }
      }
      # No usage for the LED pin signal ($2 == "4") so far.
    }
  ' < "${VCD_FILE}" > "${DATA_FILE}"


  # Create a plot.
  gnuplot << EOF
    set terminal png size 1024,768
    set output "${FILE}.png"

    set title "${GCODE_FILE}"

    set xlabel "X steps"
    set ylabel "Y steps"
    set y2label "feedrate [steps/s]"

    #set origin 10,10;
    plot "${FILE}.data" using (\$2):(\$3) with dots title "position", \
         "${FILE}.data" using (\$2):(\$4) with dots title "X feedrate", \
         "${FILE}.data" using (\$2):(\$5) with dots title "Y feedrate"
EOF


  # Next task: rewrite the VCD file to add speed values.
  #
  # This is a bit tricky, as VCD files demand timestamps in ascending order.
  # Strategy taken: write out all timestamped data with one line per event,
  # then run it through 'sort -g' and reformat it yet again to be a properly
  # formatted VCD file.
  awk '
    function print_binary(n, e) {  # n = number; e = number of bits
      string = "";
      for (i = 0; i < e; i++) {
        if (n >= (2 ^ (e - 1))) # top bit set
          string = string "1";
        else
          string = string "0";
        n *= 2;
        if (n >= (2 ^ e))
          n -= (2 ^ e);
      }
      return string;
    }
    BEGIN {
      # These lines must match the ones after the sort.
      intLen = 16;
      xStepID = "0"; xPosID = "1"; xUmID = "2"; xVelID = "3"; xMmmID = "4";
      yStepID = "5"; yPosID = "6"; yUmID = "7"; yVelID = "8"; yMmmID = "9";
      ledID = "10"; ledTimeID = "11";

      xDir = yDir = 0;
      xPos = yPos = 0;
      lastxTime = lastyTime = 0;
      ledOnTime = 0;

      ledTimeMin = ledTimeMax = 0;
      ledTimeCount = 0;
      ledTimeSum = 0;
    }
    /^#/ {
      time = substr($0, 2);
      if (time == 0) {
        do {
          getline;
        } while ($0 != "$end");
      }
      next;
    }
    {
      bit = substr($1, 2);
      if ($2 == "0") { # X Dir
        if (bit == 0) xDir = -1;
        else if (bit == 1) xDir = 1;
        else xDir = 0;
      }
      if ($2 == "2") { # Y Dir
        if (bit == 0) yDir = -1;
        else if (bit == 1) yDir = 1;
        else yDir = 0;
      }
      if ($2 == "1") { # X Step
        if (bit == 1) { # raising flange
          xPos += xDir;
          print time " b" print_binary(xPos, intLen) " " xPosID;
          # TODO: it might be better to output mm as real value, but ...
          #       ... does the VCD file format support this? If yes, how?
          print time " b" print_binary(xPos * 1000000 / '"${STEPS_PER_M_X}"', intLen) " " xUmID;
          vel = 1000000000 / (time - lastxTime);
          print lastxTime " b" print_binary(vel, intLen) " " xVelID;
          vel = vel * 60000 / '"${STEPS_PER_M_X}"';
          print lastxTime " b" print_binary(vel, intLen) " " xMmmID;
          print time " b" bit " " xStepID;
          lastxTime = time;
        } else { # falling flange
          print time " b" bit " " xStepID;
        }
      }
      if ($2 == "3") { # Y Step
        if (bit == 1) { # raising flange
          yPos += yDir;
          print time " b" print_binary(yPos, intLen) " " yPosID;
          print time " b" print_binary(yPos * 1000000 / '"${STEPS_PER_M_Y}"', intLen) " " yUmID;
          vel = 1000000000 / (time - lastyTime);
          print lastyTime " b" print_binary(vel, intLen) " " yVelID;
          vel = vel * 60000 / '"${STEPS_PER_M_Y}"';
          print lastyTime " b" print_binary(vel, intLen) " " yMmmID;
          print time " b" bit " " yStepID;
          lastyTime = time;
        } else { # falling flange
          print time " b" bit " " yStepID;
        }
      }
      if ($2 == "4") { # LED signal
        if (bit == 1) { # raising flange
          print time " b" bit " " ledID;
          ledOnTime = time;
        } else { # falling flange
          print time " b" bit " " ledID;
          if (ledOnTime != 0) {
            # Convert from nanoseconds to clock cycles.
            ledTime = (time - ledOnTime) / 50;
            print ledOnTime " b" print_binary(ledTime, 32) " " ledTimeID;
            if (ledTimeMin == 0 || ledTime < ledTimeMin) {
              ledTimeMin = ledTime;
            }
            if (ledTime > ledTimeMax) {
              ledTimeMax = ledTime;
            }
            ledTimeCount++;
            ledTimeSum += ledTime;
          }
        }
      }
    }
    END {
      if (ledTimeCount > 0) {
        print "LED on occurences: " ledTimeCount "." >> "'${STATISTICS_FILE}'";
        print "LED on time minimum: " ledTimeMin " clock cycles." \
            >> "'${STATISTICS_FILE}'";
        print "LED on time maximum: " ledTimeMax " clock cycles." \
            >> "'${STATISTICS_FILE}'";
        print "LED on time average: " ledTimeSum / ledTimeCount " clock cycles." \
            >> "'${STATISTICS_FILE}'";
      } else {
        print "Debug LED apparently unused." > "'${STATISTICS_FILE}'";
      }
    }
  ' < "${VCD_FILE}" | \
  sort -g | \
  awk '
    BEGIN {
      # These lines must match the ones before the sort.
      intLen = 16;
      xStepID = "0"; xPosID = "1"; xUmID = "2"; xVelID = "3"; xMmmID = "4";
      yStepID = "5"; yPosID = "6"; yUmID = "7"; yVelID = "8"; yMmmID = "9";
      ledID = "10"; ledTimeID = "11";

      lastTime = "";

      print "$timescale 1ns $end";
      print "$scope module Steppers $end";
      print "$var wire 1 " xStepID " X_step $end";
      print "$var integer " intLen " " xPosID " X_pos_steps $end";
      print "$var integer " intLen " " xUmID " X_pos_um $end";
      print "$var integer " intLen " " xVelID " X_steps/s $end";
      print "$var integer " intLen " " xMmmID " X_mm/min $end";
      print "$var wire 1 " yStepID " Y_step $end";
      print "$var integer " intLen " " yPosID " Y_pos_steps $end";
      print "$var integer " intLen " " yUmID " Y_pos_um $end";
      print "$var integer " intLen " " yVelID " Y_steps/s $end";
      print "$var integer " intLen " " yMmmID " Y_mm/min $end";
      print "$upscope $end";
      print "$scope module Timings $end";
      print "$var wire 1 " ledID " Debug_LED $end";
      print "$var integer " 32 " " ledTimeID " LED_on_clocks $end";
      print "$upscope $end";
      print "$enddefinitions $end";
      print "#0";
      print "$dumpvars";
      print "bz " xStepID;
      print "bz " xPosID;
      print "bz " xUmID;
      print "bz " xVelID;
      print "bz " xMmmID;
      print "bz " yStepID;
      print "bz " yPosID;
      print "bz " yUmID;
      print "bz " yVelID;
      print "bz " yMmmID;
      print "bz " ledID;
      print "bz " ledTimeID;
      print "$end";
    }
    {
      if ($1 != lastTime) {
        print "#" $1;
        lastTime = $1;
      }
      print $2 " " $3;
    }
  ' > "${VEL_FILE}"


done

