import defaultTo from 'lodash/defaultTo';
import { IFormattingTool } from '../types';

export interface IRichTextFormat {
  type: string;
}

export interface IRichTextValue {
  text: string;
  formats: IRichTextFormat[][];
  start: number|null;
  end: number|null;
}

export const findToolForElement = (
  tools: IFormattingTool[],
  element: Element,
): IFormattingTool|null => tools.find(tool => {
  if (tool.tagName && element.tagName !== tool.tagName) {
    return false;
  }

  if (tool.className && !element.classList.contains(tool.className)) {
    return false;
  }

  return true;
}) || null;

export const createFromString = (
  value: string = '',
  formats: IRichTextFormat[] = [],
): IRichTextValue => ({
  text: value,
  formats: (new Array(value.length)).fill([...formats]),
  start: null,
  end: null,
});

const createFromDOMNodeRecursively = (
  tools: IFormattingTool[],
  node: ChildNode,
  formats: IRichTextFormat[] = [],
): IRichTextValue => {
  if (node.nodeType === node.TEXT_NODE) {
    return createFromString(node.textContent || '', formats);
  }

  if (node.nodeType === node.ELEMENT_NODE) {
    return createFromDOMElement(tools, node as Element, formats);
  }

  return createFromString('', []);
};

export const createFromDOMElement = (
  tools: IFormattingTool[],
  element: Element,
  formats: IRichTextFormat[] = [],
): IRichTextValue => {
  const tool = findToolForElement(tools, element);
  const subFormats = tool
    ? [
      ...formats.filter(f => f.type !== tool.name),
      { type: tool.name },
    ]
    : [ ...formats ];

  const nodes = Array.from(element.childNodes);
  return concat(...nodes.map(node => createFromDOMNodeRecursively(tools, node, subFormats)));
};

export const createFromDOMString = (
  tools: IFormattingTool[],
  value: string,
  formats: IRichTextFormat[] = [],
): IRichTextValue => {
  const div = document.createElement('div');
  div.innerHTML = value;
  return createFromDOMElement(tools, div, formats);
};

export const create = (
  tools: IFormattingTool[],
  value: Element|string = '',
): IRichTextValue => {
  if (typeof value === 'string') {
    return createFromDOMString(tools, value);
  }
  return createFromDOMElement(tools, value);
}

export const toHTMLString = (
  tools: IFormattingTool[],
  value: IRichTextValue,
) => {
  const tools = [...tools].sort((a, b) => {
    const tensionA = defaultTo(a.surfaceTension, Infinity);
    const tensionB = defaultTo(b.surfaceTension, Infinity);
    return tensionA - tensionB;
  });

  const elementTree = [];
  const string = '';
  for (let i = 0; i < value.text.length; i++) {
    const c = value.text[i];
    const formats = value.formats[i];
    const activeFormats = value.formats[i - 1] || [];

    const removedFormats = activeFormats
      .filter(a => !formats
        .find(f => f.type === a.type
            && JSON.stringify(f) === JSON.stringify(a)));
    const addedFormats = formats
      .filter(f => !activeFormats
        .find(a => a.type === f.type
            && JSON.stringify(a) === JSON.stringify(f)));

    console.log(c);
    for (let removedFormat of removedFormats) {
      const tool = tools.find(tool => tool.name === removedFormat.type);
      if (!tool) {
        continue;
      }

      tool.
    }
  }

  return string;
}

export const getActiveFormats = (richTextValue: IRichTextValue): IRichTextFormat[] =>
  richTextValue.start !== null
  ? richTextValue.formats[richTextValue.start]
  : [];

export const applyFormat = (
  value: IRichTextValue,
  format: IRichTextFormat,
  startIndex?: number,
  endIndex?: number,
): IRichTextValue => {
  const start = defaultTo(defaultTo(startIndex, value.start), 0);
  const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);

  return {
    ...value,
    formats: [
      ...value.formats.slice(0, start),
      ...value.formats.slice(start, end).map(letterFormatList => [
        ...letterFormatList.filter(letterFormat => letterFormat.type === format.type),
        format,
      ]),
      ...value.formats.slice(end),
    ],
  }
};

export const removeFormat = (
  value: IRichTextValue,
  format: IRichTextFormat,
  startIndex?: number,
  endIndex?: number,
): IRichTextValue => {
  const start = defaultTo(defaultTo(startIndex, value.start), 0);
  const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);

  return {
    ...value,
    formats: [
      ...value.formats.slice(0, start),
      ...value.formats.slice(start, end)
        .map(letterFormatList => letterFormatList.filter(letterFormat => letterFormat.type === format.type)),
      ...value.formats.slice(end),
    ],
  };
}

export const toggleFormat = (
  value: IRichTextValue,
  format: IRichTextFormat,
): IRichTextValue => {
  const activeFormats = getActiveFormats(value);
  if (activeFormats.find(f => f.type === format.type)) {
    return removeFormat(value, format);
  }

  return applyFormat(value, format);
}

export const concat = (...richTextValues:IRichTextValue[]): IRichTextValue => richTextValues
.reduce((newValue, value) => ({
  text: newValue.text + value.text,
  formats: [
    ...newValue.formats,
    ...value.formats,
  ],
  start: newValue.start !== null ? newValue.start : value.start,
  end: value.end !== null ? value.end : newValue.end,
}), { text: '', formats: [], start: null, end: null });

export const join = (
  richTextValues:IRichTextValue[],
  separator?: string|IRichTextValue,
): IRichTextValue => richTextValues
.reduce((total, value) => concat(
  total,
  ...(separator
    ? [typeof separator === 'string' ? createFromString(separator) : separator]
    : []),
  value,
), createFromString());

export const slice = (
  value: IRichTextValue,
  startIndex?: number,
  endIndex?: number,
): IRichTextValue => {
  const start = defaultTo(defaultTo(startIndex, value.start), 0);
  const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);

  return {
    text: value.text.slice(start, end),
    formats: value.formats.slice(start, end),
    start: value.start !== null ? (value.start >= start ? value.start - start : null) : null,
    end: value.end !== null ? (value.end <= end ? end - value.end : null) : null,
  };
};

export const insert = (
  value: IRichTextValue,
  valueToInsert: IRichTextValue|string,
  startIndex?: number,
  endIndex?: number,
) => {
  const start = defaultTo(defaultTo(startIndex, value.start), value.text.length);
  const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);

  return concat(
    slice(value, 0, start),
    typeof valueToInsert === 'string' ? createFromString(valueToInsert) : valueToInsert,
    slice(value, end, value.text.length),
  );
}

export const split = (
  value: IRichTextValue,
  valueToInsert: IRichTextValue|string,
  startIndex?: number,
  endIndex?: number,
) => {
  const start = defaultTo(defaultTo(startIndex, value.start), value.text.length);
  const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);

  return concat(
    slice(value, 0, start),
    typeof valueToInsert === 'string' ? createFromString(valueToInsert) : valueToInsert,
    slice(value, end, value.text.length),
  );
}
