package nxm.sys.libxm;

import nxm.sys.inc.DataTypes;
import nxm.sys.inc.InternalUseOnly;
import nxm.sys.inc.Keyable;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Data;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.Parser;
import nxm.sys.lib.XMValue;
import nxm.sys.lib.XMValue.Backend;
import nxm.sys.lib.XMValue.KVPair;

/** A wrapper for X-Midas's native C++ implementation for {@link XMValue}. This is not a public API,
    all public XMValue users should use the {@link XMValue} class. <b>This class should never be
    called directly (except internal to XMValue), and is subject to change without notice.</b>
    @author  J. Czechowski IV
    @since   NeXtMidas 2.7.1
 */
@InternalUseOnly
public class XMValueNative implements Backend {
  private long pointer; // pointer to native resource
  private boolean owned;

  static {
    XMidas.loadXmJvmLib(); // Load the JNI library that implements the native methods
  }

  /** New instance. */
  public XMValueNative () {
    this.pointer = init();
    owned = true;
  }

  /** New instance using given pointer. */
  private XMValueNative (long pntr) {
    this.pointer = pntr;
    owned = true;
  }

  /**
   * {@inheritDoc}
   * @deprecated will be removed in 4.1.0
   */
  @Deprecated
  protected void finalize () throws Throwable {
    delete();
  }

  /** Frees the pointer by deleting its associated XMValue
   *  @since NeXtMidas 4.1.3 */
  public void delete() {
    if (owned) {
      free(pointer);
      owned = false;
      pointer = 0;
    }
  }

  @Override
  public String toString () {
    return getClass().getName()+" (pointer=0x"+Long.toHexString(pointer)+")";
  }

  @Override
  public boolean equals (Object obj) {
    return (obj instanceof XMValueNative) && (((XMValueNative)obj).pointer == pointer);
  }

  @Override
  public int hashCode () {
    return (int)((pointer >>> 32) ^ pointer);
  }

  /** Create new XMValue with given native pointer. */
  private static XMValue newXMValue (long pntr) {
    if (pntr == 0) return null;
    XMValueNative b = new XMValueNative(pntr);
    return new XMValue(b);
  }

  /** Get pointer for given XMValue, if not a native instance, convert to native first. */
  private static long pntr (XMValue v) {
    Backend b = v.getBackend();
    if (b instanceof XMValueNative) {
      return ((XMValueNative)b).pointer;
    }

    // Change the backend of the value to be a native one
    XMValueNative val = new XMValueNative();
    val.setValue(v);
    v.setBackend(val);

    return val.pointer;
  }

  public void    setValue (String   v)  { setValuea(pointer, v); }
  public void    setValue (byte     v)  { setValueb(pointer, v); }
  public void    setValue (short    v)  { setValuei(pointer, v); }
  public void    setValue (int      v)  { setValuel(pointer, v); }
  public void    setValue (long     v)  { setValuex(pointer, v); }
  public void    setValue (float    v)  { setValuef(pointer, v); }
  public void    setValue (double   v)  { setValued(pointer, v); }
  public void    setValue (byte[]   v)  { setValueB(pointer, v); }
  public void    setValue (short[]  v)  { setValueI(pointer, v); }
  public void    setValue (int[]    v)  { setValueL(pointer, v); }
  public void    setValue (long[]   v)  { setValueX(pointer, v); }
  public void    setValue (float[]  v)  { setValueF(pointer, v); }
  public void    setValue (double[] v)  { setValueD(pointer, v); }
  public void    setValue (XMValue  v)  { setValueXMValue(pointer,pntr(v)); }
  public void    clear ()               { clear(pointer); }
  public char    getFormatType ()       { return getType(pointer); }
  public boolean isScalar ()            { return isScalar(pointer); }
  public boolean containsKey (String k) { return containsKey(pointer,k); }
  public double  toD ()                 { return toD(pointer); }
  public float   toF ()                 { return toF(pointer); }
  public long    toX ()                 { return toX(pointer); }
  public int     toL ()                 { return toL(pointer); }
  public short   toI ()                 { return toI(pointer); }
  public byte    toB ()                 { return toB(pointer); }
  public String  toA ()                 { return toA(pointer); }
  public String  toZ ()                 { return toZ(pointer); }
  public XMValue get (int i)            { return newXMValue( getI(pointer,i)    ); }
  public XMValue get (String k)         { return newXMValue( getK(pointer,k)    ); }
  public XMValue vectorize ()           { return newXMValue( vectorize(pointer) ); }
  public XMValue tablize ()             { return newXMValue( tablize(pointer)   ); }
  public XMValue keys ()                { return newXMValue( keys(pointer)      ); }
  public XMValue items ()               { return newXMValue( items(pointer)     ); }

  public void    add      (                 XMValue v) { addV  (pointer,       pntr(v)); }
  public void    add      (       String k, XMValue v) { addKV (pointer,    k, pntr(v)); }
  public void    put      (       String k, XMValue v) { putKV (pointer,    k, pntr(v)); }
  public void    set      (int i,           XMValue v) { setIV (pointer, i,    pntr(v)); }
  public void    set      (int i, String k, XMValue v) { setIKV(pointer, i, k, pntr(v)); }
  public void    setValue (char t)                     { setValueT  (pointer, t      ); }
  public void    setValue (char t, int s, double v)    { setValueTSV(pointer, t, s, v); }

  public int size () {
    long size = size(pointer);
    if (size > Integer.MAX_VALUE) {
      throw new MidasException("Size of XMValue "+size+" exceeds Java limit of "+Integer.MAX_VALUE+".");
    }
    return (int)size;
  }

  public KVPair getPair (int i) {
    String key = getKey(pointer,i);
    long   val = getVal(pointer,i);
    return new KVPair(key, newXMValue(val));
  }

  public void setValue (Data v) {
    Data data = v.copy();
    data.setSPA(1); // Converts 'S'->1*'S' 'C'->2*'S' 'V'->3*'S', etc.

    char type = (char)v.getFormatType();

    if (v.getSize() == 1) {  // scalar
      switch (type) {
        case DataTypes.BYTE:   setValue(data.getB(0));  break;
        case DataTypes.INT:    setValue(data.getI(0));  break;
        case DataTypes.LONG:   setValue(data.getL(0));  break;
        case DataTypes.XLONG:  setValue(data.getX(0));  break;
        case DataTypes.FLOAT:  setValue(data.getF(0));  break;
        case DataTypes.DOUBLE: setValue(data.getD(0));  break;
        default:   throw new AssertionError("Invalid XMValue type '"+type+"'.");
      }
    }
    else {
      switch (type) {
        case DataTypes.BYTE:   { byte[]   array = data.castB(true); setValue(array); data.uncast(array,false); }  break;
        case DataTypes.INT:    { short[]  array = data.castI(true); setValue(array); data.uncast(array,false); }  break;
        case DataTypes.LONG:   { int[]    array = data.castL(true); setValue(array); data.uncast(array,false); }  break;
        case DataTypes.XLONG:  { long[]   array = data.castX(true); setValue(array); data.uncast(array,false); }  break;
        case DataTypes.FLOAT:  { float[]  array = data.castF(true); setValue(array); data.uncast(array,false); }  break;
        case DataTypes.DOUBLE: { double[] array = data.castD(true); setValue(array); data.uncast(array,false); }  break;
        default:   throw new AssertionError("Invalid XMValue type '"+type+"'.");
      }
    }
  }

  /** Does a m_vrfind(..) with the given result name. Returns null if not found. */
  static final XMValue vrfind (String name) {
    return newXMValue( vrfind0(name) );
  }

  /** Does a m_vrslt(..) with the given result name and value. */
  static final void vrslt (String name, XMValue val) {
    vrslt(name, pntr(val));
  }

  /** Does the current X-Midas version support XMValue?
   *  @return true if the current X-Midas version supports XMValue */
  public  static final native boolean isSupported     ();
  private static final native long    vrfind0         (String name);
  private static final native void    vrslt           (String name, long pntr);
  private static final native long    init            ();             // create new instance
  private static final native void    free            (long pointer); // done with instance
  private static final native void    setValuea       (long pointer, String   v);
  private static final native void    setValueb       (long pointer, byte     v);
  private static final native void    setValuei       (long pointer, short    v);
  private static final native void    setValuel       (long pointer, int      v);
  private static final native void    setValuex       (long pointer, long     v);
  private static final native void    setValuef       (long pointer, float    v);
  private static final native void    setValued       (long pointer, double   v);
  private static final native void    setValueB       (long pointer, byte[]   v);
  private static final native void    setValueI       (long pointer, short[]  v);
  private static final native void    setValueL       (long pointer, int[]    v);
  private static final native void    setValueX       (long pointer, long[]   v);
  private static final native void    setValueF       (long pointer, float[]  v);
  private static final native void    setValueD       (long pointer, double[] v);
  private static final native void    setValueXMValue (long pointer, long  pntr);

  private static final native void    addV            (long pointer,                         long pntr);
  private static final native void    addKV           (long pointer,             String key, long pntr);
  private static final native void    putKV           (long pointer,             String key, long pntr);
  private static final native void    setIV           (long pointer, int  index,             long pntr);
  private static final native void    setIKV          (long pointer, int  index, String key, long pntr);
  private static final native void    setValueT       (long pointer, char type                    );
  private static final native void    setValueTSV     (long pointer, char type, int size, double v);

  private static final native void    clear           (long pointer);
  private static final native char    getType         (long pointer);
  private static final native long    size            (long pointer);
  private static final native boolean isScalar        (long pointer);
  private static final native boolean containsKey     (long pointer, String key);
  private static final native double  toD             (long pointer);
  private static final native float   toF             (long pointer);
  private static final native long    toX             (long pointer);
  private static final native int     toL             (long pointer);
  private static final native short   toI             (long pointer);
  private static final native byte    toB             (long pointer);
  private static final native String  toA             (long pointer);
  private static final native String  toZ             (long pointer);
  private static final native long    getI            (long pointer, int index);
  private static final native long    getK            (long pointer, String key);
  private static final native long    vectorize       (long pointer);
  private static final native long    tablize         (long pointer);
  private static final native long    keys            (long pointer);
  private static final native long    items           (long pointer);
  private static final native String  getKey          (long pointer, int index);
  private static final native long    getVal          (long pointer, int index);


  //////////////////////////////////////////////////////////////////////////////////////////////////
  // Results Table
  //////////////////////////////////////////////////////////////////////////////////////////////////
  /** Access to X-Midas results table. */
  static class ResultTable extends Object implements Keyable {
    public Object getKey (String key) {
      return XMValueNative.vrfind(key);
    }

    public Object setKey (String key, Object value) {
      if (key.startsWith("\"") && key.endsWith("\"")) {
        key = key.substring(1, key.length()-1); // Remove quotes (used to access hidden results).
      }

      if (value == null) {
        XMidas.del_result(key, 0);
        return null;
      }
      else {
        char type = Convert.guessType(value, (char)DataTypes.STRING, '_');
        XMValue val = XMValue.toXMValue(value, ' ', type, null);
        XMValueNative.vrslt(key, val);
        return val;
      }
    }

    public String[] getKeys() {
      StringBuffer sb = XMidas.getResultNames("*");
      Parser p = new Parser(sb.toString());
      return p.getArray(0);
    }

    public String toString() {
      return "X-Midas ResultsTable";
    }
  }
}
