import { isObject } from './inspect';

// --- Static ---

const assign = function () {
  return Object.assign(...arguments);
};
const create = (proto, optionalProps) => Object.create(proto, optionalProps);
const defineProperties = (obj, props) => Object.defineProperties(obj, props);
const defineProperty = (obj, prop, descriptor) => Object.defineProperty(obj, prop, descriptor);
const freeze = obj => Object.freeze(obj);
const getOwnPropertyNames = obj => Object.getOwnPropertyNames(obj);
const getOwnPropertyDescriptor = (obj, prop) => Object.getOwnPropertyDescriptor(obj, prop);
const getOwnPropertySymbols = obj => Object.getOwnPropertySymbols(obj);
const getPrototypeOf = obj => Object.getPrototypeOf(obj);
const is = (value1, value2) => Object.is(value1, value2);
const isFrozen = obj => Object.isFrozen(obj);
const keys = obj => Object.keys(obj);

// --- "Instance" ---

const hasOwnProperty = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
const toString = obj => Object.prototype.toString.call(obj);

// --- Utilities ---

// Shallow copy an object
const clone = obj => ({
  ...obj
});

// Return a shallow copy of object with the specified properties only
// See: https://gist.github.com/bisubus/2da8af7e801ffd813fab7ac221aa7afc
const pick = (obj, props) => keys(obj).filter(key => props.indexOf(key) !== -1).reduce((result, key) => ({
  ...result,
  [key]: obj[key]
}), {});

// Return a shallow copy of object with the specified properties omitted
// See: https://gist.github.com/bisubus/2da8af7e801ffd813fab7ac221aa7afc
const omit = (obj, props) => keys(obj).filter(key => props.indexOf(key) === -1).reduce((result, key) => ({
  ...result,
  [key]: obj[key]
}), {});

// Merges two object deeply together
// See: https://gist.github.com/Salakar/1d7137de9cb8b704e48a
const mergeDeep = (target, source) => {
  if (isObject(target) && isObject(source)) {
    keys(source).forEach(key => {
      if (isObject(source[key])) {
        if (!target[key] || !isObject(target[key])) {
          target[key] = source[key];
        }
        mergeDeep(target[key], source[key]);
      } else {
        assign(target, {
          [key]: source[key]
        });
      }
    });
  }
  return target;
};

// Returns a shallow copy of the object with keys in sorted order
const sortKeys = obj => keys(obj).sort().reduce((result, key) => ({
  ...result,
  [key]: obj[key]
}), {});

// Convenience method to create a read-only descriptor
const readonlyDescriptor = () => ({
  enumerable: true,
  configurable: false,
  writable: false
});

export { assign, clone, create, defineProperties, defineProperty, freeze, getOwnPropertyDescriptor, getOwnPropertyNames, getOwnPropertySymbols, getPrototypeOf, hasOwnProperty, is, isFrozen, keys, mergeDeep, omit, pick, readonlyDescriptor, sortKeys, toString };
