package io.github.jsonSnapshot;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class SnapshotCaptor {

  private Class<?> parameterClass;

  private Class<?> argumentClass;

  private String[] ignore;

  public SnapshotCaptor(Class<?> parameterClass, String... ignore) {
    this.parameterClass = parameterClass;
    this.argumentClass = parameterClass;
    this.ignore = ignore;
  }

  public SnapshotCaptor(Class<?> parameterClass, Class<?> argumentClass, String... ignore) {
    this.parameterClass = parameterClass;
    this.argumentClass = argumentClass;
    this.ignore = ignore;
  }

  public Class<?> getParameterClass() {
    return parameterClass;
  }

  public Object removeIgnored(Object value) {
    Object newValue = value;
    if (ignore != null && ignore.length > 0) {
      newValue = shallowCopy(value);
      for (String each : ignore) {
        try {
          Field field = this.argumentClass.getDeclaredField(each);
          field.setAccessible(true);
          if (field.getType().isPrimitive()) {
            field.setByte(newValue, Integer.valueOf(0).byteValue());
          } else {
            field.set(newValue, null);
          }
        } catch (IllegalAccessException | NoSuchFieldException e) {
          throw new SnapshotMatchException("Invalid Ignore value " + each, e.getCause());
        }
      }
    }
    return newValue;
  }

  private Object shallowCopy(Object value) {
    try {
      Object newValue = constructCopy(this.argumentClass);

      Field[] fields = this.argumentClass.getDeclaredFields();

      for (Field field : fields) {
        field.setAccessible(true);
        try {
          field.set(newValue, field.get(value));
        } catch (Exception e) {
          // ignore
        }
      }
      return newValue;
    } catch (Exception e) {
      throw new SnapshotMatchException(
          "Class "
              + this.argumentClass.getSimpleName()
              + " must have a default empty constructor!");
    }
  }

  private Object constructCopy(Class<?> argumentClass)
      throws InstantiationException, IllegalAccessException,
          java.lang.reflect.InvocationTargetException, NoSuchMethodException {

    try {
      return argumentClass.getDeclaredConstructor().newInstance();
    } catch (Exception e) {
      // Ignore - should log
    }

    Constructor[] constructors = argumentClass.getDeclaredConstructors();

    if (constructors.length == 0) {
      return argumentClass.getDeclaredConstructor().newInstance();
    }

    int i = 0;
    Class[] types = constructors[i].getParameterTypes();
    Object[] paramValues = new Object[types.length];

    for (int j = 0; j < types.length; j++) {
      if (types[j].isPrimitive()) {
        paramValues[j] = Integer.valueOf(0).byteValue();
      } else {
        paramValues[j] = constructCopy(types[j]);
      }
    }
    return constructors[i].newInstance(paramValues);
  }
}