content: /**
   * erlyberly, erlang trace debugger
   * Copyright (C) 2016 Andy Till
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program. If not, see <http://www.gnu.org/licenses/>.
   */
  package erlyberly;
  
  import java.util.HashMap;
  import java.util.Map;
  import java.util.concurrent.atomic.AtomicLong;
  
  import com.ericsson.otp.erlang.OtpErlangAtom;
  import com.ericsson.otp.erlang.OtpErlangList;
  import com.ericsson.otp.erlang.OtpErlangLong;
  import com.ericsson.otp.erlang.OtpErlangObject;
  import com.ericsson.otp.erlang.OtpErlangString;
  import com.ericsson.otp.erlang.OtpErlangTuple;
  
  import erlyberly.node.OtpUtil;
  import javafx.application.Platform;
  import javafx.beans.property.ReadOnlyBooleanProperty;
  import javafx.beans.property.SimpleBooleanProperty;
  import javafx.beans.property.SimpleLongProperty;
  import javafx.beans.property.SimpleStringProperty;
  
  public class TraceLog implements Comparable<TraceLog> {
  
      private static final OtpErlangAtom FN_ATOM = new OtpErlangAtom("fn");
      public static final OtpErlangAtom EXCEPTION_FROM_ATOM = new OtpErlangAtom("exception_from");
      public static final OtpErlangAtom RESULT_ATOM = new OtpErlangAtom("result");
      public static final OtpErlangAtom ATOM_PID = new OtpErlangAtom("pid");
      public static final OtpErlangAtom ATOM_REG_NAME = new OtpErlangAtom("reg_name");
      public static final OtpErlangAtom ATOM_UNDEFINED = new OtpErlangAtom("undefined");
      private static final Object TIMESTAMP_CALL_ATOM = new OtpErlangAtom("timetamp_call_us");
      private static final Object TIMESTAMP_RETURN_ATOM = new OtpErlangAtom("timetamp_return_us");
  
      private static final AtomicLong INSTANCE_COUNTER = new AtomicLong();
  
      private final Map<Object, Object> map;
  
      private final long instanceNum;
  
      private final SimpleStringProperty summary = new SimpleStringProperty("");
  
      private final SimpleLongProperty duration = new SimpleLongProperty(0L);
  
      private final SimpleStringProperty result = new SimpleStringProperty("");
  
      private final SimpleBooleanProperty complete = new SimpleBooleanProperty(false);
  
      private String tracePropsToString;
  
      private final String pid, registeredName, function, argsString;
  
      private final String cssClass;
  
      public TraceLog(Map<Object, Object> map) {
          this.map = map;
          instanceNum = INSTANCE_COUNTER.incrementAndGet();
          pid = getPidString();
          registeredName = regNameString().intern();
          function =  appendModFuncArity(new StringBuilder()).toString().intern();
          argsString = appendArgsToString(new StringBuilder(), getArgsList().elements()).toString();
          cssClass = null;
      }
  
      public TraceLog(String aCssClass, String text) {
          function = text;
          instanceNum = INSTANCE_COUNTER.incrementAndGet();
          map = new HashMap<>(0);
          cssClass = aCssClass;
          pid = "";
          registeredName = "";
          argsString = "";
      }
  
      public long getInstanceNum() {
          return instanceNum;
      }
  
      private String regNameString() {
          Object object = map.get(ATOM_REG_NAME);
          if(ATOM_UNDEFINED.equals(object))
              return "";
          return object.toString();
      }
  
      public SimpleStringProperty summaryProperty() {
          if(summary.get().isEmpty()) {
              summary.set(toString());
          }
          return summary;
      }
  
      @Override
      public String toString() {
          if(tracePropsToString == null)
              tracePropsToString = tracePropsToString();
          return tracePropsToString;
      }
  
      private String tracePropsToString() {
          StringBuilder sb = new StringBuilder(1024);
  
          boolean appendArity = false;
          toCallString(sb, appendArity);
  
          sb.append(" => ");
  
          if(map.containsKey(RESULT_ATOM)) {
              ErlyBerly.getTermFormatter().appendToString((OtpErlangObject) map.get(RESULT_ATOM), sb);
          }
          else if(isExceptionThrower()) {
              OtpErlangTuple exception = (OtpErlangTuple) map.get(EXCEPTION_FROM_ATOM);
              ErlyBerly.getTermFormatter().exceptionToString(
                  (OtpErlangAtom) exception.elementAt(0), exception.elementAt(1)
              );
          }
          return sb.toString();
      }
  
      private StringBuilder toCallString(StringBuilder sb, boolean appendArity) {
          OtpErlangAtom regName = (OtpErlangAtom) map.get(ATOM_REG_NAME);
  
          if(regName != null && !ATOM_UNDEFINED.equals(regName)) {
              sb.append(regName.atomValue());
          }
          else {
              OtpErlangString pidString = (OtpErlangString) map.get(ATOM_PID);
              if(pidString == null)
                  sb.append("<NULL PID>");
              else
                  sb.append(pidString.stringValue());
          }
          sb.append(" ");
  
          sb.append("+").append(durationFromMap()).append("us");
  
          appendModFuncArity(sb);
  
          return sb;
      }
  
      private long durationFromMap() {
          Object tsCall = map.get(TIMESTAMP_CALL_ATOM);
          Object tsReturn = map.get(TIMESTAMP_RETURN_ATOM);
  
          if(tsCall == null|| tsReturn == null)
              return 0;
  
          return ((OtpErlangLong)tsReturn).longValue() - ((OtpErlangLong)tsCall).longValue();
      }
  
      /**
       * Returns an MFA.
       */
      private OtpErlangTuple getFunctionFromMap() {
          return (OtpErlangTuple)map.get(FN_ATOM);
      }
  
      public boolean isExceptionThrower() {
          return map.containsKey(EXCEPTION_FROM_ATOM);
      }
  
      public StringBuilder appendFunctionToString(StringBuilder sb) {
          OtpErlangTuple tuple = getFunctionFromMap();
  
          return sb.append(ErlyBerly.getTermFormatter().modFuncArgsToString(tuple));
      }
  
      public StringBuilder appendModFuncArity(StringBuilder sb) {
          OtpErlangTuple fn = getFunctionFromMap();
          // there might be no function if this TraceLog is acting as a NODE DOWN
          if(fn == null) {
              return sb;
          }
          return sb.append(ErlyBerly.getTermFormatter().modFuncArityToString(fn));
      }
  
      private StringBuilder appendArgsToString(StringBuilder sb, OtpErlangObject[] elements) {
          if(elements.length > 0)
              ErlyBerly.getTermFormatter().appendToString(elements[0], sb);
  
          for(int i=1; i<elements.length; i++) {
              sb.append(", ");
              ErlyBerly.getTermFormatter().appendToString(elements[i], sb);
          }
          return sb;
      }
  
      public OtpErlangList getArgsList() {
          OtpErlangTuple tuple = getFunctionFromMap();
          OtpErlangList args = OtpUtil.toOtpList(tuple.elementAt(2));
          return args;
      }
  
      public String getPidString() {
          OtpErlangString s = (OtpErlangString)map.get(ATOM_PID);
          if(s == null)
              return "";
          else
              return s.stringValue();
      }
  
      public OtpErlangObject getResultFromMap() {
          Object object = map.get(RESULT_ATOM);
          if(object == null) {
              OtpErlangTuple exception = (OtpErlangTuple) map.get(EXCEPTION_FROM_ATOM);
  
              if(exception != null) {
                  return exception.elementAt(1);
              }
          }
          return (OtpErlangObject) object;
      }
  
      @Override
      public int compareTo(TraceLog o) {
          return Long.compare(instanceNum, o.instanceNum);
      }
  
      public void complete(Map<Object, Object> resultMap) {
          tracePropsToString = null;
          Object e = resultMap.get(EXCEPTION_FROM_ATOM);
          Object ts = resultMap.get(TIMESTAMP_RETURN_ATOM);
          Object r = resultMap.get(RESULT_ATOM);
          if(e != null) {
              map.put(EXCEPTION_FROM_ATOM, e);
              if(r == null) {
                  r = e;
              }
          }
  
          if(r != null) {
              map.put(RESULT_ATOM, r);
              String resultString = ErlyBerly.getTermFormatter().toString((OtpErlangObject) r);
              result.set(resultString);
          }
          if(ts != null)
              map.put(TIMESTAMP_RETURN_ATOM, ts);
  
          duration.set(durationFromMap());
  
          Platform.runLater(() -> { summary.set(toString()); complete.set(true); });
      }
  
      public ReadOnlyBooleanProperty isCompleteProperty() {
          return complete;
      }
  
      public boolean isComplete() {
          return complete.get();
      }
  
      /**
       * A call string is the pid and function with arity.
       */
      public String toCallString() {
          StringBuilder sb = new StringBuilder(255);
          boolean appendArity = true;
          return toCallString(sb, appendArity).toString();
      }
  
      public String getPid() {
          return pid;
      }
  
      public String getRegName() {
          return registeredName;
      }
  
      public long getDuration() {
          return duration.get();
      }
  
      public SimpleLongProperty durationProperty() {
          return duration;
      }
  
      public String getFunction() {
          return function;
      }
  
      public String getArgs() {
          return argsString;
      }
  
      public String getResult() {
          return result.get();
      }
  
      public SimpleStringProperty resultProperty() {
          return result;
      }
  
      public String getCssClass() {
          return cssClass;
      }
  
      public static TraceLog newBreakLog() {
          return new TraceLog("breaker-row", "BREAK");
      }
  
      public static TraceLog newNodeDown() {
          return new TraceLog("breaker-row", "NODE DOWN");
      }
  
      public static TraceLog newLoadShedding() {
          return new TraceLog("breaker-row", "LOAD SHEDDING");
      }
  
      public OtpErlangList getStackTrace() {
          OtpErlangList stacktrace = (OtpErlangList) map.get(OtpUtil.atom("stack_trace"));
          if(stacktrace == null)
              stacktrace = new OtpErlangList();
          return stacktrace;
      }
  
      public ModFunc getModFunc() {
          OtpErlangTuple mfa = getFunctionFromMap();
          OtpErlangAtom module = (OtpErlangAtom) mfa.elementAt(0);
          OtpErlangAtom function = (OtpErlangAtom) mfa.elementAt(1);
          int arity = ((OtpErlangList) mfa.elementAt(2)).arity();
          return new ModFunc(module.atomValue(), function.atomValue(), arity, false, false);
      }
  }
  