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.node;
  
  import java.io.IOException;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  
  import com.ericsson.otp.erlang.OtpConn;
  import com.ericsson.otp.erlang.OtpErlangAtom;
  import com.ericsson.otp.erlang.OtpErlangBinary;
  import com.ericsson.otp.erlang.OtpErlangExternalFun;
  import com.ericsson.otp.erlang.OtpErlangFun;
  import com.ericsson.otp.erlang.OtpErlangList;
  import com.ericsson.otp.erlang.OtpErlangLong;
  import com.ericsson.otp.erlang.OtpErlangMap;
  import com.ericsson.otp.erlang.OtpErlangObject;
  import com.ericsson.otp.erlang.OtpErlangString;
  import com.ericsson.otp.erlang.OtpErlangTuple;
  import com.ericsson.otp.erlang.OtpMbox;
  
  /**
   * Sin bin for utils dealing with jinterface.
   */
  public class OtpUtil {
  
      private static final OtpErlangAtom ERLYBERLY_RECORD_ATOM = OtpUtil.atom("erlyberly_record");
  
      private static final OtpErlangAtom ERLYBERLY_RECORD_FIELD_ATOM = OtpUtil.atom("erlyberly_record_field");
  
      public static final Set<Class<?>> CONTAINER_TERM_TYPES = new HashSet<Class<?>>(
          Arrays.asList(OtpErlangTuple.class, OtpErlangMap.class, OtpErlangList.class)
      );
  
  
      public static final Set<Class<?>> LARGE_TERM_TYPES = new HashSet<Class<?>>(
          Arrays.asList(OtpErlangFun.class, OtpErlangExternalFun.class)
      );
  
      private static final OtpErlangAtom TRUE_ATOM = new OtpErlangAtom(true);
      private static final OtpErlangAtom FALSE_ATOM = new OtpErlangAtom(false);
      private static final OtpErlangAtom CALL_ATOM = new OtpErlangAtom("call");
      private static final OtpErlangAtom USER_ATOM = new OtpErlangAtom("user");
      private static final OtpErlangAtom ERROR_ATOM = atom("error");
      public static final OtpErlangAtom OK_ATOM = atom("ok");
  
      private OtpUtil() {}
  
      public static OtpErlangTuple tuple(Object... elements) {
          OtpErlangObject[] tuple = toOtpElementArray(elements);
  
          return new OtpErlangTuple(tuple);
      }
  
      public static OtpErlangList list(Object... elements) {
          OtpErlangObject[] tuple = toOtpElementArray(elements);
  
          return new OtpErlangList(tuple);
      }
  
      private static OtpErlangObject[] toOtpElementArray(Object... elements) {
          OtpErlangObject[] tuple = new OtpErlangObject[elements.length];
  
          for(int i=0; i < elements.length; i++) {
              Object e = elements[i];
              if(e instanceof Integer) {
                  tuple[i] = new OtpErlangLong((Integer)e);
              }
              else if(e instanceof Long) {
                  tuple[i] = new OtpErlangLong((Long)e);
              }
              else if(e instanceof OtpErlangObject) {
                  tuple[i] = (OtpErlangObject) e;
              }
              else if(e instanceof String) {
                  tuple[i] = new OtpErlangString((String)e);
              }
              else if(e instanceof Boolean) {
                  if(Boolean.TRUE.equals(e))
                      tuple[i] = TRUE_ATOM;
                  else
                      tuple[i] = FALSE_ATOM;
              }
              else {
                  throw new RuntimeException(e + " cannot be converted to an OtpErlangObject");
              }
          }
          return tuple;
      }
  
  
      public static OtpErlangAtom atom(String name) {
          return new OtpErlangAtom(name.intern());
      }
  
      /**
       * Take an {@link OtpErlangList} of erlang key value tuples and converts it to a map.
       */
      public static Map<Object, Object> propsToMap(OtpErlangList pinfo) {
          HashMap<Object, Object> map = new HashMap<>();
          for (OtpErlangObject otpErlangObject : pinfo) {
              if(otpErlangObject instanceof OtpErlangTuple && ((OtpErlangTuple) otpErlangObject).arity() == 2) {
                  OtpErlangTuple tuple = ((OtpErlangTuple) otpErlangObject);
                  map.put(tuple.elementAt(0), tuple.elementAt(1));
              }
          }
          return map;
      }
  
      public static OtpErlangObject[] elementsForTerm(OtpErlangObject obj) {
          assert obj != null;
  
          if(obj instanceof OtpErlangTuple)
              return ((OtpErlangTuple) obj).elements();
          else if(obj instanceof OtpErlangList)
              return ((OtpErlangList) obj).elements();
          else
              throw new RuntimeException("No brackets for type " + obj.getClass());
      }
  
  
  
      public static boolean isTupleTagged(OtpErlangObject tag, OtpErlangObject result) {
          return isTupleTagged(tag, 0, result);
      }
  
      public static boolean isTupleTagged(OtpErlangObject tag, int index, OtpErlangObject result) {
          boolean r = false;
  
          if(result instanceof OtpErlangTuple) {
              OtpErlangTuple resultTuple = (OtpErlangTuple) result;
              r = resultTuple.arity() > 0 && resultTuple.elementAt(0).equals(tag);
          }
  
          return r;
      }
  
      /**
       * Checks if a term is tuple tagged with the erlyberly_record atom, meaning
       * it has metadata tagged with record and field names around the field values.
       */
      public static boolean isErlyberlyRecord(OtpErlangObject obj) {
          return isTupleTagged(ERLYBERLY_RECORD_ATOM, obj);
      }
  
      public static boolean isErlyberlyRecordField(OtpErlangObject obj) {
          return isTupleTagged(ERLYBERLY_RECORD_FIELD_ATOM, obj);
      }
  
      public static boolean isErrorReason(OtpErlangObject reason, OtpErlangObject error) {
          assert isTupleTagged(ERROR_ATOM, error) : "tuple " + error + "is not tagged with 'error'";
          return isTupleTagged(reason, 1, error);
      }
  
      public static OtpErlangList toOtpList(OtpErlangObject obj) {
          if(obj instanceof OtpErlangList) {
              return (OtpErlangList) obj;
          }
          else if(obj instanceof OtpErlangString) {
              OtpErlangString s = (OtpErlangString) obj;
  
              return new OtpErlangList(s.stringValue());
          }
          else {
              throw new ClassCastException("" + obj + " cannot be converted to an OtpErlangList");
          }
      }
  
      public static void sendRPC(OtpConn conn, OtpMbox m, OtpErlangAtom mod, OtpErlangAtom fun, OtpErlangList args) throws IOException {
          OtpErlangTuple rpcMessage = tuple(m.self(), tuple(CALL_ATOM, mod, fun, args, USER_ATOM));
  
          conn.send(m.self(), "rex", rpcMessage);
      }
  
      public static OtpErlangObject tupleElement(int i, OtpErlangObject obj) {
          return ((OtpErlangTuple)obj).elementAt(i);
      }
  
      public static OtpErlangObject[] iterableElements(OtpErlangObject obj) {
          if(obj instanceof OtpErlangTuple)
              return ((OtpErlangTuple) obj).elements();
          else if(obj instanceof OtpErlangList)
              return ((OtpErlangList) obj).elements();
          else if(obj instanceof OtpErlangString) {
              OtpErlangString s = (OtpErlangString) obj;
              return new OtpErlangList(s.stringValue()).elements();
          }
          else if(obj instanceof OtpErlangMap){
              OtpErlangMap m = (OtpErlangMap) obj;
              return new OtpErlangList(m.keys()).elements();
          }
          else {
              throw new RuntimeException("" + obj + " cannot return OtpErlangObject[]");
          }
      }
  
      /**
       * A short term can be displayed on a single line and does not have to be
       * broken down further.
       */
      public static boolean isLittleTerm(OtpErlangObject obj) {
          OtpErlangObject[] elements;
  
          if(obj instanceof OtpErlangList) {
              // if the list is empty consider it a little term
              return ((OtpErlangList)obj).arity() == 0;
          }
          else if(LARGE_TERM_TYPES.contains(obj.getClass())) {
              return false;
          }
          else if(CONTAINER_TERM_TYPES.contains(obj.getClass())) {
              elements = iterableElements(obj);
              // short lists and tuples which do not contain other short lists or
              // tuples are ok
              if(elements.length > 3)
                  return false;
              }
          else {
              return true;
          }
  
          for (OtpErlangObject e : elements) {
              if(LARGE_TERM_TYPES.contains(e.getClass()) || CONTAINER_TERM_TYPES.contains(e.getClass())) {
                  return false;
              }
              else if(e instanceof OtpErlangString) {
                  int stringLength = ((OtpErlangString) e).stringValue().length();
                  if(stringLength > 50)
                      return true;
              }
              else if(e instanceof OtpErlangBinary) {
                  int binaryLength = ((OtpErlangBinary) e).size();
                  if(binaryLength > 50)
                      return true;
              }
  
          }
          return true;
      }
  
      /**
       * jinterface interprets lists of integers OtpErlangString whatever
       * might be the intent, for example a list of function arguments `[10]`.
       *
       * This can cause ClassCastExceptions when something that is normally an
       * OtpErlangList comes back as an OtpErlangString, which does not inherit
       * from OtpErlangList!
       */
      public static OtpErlangList toErlangList(OtpErlangObject obj) {
          if(obj instanceof OtpErlangString) {
              return new OtpErlangList(((OtpErlangString)obj).stringValue());
          }
          else {
              // we have done our best to convert the nobbly string objects to
              // lists, if it fails just throw a ClassCastException
              return (OtpErlangList)obj;
          }
      }
  
      public static OtpErlangTuple toTuple(OtpErlangObject obj) {
          if(!(obj instanceof OtpErlangTuple)) {
              throw new ClassCastException(obj + " cannot be case to OtpErlangTuple.");
          }
          return (OtpErlangTuple) obj;
      }
  }
  