/**
 * Copyright (c) 2015 MapR, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ojai.json.impl;

import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;

import org.ojai.Document;
import org.ojai.Value;
import org.ojai.Value.Type;
import org.ojai.annotation.API;
import org.ojai.exceptions.TypeException;
import org.ojai.store.ValueBuilder;
import org.ojai.types.ODate;
import org.ojai.types.OInterval;
import org.ojai.types.OTime;
import org.ojai.types.OTimestamp;

/**
 * Helper class providing set of static methods to create instance of
 * JsonValue class from different input types
 */
@API.Internal
public class JsonValueBuilder implements ValueBuilder {

  public static final JsonValueBuilder INSTANCE = new JsonValueBuilder();

  /**
   * Singleton, should not be instantiated.
   */
  private JsonValueBuilder() {}

  public static JsonValue initFromNull() {
    return new JsonValue(Type.NULL);
  }

  public static JsonValue initFrom(boolean value) {
    JsonValue v = new JsonValue(Type.BOOLEAN);
    v.setPrimValue((value) ? 1 : 0);
    return v;
  }

  public static JsonValue initFrom(String value) {
    JsonValue v = new JsonValue(Type.STRING);
    v.setObjValue(value);
    return v;
  }

  public static JsonValue initFrom(byte value) {
    JsonValue v = new JsonValue(Type.BYTE);
    v.setPrimValue(value);
    return v;
  }

  public static JsonValue initFrom(short value) {
    JsonValue v = new JsonValue(Type.SHORT);
    v.setPrimValue(value);
    return v;
  }

  public static JsonValue initFrom(int value) {
    JsonValue v = new JsonValue(Type.INT);
    v.setPrimValue(value);
    return v;
  }

  public static JsonValue initFrom(long value) {
    JsonValue v = new JsonValue(Type.LONG);
    v.setPrimValue(value);
    return v;
  }

  public static JsonValue initFrom(float value) {
    JsonValue v = new JsonValue(Type.FLOAT);
    v.setPrimValue(Float.floatToIntBits(value));
    return v;
  }

  public static JsonValue initFrom(double value) {
    JsonValue v = new JsonValue(Type.DOUBLE);
    v.setPrimValue(Double.doubleToLongBits(value));
    return v;
  }

  public static JsonValue initFrom(OTime value) {
    JsonValue v = new JsonValue(Type.TIME);
    v.setPrimValue(value.toTimeInMillis());
    return v;
  }

  public static JsonValue initFrom(ODate value) {
    JsonValue v = new JsonValue(Type.DATE);
    v.setPrimValue(value.toDaysSinceEpoch());
    return v;
  }

  public static JsonValue initFrom(BigDecimal value) {
    JsonValue v = new JsonValue(Type.DECIMAL);
    v.setObjValue(value);
    return v;
  }

  // NOTE : We are ignoring the nano part of the timestamp here
  public static JsonValue initFrom(OTimestamp value) {
    JsonValue v = new JsonValue(Type.TIMESTAMP);
    v.setPrimValue(value.getMillis());
    return v;
  }

  public static JsonValue initFrom(OInterval value) {
    JsonValue v = new JsonValue(Type.INTERVAL);
    v.setPrimValue(value.getTimeInMillis());
    return v;
  }

  public static JsonValue initFrom(ByteBuffer value) {
    JsonValue v = new JsonValue(Type.BINARY);
    v.setObjValue(value);
    return v;
  }

  public static<T extends Object> JsonValue initFrom(List<T> list) {
    if (list instanceof JsonList) {
      return (JsonValue) list;
    }

    JsonList l = new JsonList();
    for (Object o : list) {
      l.addToList(initFromObject(o));
    }
    return l;
  }

  public static <T extends Object> JsonValue initFrom(Map<String, T> map) {
    // We can't put the JsonDocument directly into the map as
    // it could be part of some other document as well and we
    // will be putting information like order of field in each
    // keyvalue which would change based on which document tree
    // this value is part of. Hence make a shallow copy of the
    // value before return it.
    if (map instanceof JsonDocument) {
      return ((JsonDocument) map).shallowCopy();
    }

    JsonDocument rec = new JsonDocument();
    for (String k : map.keySet()) {
      rec.set(k, initFromObject(map.get(k)));
      //rec.put(k, map.get(k));
    }
    return rec;
  }

  public static JsonValue initFrom(Document value) {
    if (value instanceof JsonDocument) {
      return ((JsonDocument) value).shallowCopy();
    }

    JsonDocument r = new JsonDocument();
    for (Map.Entry<String, Value> e : value) {
      r.set(e.getKey(), initFromObject(e.getValue()));
    }
    return r;
  }

  public static JsonValue initFrom(Value value) {
    if (value instanceof JsonValue) {
      return ((JsonValue) value).shallowCopy();
    }
    return initFromObject(value.getObject());
  }

  @SuppressWarnings({ "unchecked" })
  public static JsonValue initFromObject(Object value) {
    if (value instanceof JsonValue) {
      return (JsonValue) value;
    }

    // If the passed value is null the convert it to NULL type
    if (value == null) {
      return JsonValueBuilder.initFromNull();
    }

    /* based on the type of object initialize the value */
    if (value instanceof Byte) {
      return initFrom(((Byte) value).byteValue());
    }

    if (value instanceof Boolean) {
      return initFrom(((Boolean) value).booleanValue());
    }

    if (value instanceof String) {
      return initFrom((String) value);
    }

    if (value instanceof Short) {
      return initFrom(((Short) value).shortValue());
    }

    if (value instanceof Integer) {
      return initFrom(((Integer) value).intValue());
    }

    if (value instanceof Long) {
      return initFrom(((Long) value).longValue());
    }

    if (value instanceof Float) {
      return initFrom(((Float) value).floatValue());
    }

    if (value instanceof Double) {
      return initFrom(((Double) value).doubleValue());
    }

    if (value instanceof OTime) {
      return initFrom(((OTime) value));
    }

    if (value instanceof ODate) {
      return initFrom(((ODate) value));
    }

    if (value instanceof OTimestamp) {
      return initFrom(((OTimestamp) value));
    }

    if (value instanceof BigDecimal) {
      return initFrom(((BigDecimal) value));
    }

    if (value instanceof ByteBuffer) {
      return initFrom(((ByteBuffer) value));
    }

    if (value instanceof OInterval) {
      return initFrom(((OInterval) value));
    }

    if (value instanceof Document) {
      return initFrom(((Document) value));
    }

    if (value instanceof Map) {
      return initFrom((Map<String, Object>) value);
    }

    if (value instanceof List<?>) {
      return initFrom((List<Object>) value);
    }

    // must be tried as last
    if (value instanceof Value) {
      return initFrom((Value) value);
    }

    // Its an array of primitive or object type
    if (value.getClass().isArray()) {
      return initFromArray(value);
    }

    throw new TypeException("Unsupported object type for value " + value);
  }

  public static JsonValue initFromArray(Object value) {
    Class<?> c = value.getClass().getComponentType();
    if (c.isPrimitive()) {
      if (c == byte.class) {
        byte[] v = (byte[]) value;
        return initFromArray(v);
      }
      if (c == short.class) {
        short[] v = (short[]) value;
        return initFromArray(v);
      }
      if (c == int.class) {
        int[] v = (int[]) value;
        return initFromArray(v);
      }
      if (c == long.class) {
        long[] v = (long[]) value;
        return initFromArray(v);
      }
      if (c == float.class) {
        float[] v = (float[]) value;
        return initFromArray(v);
      }
      if (c == double.class) {
        double[] v = (double[]) value;
        return initFromArray(v);
      }
      if (c == boolean.class) {
        boolean[] v = (boolean[]) value;
        return initFromArray(v);
      }
      if (c == char.class) {
        char[] v = (char[]) value;
        return initFromArray(v);
      }
    } else {
      return initFromArray((Object [])value);
    }
    return null;
  }

  private static JsonValue initFromArray(byte[] values) {
    JsonList list = new JsonList();
    for (int i = 0; i < values.length; ++i) {
      list.addToList(JsonValueBuilder.initFrom(values[i]));
    }
    return list;
  }

  private static JsonValue initFromArray(short[] values) {
    JsonList list = new JsonList();
    for (int i = 0; i < values.length; ++i) {
      list.addToList(JsonValueBuilder.initFrom(values[i]));
    }
    return list;
  }

  private static JsonValue initFromArray(int[] values) {
    JsonList list = new JsonList();
    for (int i = 0; i < values.length; ++i) {
      list.addToList(JsonValueBuilder.initFrom(values[i]));
    }
    return list;
  }

  private static JsonValue initFromArray(long[] values) {
    JsonList list = new JsonList();
    for (int i = 0; i < values.length; ++i) {
      list.addToList(JsonValueBuilder.initFrom(values[i]));
    }
    return list;
  }

  private static JsonValue initFromArray(float[] values) {
    JsonList list = new JsonList();
    for (int i = 0; i < values.length; ++i) {
      list.addToList(JsonValueBuilder.initFrom(values[i]));
    }
    return list;
  }

  private static JsonValue initFromArray(double[] values) {
    JsonList list = new JsonList();
    for (int i = 0; i < values.length; ++i) {
      list.addToList(JsonValueBuilder.initFrom(values[i]));
    }
    return list;
  }

  private static JsonValue initFromArray(boolean[] values) {
    JsonList list = new JsonList();
    for (int i = 0; i < values.length; ++i) {
      list.addToList(JsonValueBuilder.initFrom(values[i]));
    }
    return list;
  }

  private static JsonValue initFromArray(char[] values) {
    JsonList list = new JsonList();
    for (int i = 0; i < values.length; ++i) {
      list.addToList(JsonValueBuilder.initFrom((short)values[i]));
    }
    return list;
  }

  private static JsonValue initFromArray(Object[] values) {
    JsonList list = new JsonList();
    for (int i = 0; i < values.length; ++i) {
      list.addToList(JsonValueBuilder.initFromObject(values[i]));
    }
    return list;
  }

  @Override
  public Value newNullValue() {
    return initFromNull();
  }

  @Override
  public Value newValue(boolean value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(String value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(byte value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(short value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(int value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(long value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(float value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(double value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(OTime value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(ODate value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(BigDecimal value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(OTimestamp value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(OInterval value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(ByteBuffer value) {
    return initFrom(value);
  }

  @Override
  public Value newValue(byte[] value) {
    return initFrom(ByteBuffer.wrap(value));
  }

  @Override
  public Value newValue(List<? extends Object> list) {
    return initFrom(list);
  }

  @Override
  public Value newValue(Map<String, ? extends Object> map) {
    return initFrom(map);
  }

}