package nxm.sys.lib;

import static nxm.sys.lib.XMValue.ASCII;
import static nxm.sys.lib.XMValue.ASCII256;
import static nxm.sys.lib.XMValue.INT_1;
import static nxm.sys.lib.XMValue.INT_2;
import static nxm.sys.lib.XMValue.INT_4;
import static nxm.sys.lib.XMValue.INT_8;
import static nxm.sys.lib.XMValue.INT_U1;
import static nxm.sys.lib.XMValue.KVLIST;
import static nxm.sys.lib.XMValue.LIST;
import static nxm.sys.lib.XMValue.NONE;
import static nxm.sys.lib.XMValue.REAL_4;
import static nxm.sys.lib.XMValue.REAL_8;
import static nxm.sys.lib.XMValue.TABLE;
import static nxm.sys.lib.XMValue.isNumericType;

import java.util.Vector;

import nxm.sys.inc.DataTypes;
import nxm.sys.lib.XMValue.Backend;
import nxm.sys.lib.XMValue.KVPair;

/** A pure-Java implementation of the backend for {@link XMValue}. This is not a public API, all
    XMValue users should use the {@link XMValue} class.
    @author  J. Czechowski IV
    @since   NeXtMidas 2.7.1
 */
class XMValueJava implements Backend {
  private char                type;   // Type specifier
  private Data                data;   // Value for numeric types
  private boolean             scalar; // Is numeric data scalar?
  private Vector<XMValue>     list;   // Value for type=LIST
  private KeyVector           kvlist; // Value for type=KVLIST
  private Table               table;  // Value for type=TABLE

  public XMValueJava () {
    reset(NONE);
  }

  private XMValueJava (char type) {
    reset(type);
  }

  private XMValue newXMValue (String   v) { XMValueJava b = new XMValueJava(); b.setValue(v); return new XMValue(b); }
  private XMValue newXMValue (byte     v) { XMValueJava b = new XMValueJava(); b.setValue(v); return new XMValue(b); }
  private XMValue newXMValue (short    v) { XMValueJava b = new XMValueJava(); b.setValue(v); return new XMValue(b); }
  private XMValue newXMValue (int      v) { XMValueJava b = new XMValueJava(); b.setValue(v); return new XMValue(b); }
  private XMValue newXMValue (long     v) { XMValueJava b = new XMValueJava(); b.setValue(v); return new XMValue(b); }
  private XMValue newXMValue (float    v) { XMValueJava b = new XMValueJava(); b.setValue(v); return new XMValue(b); }
  private XMValue newXMValue (double   v) { XMValueJava b = new XMValueJava(); b.setValue(v); return new XMValue(b); }
  public  void    setValue   (String   v) { reset((char)DataTypes.ASCII);  data = new Data(v); scalar = true;  }
  public  void    setValue   (byte     v) { reset((char)DataTypes.BYTE);   data = new Data(v); scalar = true;  }
  public  void    setValue   (short    v) { reset((char)DataTypes.INT);    data = new Data(v); scalar = true;  }
  public  void    setValue   (int      v) { reset((char)DataTypes.LONG);   data = new Data(v); scalar = true;  }
  public  void    setValue   (long     v) { reset((char)DataTypes.XLONG);  data = new Data(v); scalar = true;  }
  public  void    setValue   (float    v) { reset((char)DataTypes.FLOAT);  data = new Data(v); scalar = true;  }
  public  void    setValue   (double   v) { reset((char)DataTypes.DOUBLE); data = new Data(v); scalar = true;  }
  public  void    setValue   (byte[]   v) { reset((char)DataTypes.BYTE);   data = new Data(v); scalar = false; }
  public  void    setValue   (short[]  v) { reset((char)DataTypes.INT);    data = new Data(v); scalar = false; }
  public  void    setValue   (int[]    v) { reset((char)DataTypes.LONG);   data = new Data(v); scalar = false; }
  public  void    setValue   (long[]   v) { reset((char)DataTypes.XLONG);  data = new Data(v); scalar = false; }
  public  void    setValue   (float[]  v) { reset((char)DataTypes.FLOAT);  data = new Data(v); scalar = false; }
  public  void    setValue   (double[] v) { reset((char)DataTypes.DOUBLE); data = new Data(v); scalar = false; }
  public  double  toD ()                  { return data.toD(); }
  public  float   toF ()                  { return data.toF(); }
  public  long    toX ()                  { return data.toX(); }
  public  int     toL ()                  { return data.toNumber().intValue(); } // since 3.9.1
  public  short   toI ()                  { return data.toI(); }
  public  byte    toB ()                  { return data.toB(); }
  public  String  toA ()                  { return (data != null)? data.toString() : null; }
  public  String  toZ ()                  { return (data != null)? data.toString() : null; }
  public  char    getFormatType ()              { return type; }

  private XMValue newXMValue (String key, XMValue val) {
    XMValueJava b = new XMValueJava();
    b.reset(KVLIST);
    b.kvlist = new KeyVector();
    b.kvlist.add(key, val);
    return new XMValue(b);
  }

  /** Reset internal variables and set type to t. */
  private void reset (char t) {
    type   = t;
    data   = null;
    scalar = true;
    list   = null;
    table  = null;
    kvlist = null;
  }

  /** Reset internal variables and set type to t. */
  private XMValueJava copy () {
    XMValueJava b = new XMValueJava();
    b.setValue(this);
    return b;
  }

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

  public void setValue (XMValue v) {
    Backend b = v.getBackend();

    if (b instanceof XMValueJava) {
      setValue((XMValueJava)b);
      return;
    }
    char t = v.getFormatType();
    int  s = v.getSize();

    if (t == NONE ) { reset(t);          return; }
    if (t == ASCII) { setValue(v.toA()); return; }

    if (isNumericType(t)) {
      reset(t);
      scalar = b.isScalar();
      if (scalar) {
        switch (t) {
          case INT_1:  setValue(v.toB()); return;
          case INT_2:  setValue(v.toI()); return;
          case INT_4:  setValue(v.toL()); return;
          case INT_8:  setValue(v.toX()); return;
          case REAL_4: setValue(v.toF()); return;
          case REAL_8: setValue(v.toD()); return;
          default:     throw new AssertionError("Invalid XMValue type '"+t+"'.");
        }
      }
      else {
        switch (type) {
          case INT_1:  data = new Data("SB",s);  for (int i=0; i<s; i++) data.setB(i,v.get(i).toB());  break;
          case INT_2:  data = new Data("SI",s);  for (int i=0; i<s; i++) data.setI(i,v.get(i).toI());  break;
          case INT_4:  data = new Data("SL",s);  for (int i=0; i<s; i++) data.setL(i,v.get(i).toL());  break;
          case INT_8:  data = new Data("SX",s);  for (int i=0; i<s; i++) data.setX(i,v.get(i).toX());  break;
          case REAL_4: data = new Data("SF",s);  for (int i=0; i<s; i++) data.setF(i,v.get(i).toF());  break;
          case REAL_8: data = new Data("SD",s);  for (int i=0; i<s; i++) data.setD(i,v.get(i).toD());  break;
          default:     throw new AssertionError("Invalid XMValue type '"+type+"'.");
        }
      }
    }

    if (t == LIST) {
      reset(t);
      list = new Vector<>();
      for (int i = 0; i < s; i++) {
        list.add(v.get(i));
      }
    }

    if (t == KVLIST) {
      reset(t);
      kvlist = new KeyVector();
      for (int i = 0; i < s; i++) {
        KVPair pair = b.getPair(i);
        add(pair.getKey(), pair.getValue());
      }
    }

    if (t == TABLE) {
      reset(t);
      table = new Table();
      Backend bb = b.vectorize().getBackend();
      for (int i = 0; i < s; i++) {
        KVPair pair = bb.getPair(i);
        add(pair.getKey(), pair.getValue());
      }
    }

    throw new AssertionError("Invalid XMValue type '"+type+"'.");
  }

  private void setValue (XMValueJava b) {
    reset(b.type);
    if (b.data != null) {
      scalar = b.scalar;
      data   = b.data.copy();
      return;
    }
    switch (type) {
      case NONE:                                             break;
      case LIST:   list   = new Vector<>(b.list);     break;
      case KVLIST: kvlist = b.kvlist.copy();                 break;
      case TABLE:  table  = b.table.copy();                  break;
      default:     throw new AssertionError("Invalid XMValue type '"+type+"'.");
    }
  }

  public int size () {
    if (data != null) return data.getSize();

    switch (type) {
      case NONE:   return 0;
      case LIST:   return list.size();
      case KVLIST: return kvlist.size();
      case TABLE:  return table.size();
      default:     throw new AssertionError("Invalid XMValue type '"+type+"'.");
    }
  }

  public void clear () {
    if (data != null) {
      data.setSize(0);
      return;
    }

    switch (type) {
      case NONE:                   break;
      case LIST:   list.clear();   break;
      case KVLIST: kvlist.clear(); break;
      case TABLE:  table.clear();  break;
      default:     throw new AssertionError("Invalid XMValue type '"+type+"'.");
    }
  }

  public boolean isScalar () {
    return scalar;
  }

  public XMValue get (int i) {
    switch (type) {
      case NONE:   return newXMValue(0);
      case ASCII:  return newXMValue(data.toString());
      case INT_1:  return newXMValue(data.getB(i-1));
      case INT_U1: return newXMValue(data.getP(i-1));
      case INT_2:  return newXMValue(data.getI(i-1));
      case INT_4:  return newXMValue(data.getL(i-1));
      case INT_8:  return newXMValue(data.getX(i-1));
      case REAL_4: return newXMValue(data.getF(i-1));
      case REAL_8: return newXMValue(data.getD(i-1));
      case LIST:   return list.get(i-1);
      case KVLIST: return newXMValue(kvlist.getKey(i-1), (XMValue)kvlist.get(i-1));
      default:     throw new AssertionError("Invalid XMValue type '"+type+"'.");
    }
  }

  public void set (int i, String k, XMValue v) { // only used on a KVLIST
    kvlist.set(i-1, k, v);
  }

  public void set (int i, XMValue v) {
    switch (type) {
      case ASCII:    data = new Data(v.toA());   break;
      case ASCII256: data = new Data(v.toZ(),true); break; // NXM-3570
      case INT_1:    data.setB(i-1,v.toB());     break;
      case INT_2:    data.setI(i-1,v.toI());     break;
      case INT_4:    data.setL(i-1,v.toL());     break;
      case INT_8:    data.setX(i-1,v.toX());     break;
      case REAL_4:   data.setF(i-1,v.toF());     break;
      case REAL_8:   data.setD(i-1,v.toD());     break;
      case LIST:     list.set(i-1,v);            break;
      default:     throw new AssertionError("Invalid XMValue type '"+type+"'.");
    }
  }

  public KVPair getPair (int i) {
    String  key = kvlist.getKey(i-1);
    XMValue val = (XMValue)kvlist.get(i-1);
    return new KVPair(key, val);
  }

  public XMValue get (String key) {
    switch (type) {
      case KVLIST: return (XMValue)kvlist.get(key);
      case TABLE:  return (XMValue)table.get(key);
      default:     throw new AssertionError("Invalid XMValue type '"+type+"'.");
    }
  }

  public void setValue (char type) {
    reset(type);
    switch (type) {
      case NONE:                                   break;
      case ASCII:    data   = new Data("");        break;
      case ASCII256: data   = new Data("",true);   break; // force type to 'Z' NXM-3570
      case INT_1:    data   = new Data("SB", 0);   break;
      case INT_U1:   data   = new Data("SO", 0);   break;
      case INT_2:    data   = new Data("SI", 0);   break;
      case INT_4:    data   = new Data("SL", 0);   break;
      case INT_8:    data   = new Data("SX", 0);   break;
      case REAL_4:   data   = new Data("SF", 0);   break;
      case REAL_8:   data   = new Data("SD", 0);   break;
      case LIST:   list   = new Vector<>(); break;
      case KVLIST: kvlist = new KeyVector();       break;
      case TABLE:  table  = new Table();           break;
      default:     throw new AssertionError("Invalid XMValue type '"+type+"'.");
    }
  }

  public void setValue (char type, int size, double value) {
    reset(type);
    int i = 0;

    if (type == LIST) {
      XMValue v = newXMValue(value);
      list = new Vector<>();
      while (i<size) { list.add(v); i++; }
      return;
    }

    switch (type) {
      case INT_1:  { data = new Data("SB",size);  byte   v = (byte )value;  while (i<size) data.setB(i++,v); } break;
      case INT_U1: { data = new Data("SO",size);  byte   v = (byte )value;  while (i<size) data.setP(i++,v); } break;
      case INT_2:  { data = new Data("SI",size);  short  v = (short)value;  while (i<size) data.setI(i++,v); } break;
      case INT_4:  { data = new Data("SL",size);  int    v = (int  )value;  while (i<size) data.setL(i++,v); } break;
      case INT_8:  { data = new Data("SX",size);  long   v = (long )value;  while (i<size) data.setX(i++,v); } break;
      case REAL_4: { data = new Data("SF",size);  float  v = (float)value;  while (i<size) data.setF(i++,v); } break;
      case REAL_8: { data = new Data("SD",size);  double v =        value;  while (i<size) data.setD(i++,v); } break;
      default:     throw new AssertionError("Invalid XMValue type '"+type+"'.");
    }
  }

  public boolean containsKey (String key) {
    switch (type) {
      case KVLIST: return (kvlist.findKey(key) >= 0);
      case TABLE:  return table.containsKey(key);
      default:     throw new AssertionError("Invalid XMValue type '"+type+"'.");
    }
  }

  public void add (XMValue v) {
    if (type == LIST) { list.add(v); return; }

    int size = data.getSize();
    data.setSize(size + 1);

    switch (type) {
      case INT_1:  data.setB(size, v.toB()); break;
      case INT_U1: data.setB(size, v.toB()); break;
      case INT_2:  data.setI(size, v.toI()); break;
      case INT_4:  data.setL(size, v.toL()); break;
      case INT_8:  data.setX(size, v.toX()); break;
      case REAL_4: data.setF(size, v.toF()); break;
      case REAL_8: data.setD(size, v.toD()); break;
      default:     throw new AssertionError("Invalid XMValue type '"+type+"'.");
    }
  }

  public void add (String key, XMValue val) {
    switch (type) {
      case KVLIST: kvlist.add(key, val); break;
      case TABLE:  table .put(key, val); break;
    }
  }

  public void put (String key, XMValue val) {
    switch (type) {
      case KVLIST: kvlist.put(key, val); break;
      case TABLE:  table .put(key, val); break;
    }
  }

  public XMValue vectorize () {
    XMValueJava b = copy();
    b.scalar = false;
    return new XMValue(b);
  }

  public XMValue keys () {
    String[] keys = (type == TABLE)? table.getKeys() : kvlist.getKeys();

    XMValueJava b = new XMValueJava(LIST);
    b.list = new Vector<>();
    for (String key : keys) {
      b.list.add(newXMValue(key));
    }
    return new XMValue(b);
  }

  public XMValue tablize () {
    if (type == TABLE) return new XMValue(copy());

    XMValueJava v = new XMValueJava(TABLE);
    v.table = new Table();
    for (int i = 0; i < kvlist.size(); i++) {
      v.table.put(kvlist.getKey(i), kvlist.get(i));
    }
    return new XMValue(v);
  }

  public XMValue items () {
    if (type == KVLIST) return new XMValue(copy());

    XMValueJava b = new XMValueJava(KVLIST);
    b.kvlist = new KeyVector();
    for (String key : table.getKeys()) {
      b.kvlist.add(key, table.get(key));
    }
    return new XMValue(b);
  }
}
