function useDefaultValues(constructor) {
  constructor = window[constructor];

  constructor.default = (defaultValue) => {
    const _ = (value) => constructor.call(constructor, value || defaultValue);
    return _;
  };

  return constructor;
}

export const String = useDefaultValues('String');
export const Number = useDefaultValues('Number');
export const Boolean = useDefaultValues('Boolean');

export default class Model {
  constructor(model) {
    this.model = model;
  }

  format(obj) {
    const initialObj = obj.constructor();
    return this.go(initialObj, this.model, obj);
  }

  go(newObj, model, obj) {
    const isArray = Array.isArray(model);
    const iteratorObject = isArray ? obj : model;

    for (const propertyName in iteratorObject) {
      const index = isArray ? 0 : propertyName;
      const modelPropertyValue = model[index];
      const objPropertyValue = this.isDefined(obj) ? obj[propertyName] : null;

      const isObject = typeof modelPropertyValue === 'object';
      const constructor = isObject ? modelPropertyValue.constructor : modelPropertyValue;

      if (isObject && objPropertyValue !== null) {
        newObj[propertyName] = constructor();
        this.go(newObj[propertyName], modelPropertyValue, objPropertyValue);
      } else {
        if (this.isNull(objPropertyValue)) {
          newObj[propertyName] = constructor.name === '_' ? constructor() : null;
        } else {
          newObj[propertyName] = constructor(objPropertyValue);
        }
      }
    }

    return newObj;
  }

  isDefined(value) {
    return typeof value != 'undefined';
  }

  isNull(value) {
    return typeof value == 'undefined' || value === null;
  }
}