// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import React from 'react';
import { classes } from 'typestyle/lib';

import { camelize } from '../utils';
import { IIconStyle, iconStyle, iconStyleFlat } from '../style/icon';

/**
 * To add an icon to the defaultIconRegistry requires two lines of code:
 *   1. import the icon's .svg
 *   2. add a relevant entry to _defaultIcons
 */

/* tslint:disable */
import jupyterFaviconSvg from '../../style/icons/jupyter-favicon.svg';

// filetype icons
import html5Svg from '../../style/icons/html5.svg';

// status bar icons
import kernelSvg from '../../style/icons/kernel.svg';
import lineFormSvg from '../../style/icons/line-form.svg';
import notTrustedSvg from '../../style/icons/not-trusted.svg';
import statusBarSvg from '../../style/icons/status-bar.svg';
import terminalSvg from '../../style/icons/terminal.svg';
import trustedSvg from '../../style/icons/trusted.svg';

// sidebar tab icons
import buildSvg from '../../style/tabicons/build.svg'; // originally ic-build-24px.svg
import extensionSvg from '../../style/tabicons/extension.svg'; // originally ic-extension-24px.svg
import folderSvg from '../../style/tabicons/folder.svg'; // originally ic-folder-24px.svg
import paletteSvg from '../../style/tabicons/palette.svg'; // originally ic-palette-24px.svg
import runningSvg from '../../style/tabicons/running.svg'; // originally stop-circle.svg
import tabSvg from '../../style/tabicons/tab.svg'; // originally ic-tab-24px.svg

const _defaultIcons: ReadonlyArray<IconRegistry.IModel> = [
  { name: 'jupyter-favicon', svg: jupyterFaviconSvg },

  { name: 'html5', svg: html5Svg },

  { name: 'kernel', svg: kernelSvg },
  { name: 'line-form', svg: lineFormSvg },
  { name: 'not-trusted', svg: notTrustedSvg },
  { name: 'status-bar', svg: statusBarSvg },
  { name: 'terminal', svg: terminalSvg },
  { name: 'trusted', svg: trustedSvg },

  { name: 'build', svg: buildSvg },
  { name: 'extension', svg: extensionSvg },
  { name: 'folder', svg: folderSvg },
  { name: 'palette', svg: paletteSvg },
  { name: 'running', svg: runningSvg },
  { name: 'tab', svg: tabSvg }
];
/* tslint:enable */

/**
 * The icon registry class.
 */
export class IconRegistry {
  constructor(...icons: IconRegistry.IModel[]) {
    if (icons.length) {
      this.addIcon(...icons);
    } else {
      this.addIcon(..._defaultIcons);
    }
  }

  addIcon(...icons: IconRegistry.IModel[]): void {
    icons.forEach((icon: IconRegistry.IModel) => {
      let className = icon.className
        ? icon.className
        : IconRegistry.iconClassName(icon.name);
      this._classNameToName[className] = icon.name;
      this._nameToClassName[icon.name] = className;
      this._svgs[icon.name] = icon.svg;
    });
  }

  get classNameToName(): { [key: string]: string } {
    return this._classNameToName;
  }

  get nameToClassName(): { [key: string]: string } {
    return this._nameToClassName;
  }

  /**
   * Get the icon as an HTMLElement of tag <svg><svg/>
   */
  icon(
    props: IconRegistry.IIconOptions & { container: HTMLElement } & IIconStyle
  ): HTMLElement {
    const { name, className, title, container, ...propsStyle } = props;
    let svgNode = Private.parseSvg(this.svg(name));

    if (title) {
      Private.setTitleSvg(svgNode, title);
    }

    if (container) {
      // clear any existing icon in container (and all other child elements)
      container.textContent = '';
      container.appendChild(svgNode);

      let styleClass = propsStyle ? iconStyle(propsStyle) : '';
      if (className) {
        // override the className, if explicitly passed
        container.className = classes(className, styleClass);
      } else if (!(styleClass in container.classList)) {
        // add icon styling class to the container's class, if not already present
        container.className = classes(container.className, styleClass);
      }
    } else {
      // add icon styling class directly to the svg node
      svgNode.setAttribute(
        'class',
        classes(className, propsStyle ? iconStyleFlat(propsStyle) : '')
      );
    }

    return svgNode;
  }

  /**
   * Get the icon as a ReactElement of tag <tag><svg><svg/><tag/>
   * TODO: figure out how to remove the unnecessary outer <tag>
   */
  iconReact(
    props: IconRegistry.IIconOptions & { tag?: 'div' | 'span' } & IIconStyle
  ): React.ReactElement {
    const { name, className, title, tag, ...propsStyle } = props;
    const Tag = tag || 'div';

    return (
      <Tag
        className={classes(className, propsStyle ? iconStyle(propsStyle) : '')}
        dangerouslySetInnerHTML={{ __html: this.svg(name) }}
      />
    );
  }

  override(
    props: { className: string; name?: string; title?: string } & IIconStyle
  ) {
    const { name, className, title, ...propsStyle } = props;

    let usedname = name;
    if (!usedname) {
      // for now, just assume that the first className is the relevant one
      usedname = className.split(/\s+/)[0];

      // bail if no corresponding icon is found
      if (!(usedname in this._classNameToName)) {
        return;
      }

      usedname = this._classNameToName[usedname];
    }

    for (let container of document.getElementsByClassName(
      className
    ) as HTMLCollectionOf<HTMLElement>) {
      this.icon({
        name: usedname,
        title: title,
        container: container,
        ...propsStyle
      });
    }
  }

  svg(name: string): string {
    if (!(name in this._svgs)) {
      console.error(`Invalid icon name: ${name}`);
    }

    return this._svgs[name];
  }

  static iconClassName(name: string): string {
    return 'jp-' + camelize(name, true) + 'Icon';
  }

  private _classNameToName: { [key: string]: string } = Object.create(null);
  private _nameToClassName: { [key: string]: string } = Object.create(null);
  private _svgs: { [key: string]: string } = Object.create(null);
}

/**
 * The defaultIconRegistry instance.
 */
export const defaultIconRegistry: IconRegistry = new IconRegistry();

/**
 * Alias for defaultIconRegistry.iconReact that can be used as a React component
 */
export const IconReact = (
  props: IconRegistry.IIconOptions & { tag?: 'div' | 'span' } & IIconStyle
): React.ReactElement => {
  return defaultIconRegistry.iconReact(props);
};

export namespace IconRegistry {
  export interface IModel {
    name: string;
    className?: string;
    svg: string;
  }

  export interface IIconOptions {
    name: string;
    className?: string;
    title?: string;
  }

  export const defaultIcons = _defaultIcons;
}

namespace Private {
  export function parseSvg(svg: string): HTMLElement {
    let parser = new DOMParser();
    return parser.parseFromString(svg, 'image/svg+xml').documentElement;
  }

  export function setTitleSvg(svgNode: HTMLElement, title: string): void {
    // add a title node to the top level svg node
    let titleNodes = svgNode.getElementsByTagName('title');
    if (titleNodes) {
      titleNodes[0].textContent = title;
    } else {
      let titleNode = document.createElement('title');
      titleNode.textContent = title;
      svgNode.appendChild(titleNode);
    }
  }
}
