import isUndefined from 'tui-code-snippet/type/isUndefined';
import forEach from 'tui-code-snippet/collection/forEach';
import sendHostname from 'tui-code-snippet/request/sendHostname';
import extend from 'tui-code-snippet/object/extend';
import isString from 'tui-code-snippet/type/isString';
import pick from 'tui-code-snippet/object/pick';
import inArray from 'tui-code-snippet/array/inArray';
import {
  commandNames,
  filterType,
  historyNames,
  SHAPE_FILL_TYPE,
  SHAPE_TYPE,
  emptyCropRectValues,
} from '@/consts';

const FLOATING_POINT_DIGIT = 2;
const CSS_PREFIX = 'tui-image-editor-';
const { min, max } = Math;
let hostnameSent = false;
let lastId = 0;

export function stamp(obj) {
  if (!obj.__fe_id) {
    lastId += 1;
    // eslint-disable-next-line camelcase
    obj.__fe_id = lastId;
  }

  return obj.__fe_id;
}

export function hasStamp(obj) {
  return !isNil(obj?.__fe_id);
}

export function isNil(value) {
  return isUndefined(value) || value === null;
}

export function isFunction(value) {
  return typeof value === 'function';
}

/**
 * Clamp value
 * @param {number} value - Value
 * @param {number} minValue - Minimum value
 * @param {number} maxValue - Maximum value
 * @returns {number} clamped value
 */
export function clamp(value, minValue, maxValue) {
  if (minValue > maxValue) {
    [minValue, maxValue] = [maxValue, minValue];
  }

  return max(minValue, min(value, maxValue));
}

/**
 * Make key-value object from arguments
 * @returns {object.<string, string>}
 */
export function keyMirror(...args) {
  const obj = {};

  forEach(args, (key) => {
    obj[key] = key;
  });

  return obj;
}

/**
 * Make CSSText
 * @param {Object} styleObj - Style info object
 * @returns {string} Connected string of style
 */
export function makeStyleText(styleObj) {
  let styleStr = '';

  forEach(styleObj, (value, prop) => {
    styleStr += `${prop}: ${value};`;
  });

  return styleStr;
}

/**
 * Get object's properties
 * @param {Object} obj - object
 * @param {Array} keys - keys
 * @returns {Object} properties object
 */
export function getProperties(obj, keys) {
  const props = {};
  const { length } = keys;
  let i = 0;
  let key;

  for (i = 0; i < length; i += 1) {
    key = keys[i];
    props[key] = obj[key];
  }

  return props;
}

/**
 * ParseInt simpliment
 * @param {number} value - Value
 * @returns {number}
 */
export function toInteger(value) {
  return parseInt(value, 10);
}

/**
 * String to camelcase string
 * @param {string} targetString - change target
 * @returns {string}
 * @private
 */
export function toCamelCase(targetString) {
  return targetString.replace(/-([a-z])/g, ($0, $1) => $1.toUpperCase());
}

/**
 * Check browser file api support
 * @returns {boolean}
 * @private
 */
export function isSupportFileApi() {
  return !!(window.File && window.FileList && window.FileReader);
}

/**
 * hex to rgb
 * @param {string} color - hex color
 * @param {string} alpha - color alpha value
 * @returns {string} rgb expression
 */
export function getRgb(color, alpha) {
  if (color.length === 4) {
    color = `${color}${color.slice(1, 4)}`;
  }
  const r = parseInt(color.slice(1, 3), 16);
  const g = parseInt(color.slice(3, 5), 16);
  const b = parseInt(color.slice(5, 7), 16);
  const a = alpha || 1;

  return `rgba(${r}, ${g}, ${b}, ${a})`;
}

/**
 * send hostname
 */
export function sendHostName() {
  if (hostnameSent) {
    return;
  }
  hostnameSent = true;

  sendHostname('image-editor', 'UA-129999381-1');
}

/**
 * Apply css resource
 * @param {string} styleBuffer - serialized css text
 * @param {string} tagId - style tag id
 */
export function styleLoad(styleBuffer, tagId) {
  const [head] = document.getElementsByTagName('head');
  const linkElement = document.createElement('link');
  const styleData = encodeURIComponent(styleBuffer);
  if (tagId) {
    linkElement.id = tagId;
    // linkElement.id = 'tui-image-editor-theme-style';
  }
  linkElement.setAttribute('rel', 'stylesheet');
  linkElement.setAttribute('type', 'text/css');
  linkElement.setAttribute('href', `data:text/css;charset=UTF-8,${styleData}`);
  head.appendChild(linkElement);
}

/**
 * Get selector
 * @param {HTMLElement} targetElement - target element
 * @returns {Function} selector
 */
export function getSelector(targetElement) {
  return (str) => targetElement.querySelector(str);
}

/**
 * Change base64 to blob
 * @param {String} data - base64 string data
 * @returns {Blob} Blob Data
 */
export function base64ToBlob(data) {
  const rImageType = /data:(image\/.+);base64,/;
  let mimeString = '';
  let raw, uInt8Array, i;

  raw = data.replace(rImageType, (header, imageType) => {
    mimeString = imageType;

    return '';
  });

  raw = atob(raw);
  const rawLength = raw.length;
  uInt8Array = new Uint8Array(rawLength); // eslint-disable-line

  for (i = 0; i < rawLength; i += 1) {
    uInt8Array[i] = raw.charCodeAt(i);
  }

  return new Blob([uInt8Array], { type: mimeString });
}

/**
 * Fix floating point diff.
 * @param {number} value - original value
 * @returns {number} fixed value
 */
export function fixFloatingPoint(value) {
  return Number(value.toFixed(FLOATING_POINT_DIGIT));
}

/**
 * Assignment for destroying objects.
 * @param {Object} targetObject - object to be removed.
 */
export function assignmentForDestroy(targetObject) {
  forEach(targetObject, (value, key) => {
    targetObject[key] = null;
  });
}

/**
 * Make class name for ui
 * @param {String} str  - main string of className
 * @param {String} prefix - prefix string of className
 * @returns {String} class name
 */
export function cls(str = '', prefix = '') {
  if (str.charAt(0) === '.') {
    return `.${CSS_PREFIX}${prefix}${str.slice(1)}`;
  }

  return `${CSS_PREFIX}${prefix}${str}`;
}

/**
 * Change object origin
 * @param {fabric.Object} fObject - fabric object
 * @param {Object} origin - origin of fabric object
 *   @param {string} originX - horizontal basis.
 *   @param {string} originY - vertical basis.
 */
export function changeOrigin(fObject, origin) {
  const { originX, originY } = origin;
  const { x: left, y: top } = fObject.getPointByOrigin(originX, originY);

  fObject.set({
    left,
    top,
    originX,
    originY,
  });

  fObject.setCoords();
}

/**
 * Object key value flip
 * @param {Object} targetObject - The data object of the key value.
 * @returns {Object}
 */
export function flipObject(targetObject) {
  const result = {};

  Object.keys(targetObject).forEach((key) => {
    result[targetObject[key]] = key;
  });

  return result;
}

/**
 * Set custom properties
 * @param {Object} targetObject - target object
 * @param {Object} props - custom props object
 */
export function setCustomProperty(targetObject, props) {
  targetObject.customProps = targetObject.customProps || {};
  extend(targetObject.customProps, props);
}

/**
 * Get custom property
 * @param {fabric.Object} fObject - fabric object
 * @param {Array|string} propNames - prop name array
 * @returns {object | number | string}
 */
export function getCustomProperty(fObject, propNames) {
  const resultObject = {};
  if (isString(propNames)) {
    propNames = [propNames];
  }
  forEach(propNames, (propName) => {
    resultObject[propName] = fObject.customProps[propName];
  });

  return resultObject;
}

/**
 * Capitalize string
 * @param {string} targetString - target string
 * @returns {string}
 */
export function capitalizeString(targetString) {
  return targetString.charAt(0).toUpperCase() + targetString.slice(1);
}

/**
 * Array includes check
 * @param {Array} targetArray - target array
 * @param {string|number} compareValue - compare value
 * @returns {boolean}
 */
export function includes(targetArray, compareValue) {
  return targetArray.indexOf(compareValue) >= 0;
}

/**
 * Get fill type
 * @param {Object | string} fillOption - shape fill option
 * @returns {string} 'color' or 'filter'
 */
export function getFillTypeFromOption(fillOption = {}) {
  return pick(fillOption, 'type') || SHAPE_FILL_TYPE.COLOR;
}

/**
 * Get fill type of shape type object
 * @param {fabric.Object} shapeObj - fabric object
 * @returns {string} 'transparent' or 'color' or 'filter'
 */
export function getFillTypeFromObject(shapeObj) {
  const { fill = {} } = shapeObj;
  if (fill.source) {
    return SHAPE_FILL_TYPE.FILTER;
  }

  return SHAPE_FILL_TYPE.COLOR;
}

/**
 * Check if the object is a shape object.
 * @param {fabric.Object} obj - fabric object
 * @returns {boolean}
 */
export function isShape(obj) {
  return inArray(obj.get('type'), SHAPE_TYPE) >= 0;
}

/**
 * Get object type
 * @param {string} type - fabric object type
 * @returns {string} type of object (ex: shape, icon, ...)
 */
export function getObjectType(type) {
  if (includes(SHAPE_TYPE, type)) {
    return 'Shape';
  }

  switch (type) {
    case 'i-text':
      return 'Text';
    case 'path':
    case 'line':
      return 'Draw';
    case 'activeSelection':
      return 'Group';
    default:
      return toStartOfCapital(type);
  }
}

/**
 * Get filter type
 * @param {string} type - fabric filter type
 * @param {object} [options] - filter type options
 *   @param {boolean} [options.useAlpha=true] - usage of alpha(true is 'color filter', false is 'remove white')
 *   @param {string} [options.mode] - mode of blendColor
 * @returns {string} type of filter (ex: sepia, blur, ...)
 */
function getFilterType(type, { useAlpha = true, mode } = {}) {
  const { VINTAGE, REMOVE_COLOR, BLEND_COLOR, SEPIA2, COLOR_FILTER, REMOVE_WHITE, BLEND } =
    filterType;

  let filterName;

  switch (type) {
    case VINTAGE:
      filterName = SEPIA2;
      break;
    case REMOVE_COLOR:
      filterName = useAlpha ? COLOR_FILTER : REMOVE_WHITE;
      break;
    case BLEND_COLOR:
      filterName = mode === 'add' ? BLEND : mode;
      break;
    default:
      filterName = type;
  }

  return toStartOfCapital(filterName);
}

/**
 * Check if command is silent command
 * @param {Command|string} command - command or command name
 * @returns {boolean}
 */
export function isSilentCommand(command) {
  const { LOAD_IMAGE } = commandNames;

  return typeof command === 'string' ? LOAD_IMAGE === command : LOAD_IMAGE === command.name;
}

/**
 * Get command name
 * @param {Command|string} command - command or command name
 * @returns {{name: string, ?detail: string}}
 */
// eslint-disable-next-line complexity, require-jsdoc
export function getHistoryTitle(command) {
  const {
    FLIP_IMAGE,
    ROTATE_IMAGE,
    ADD_TEXT,
    APPLY_FILTER,
    REMOVE_FILTER,
    CHANGE_SHAPE,
    CHANGE_ICON_COLOR,
    CHANGE_TEXT_STYLE,
    CLEAR_OBJECTS,
    ADD_IMAGE_OBJECT,
    REMOVE_OBJECT,
    RESIZE_IMAGE,
  } = commandNames;
  const { name, args } = command;
  let historyInfo;

  switch (name) {
    case FLIP_IMAGE:
      historyInfo = { name, detail: args[1] === 'reset' ? args[1] : args[1].slice(4) };
      break;
    case ROTATE_IMAGE:
      historyInfo = { name, detail: args[2] };
      break;
    case APPLY_FILTER:
      historyInfo = { name: historyNames.APPLY_FILTER, detail: getFilterType(args[1], args[2]) };
      break;
    case REMOVE_FILTER:
      historyInfo = { name: historyNames.REMOVE_FILTER, detail: 'Remove' };
      break;
    case CHANGE_SHAPE:
      historyInfo = { name: historyNames.CHANGE_SHAPE, detail: 'Change' };
      break;
    case CHANGE_ICON_COLOR:
      historyInfo = { name: historyNames.CHANGE_ICON_COLOR, detail: 'Change' };
      break;
    case CHANGE_TEXT_STYLE:
      historyInfo = { name: historyNames.CHANGE_TEXT_STYLE, detail: 'Change' };
      break;
    case REMOVE_OBJECT:
      historyInfo = { name: historyNames.REMOVE_OBJECT, detail: args[2] };
      break;
    case CLEAR_OBJECTS:
      historyInfo = { name: historyNames.CLEAR_OBJECTS, detail: 'All' };
      break;
    case ADD_IMAGE_OBJECT:
      historyInfo = { name: historyNames.ADD_IMAGE_OBJECT, detail: 'Add' };
      break;
    case ADD_TEXT:
      historyInfo = { name: historyNames.ADD_TEXT };
      break;
    case RESIZE_IMAGE:
      historyInfo = { name: historyNames.RESIZE, detail: `${~~args[1].width}x${~~args[1].height}` };
      break;

    default:
      historyInfo = { name };
      break;
  }

  if (args[1] === 'mask') {
    historyInfo = { name: historyNames.LOAD_MASK_IMAGE, detail: 'Apply' };
  }

  return historyInfo;
}

/**
 * Get help menubar position(opposite of menubar)
 * @param {string} position - position of menubar
 * @returns {string} position of help menubar
 */
export function getHelpMenuBarPosition(position) {
  if (position === 'top') {
    return 'bottom';
  }
  if (position === 'left') {
    return 'right';
  }
  if (position === 'right') {
    return 'left';
  }

  return 'top';
}

/**
 * Change to capital start letter
 * @param {string} str - string to change
 * @returns {string}
 */
function toStartOfCapital(str) {
  return str.replace(/[a-z]/, (first) => first.toUpperCase());
}

/**
 * Check if cropRect is Empty.
 * @param {Object} cropRect - cropRect object
 *  @param {Number} cropRect.left - cropRect left position value
 *  @param {Number} cropRect.top - cropRect top position value
 *  @param {Number} cropRect.width - cropRect width value
 *  @param {Number} cropRect.height - cropRect height value
 * @returns {boolean}
 */
export function isEmptyCropzone(cropRect) {
  const { left, top, width, height } = cropRect;
  const { LEFT, TOP, WIDTH, HEIGHT } = emptyCropRectValues;

  return left === LEFT && top === TOP && width === WIDTH && height === HEIGHT;
}
