import { IAudioItem } from './interfaces';
import cplayer from './';
import returntypeof from './helper/returntypeof';
import { EventEmitter } from 'events';
import parseHTML from "./helper/parseHTML";
import { ILyricItem } from "./lyric";

const defaultPoster = require('../defaultposter.jpg')
const htmlTemplate = require('../cplayer.html');
const playIcon = require('../playicon.svg');
const style = require('!css-loader!postcss-loader!sass-loader!../scss/cplayer.scss');

function kanaFilter(str: string) {
  const starttag = '<span class="cp-lyric-text-zoomout">';
  const endtag = '</span>';
  let res = '';
  let startflag = false;
  for(let i = 0; i < str.length; i++) {
    let ch = str.charAt(i);
    let kano = /[ぁ-んァ-ン]/.test(ch);
    if (kano && !startflag) {
      res += starttag;
      startflag = true;
    }
    if (!kano && startflag) {
      res += endtag;
      startflag = false;
    }
    res += ch;
  }
  if (startflag) {
    res += endtag;
  }
  return res;
}

function buildLyric(lyric: string, sublyric?: string, zoomOutKana: boolean = false) {
  return (zoomOutKana ? kanaFilter(lyric) : lyric) + (sublyric ? `<span class="cp-lyric-text-sub">${sublyric}</span>` : '')
}

function secondNumber2TimeStr(secondTime: number) {
  const minute: string = parseInt((secondTime / 60).toString()).toString().padStart(2, '0');
  const second: string = parseInt((secondTime % 60).toString()).toString().padStart(2, '0');
  return minute + ':' + second;
}

export interface ICplayerViewOption {
  element?: Element;
  generateBeforeElement?: boolean;
  deleteElementAfterGenerate?: boolean;
  zoomOutKana?: boolean;
  showPlaylist?: boolean;
  showPlaylistButton?: boolean;
  width?: string;
  size?: string;
  style?: string;
  dark?: boolean;
  big?: boolean;
  shadowDom?: boolean;
  dropDownMenuMode?: 'bottom' | 'top' | 'none' | string;
}

const defaultOption: ICplayerViewOption = {
  element: document.body,
  generateBeforeElement: false,
  deleteElementAfterGenerate: false,
  zoomOutKana: false,
  showPlaylist: false,
  showPlaylistButton: true,
  dropDownMenuMode: 'bottom',
  width: '',
  size: '12px',
  style: '',
  shadowDom: true
}


function createStyleElement(style: string) {
  const styleElement = document.createElement('style');
  styleElement.id = 'cplayer-style';
  styleElement.innerHTML = style;
  return styleElement;
}

function createShadowElement(targetElement: Element, htmlTemplate: string, style: string) {
  let shadowRoot = (targetElement as any).createShadowRoot() as ShadowRoot;
  shadowRoot.innerHTML = htmlTemplate;
  shadowRoot.appendChild(createStyleElement(style));
  return shadowRoot.firstChild as HTMLElement;
}

function createBeforeElement(targetElement: Element, htmlTemplate: string, style: string) {
  let element = document.createElement('div');
  element.innerHTML = htmlTemplate;
  targetElement.parentNode.insertBefore(element, targetElement);
  if (!document.getElementById('cplayer-style')) {
    document.body.appendChild(createStyleElement(style));
  }
  return element.firstChild as HTMLElement;
}

function createBeforeShadowElement(targetElement: Element, htmlTemplate: string, style: string) {
  let element = document.createElement('div');
  let shadowRoot = (element as any).createShadowRoot() as ShadowRoot;
  shadowRoot.innerHTML = htmlTemplate;
  shadowRoot.appendChild(createStyleElement(style));
  targetElement.parentNode.insertBefore(element, targetElement);
  return shadowRoot.firstChild as HTMLElement;
}

function createElement(targetElement: Element, htmlTemplate: string, style: string) {
  targetElement.innerHTML = htmlTemplate;
  if (!document.getElementById('cplayer-style')) {
    document.body.appendChild(createStyleElement(style));
  }
  return targetElement.firstChild as HTMLElement;
}

export default class cplayerView extends EventEmitter {
  private elementLinks = returntypeof(this.getElementLinks);
  private rootElement: HTMLElement;
  private player: cplayer;
  private dropDownMenuShowInfo = true;
  private options: ICplayerViewOption;

  constructor(player: cplayer, options: ICplayerViewOption) {
    super();
    this.options = {
      ...defaultOption,
      ...options
    };
    this.player = player;
    if (this.options.generateBeforeElement) {
      if ((this.options.element as any).createShadowRoot && options.shadowDom !== false) {
        this.rootElement = createBeforeShadowElement(this.options.element, htmlTemplate, style + this.options.style);
      } else {
        this.rootElement = createBeforeElement(this.options.element, htmlTemplate, style + this.options.style);
      }
    } else {
      if ((this.options.element as any).createShadowRoot && options.shadowDom !== false) {
        this.rootElement = createShadowElement(this.options.element, htmlTemplate, style + this.options.style);
      } else {
        this.rootElement = createElement(this.options.element, htmlTemplate, style + this.options.style);
      }
    }
    if (options.deleteElementAfterGenerate) {
      options.element.parentElement.removeChild(options.element);
    }
    this.rootElement.style.width = this.options.width;
    this.rootElement.style.fontSize = this.options.size;
    this.elementLinks = this.getElementLinks();
    this.injectEventListener();
    this.setPlayIcon(this.player.paused);
    this.dropDownMenuShowInfo = !this.options.showPlaylist;
    if (this.dropDownMenuShowInfo) {
      this.showInfo();
    } else this.showPlaylist();
    if (!this.options.showPlaylistButton)
      this.elementLinks.button.list.style.display = 'none';
    else
      this.elementLinks.button.list.style.display = '';
    this.elementLinks.dropDownMenu.classList.add('cp-drop-down-menu-' + this.options.dropDownMenuMode)
    if (this.options.dark) {
      this.dark();
    }
    if (this.options.big) {
      this.big();
    }

    // this.setPoster(this.player.nowplay.poster || defaultPoster);
    this.setProgress(this.player.currentTime / this.player.duration,
      this.player.currentTime,
      this.player.duration);
    // this.elementLinks.title.innerText = this.player.nowplay.name;
    // this.elementLinks.artist.innerText = this.player.nowplay.artist || '';
    this.updateLyric();
    this.updatePlaylist();
  }

  public getRootElement() {
    return this.rootElement;
  }

  public dark() {
    this.rootElement.classList.add('cp-dark');
  }

  public big() {
    this.rootElement.classList.add('cp-big');
  }

  private getPlayListLinks(rootElement: Element = this.rootElement) {
    return rootElement.querySelectorAll('.cp-playlist li');
  }

  private getElementLinks(rootElement: Element = this.rootElement) {
    let gebc: (className: string) => Element = className => rootElement.getElementsByClassName(className)[0];
    return {
      icon: {
        play: gebc('cp-play-icon') as HTMLElement,
        mode: gebc('cp-mode-icon') as HTMLElement,
      },
      button: {
        prev: gebc('cp-prev-button') as HTMLElement,
        play: gebc('cp-play-button') as HTMLElement,
        next: gebc('cp-next-button') as HTMLElement,
        volume: gebc('cp-volume-icon') as HTMLElement,
        list: gebc('cp-list-button') as HTMLElement,
        mode: gebc('cp-mode-button') as HTMLElement
      },
      progress: gebc('cp-progress') as HTMLElement,
      progressFill: gebc('cp-progress-fill') as HTMLElement,
      progressButton: gebc('cp-progress-button') as HTMLElement,
      progressDuration: gebc('cp-progress-duration') as HTMLElement,
      progressCurrentTime: gebc('cp-progress-current-time') as HTMLElement,
      poster: gebc('cp-poster') as HTMLElement,
      title: gebc('cp-audio-title') as HTMLElement,
      artist: gebc('cp-audio-artist') as HTMLElement,
      lyric: gebc('cp-lyric-text') as HTMLElement,
      lyricContainer: gebc('cp-lyric') as HTMLElement,
      volumeController: gebc('cp-volume-controller') as HTMLElement,
      volumeFill: gebc('cp-volume-fill') as HTMLElement,
      volumeControllerButton: gebc('cp-volume-controller-button') as HTMLElement,
      volumeControllerContainer: gebc('cp-volume-container') as HTMLElement,
      dropDownMenu: gebc('cp-drop-down-menu') as HTMLElement,
      playlist: gebc('cp-playlist') as HTMLElement,
      playlistItems: this.getPlayListLinks(rootElement)
    }
  }

  private setPlayIcon(paused: boolean) {
    if (paused) {
      this.elementLinks.icon.play.classList.add('cp-play-icon-paused');
    } else {
      this.elementLinks.icon.play.classList.remove('cp-play-icon-paused');
    }
  }

  private setProgress(point: number, currentTime: number, duration: number) {
    this.elementLinks.progressFill.style.width = `${point * 100}%`;
    this.elementLinks.progressButton.style.right = (1 - point) * 100 + '%';
    this.elementLinks.progressCurrentTime.innerText = secondNumber2TimeStr(currentTime);
    this.elementLinks.progressDuration.innerText = secondNumber2TimeStr(duration);
  }

  private setPoster(src: string) {
    this.elementLinks.poster.style.backgroundImage = `url("${src}")`;
  }

  private __OldVolume = 1;
  private setVolume(volume: number) {
    if (this.__OldVolume !== volume) {
      this.elementLinks.volumeFill.style.width = `${volume * 100}%`;
      this.elementLinks.volumeControllerButton.style.right = (1 - volume) * 100 + '%';
      this.__OldVolume = volume
    }
  }

  private setMode(mode: string) {
    var modeattr = document.createAttribute('data-mode');
    modeattr.value = mode;
    this.elementLinks.button.mode.attributes.setNamedItem(modeattr);
  }

  public showInfo() {
    let dropDownMenu = this.elementLinks.dropDownMenu;
    dropDownMenu.style.height = '';
    dropDownMenu.classList.remove('cp-drop-down-menu-playlist');
    dropDownMenu.classList.add('cp-drop-down-menu-info');
    this.dropDownMenuShowInfo = true;
  }

  public showPlaylist() {
    let dropDownMenu = this.elementLinks.dropDownMenu;
    dropDownMenu.style.height = this.player.playlist.length * 2.08333 + 'em';
    dropDownMenu.classList.remove('cp-drop-down-menu-info');
    dropDownMenu.classList.add('cp-drop-down-menu-playlist');
    this.dropDownMenuShowInfo = false;
  }

  public toggleDropDownMenu() {
    if (this.dropDownMenuShowInfo) {
      this.showPlaylist();
    } else {
      this.showInfo();
    }
  }

  private setVolumeControllerKeepShow() {
    this.elementLinks.volumeControllerContainer.classList.add('cp-volume-container-show');
  }

  private toggleVolumeControllerKeepShow() {
    this.elementLinks.volumeControllerContainer.classList.toggle('cp-volume-container-show');
  }

  private removeVolumeControllerKeepShow() {
    this.elementLinks.volumeControllerContainer.classList.remove('cp-volume-container-show');
  }

  private __OldLyric = '';
  private __OldTotalTime = 0;

  private setLyric(lyric: string, time: number = 0, totalTime: number = 0) {
    if (this.__OldLyric !== lyric || this.__OldTotalTime !== totalTime) {
      this.elementLinks.lyric.innerHTML = lyric;
      this.elementLinks.lyric.style.transition = '';
      this.elementLinks.lyric.style.transform = '';
      if (totalTime !== 0) {
        let lyricWidth = this.elementLinks.lyric.clientWidth;
        let lyricContainerWidth = this.elementLinks.lyricContainer.clientWidth;
        if (lyricWidth > lyricContainerWidth) {
          let duration = totalTime - time;
          let targetOffset = (lyricWidth - lyricContainerWidth);
          let timepage = lyricContainerWidth / lyricWidth * duration;
          let startTime = Math.min(timepage * 0.6, duration);
          let moveTime = duration - timepage;

          this.elementLinks.lyric.style.transition = `transform ${moveTime}ms linear ${startTime}ms`
          this.elementLinks.lyric.style.transform = `translateX(-${targetOffset}px)`;
        }
      }
      this.__OldLyric = lyric;
      this.__OldTotalTime = totalTime;
    }
  }

  private updatePlaylist() {
    var lis = this.player.playlist.map((audio, index) => {
      var element = document.createElement('li');
      element.innerHTML = `
        ${index === this.player.nowplaypoint ? playIcon : '<span class="cp-play-icon"></span>'}
        <span>${audio.name}</span><span class='cp-playlist-artist'>${audio.artist ? ' - ' + audio.artist : ''}</span>
      `
      return element;
    })
    this.elementLinks.playlist.innerHTML = '';
    lis.forEach((li) => {
      this.elementLinks.playlist.appendChild(li);
    })
    this.elementLinks.playlistItems = this.getPlayListLinks();
    this.injectPlayListEventListener();
    if (!this.dropDownMenuShowInfo) {
      this.elementLinks.dropDownMenu.style.height = this.player.playlist.length * 2.08333 + 'em';
    }
  }

  private injectPlayListEventListener() {
    Array.prototype.forEach.call(this.elementLinks.playlistItems,((i: Element, index: number) => {
      i.addEventListener('click', (event) => {
        this.handleClickPlayList(index, event);
      })
    }))
  }

  private injectEventListener() {
    this.elementLinks.button.play.addEventListener('click', this.handleClickPlayButton);
    this.elementLinks.button.prev.addEventListener('click', this.handleClickPrevButton);
    this.elementLinks.button.next.addEventListener('click', this.handleClickNextButton);
    this.elementLinks.button.volume.addEventListener('click', this.handleClickVolumeButton);
    this.elementLinks.button.list.addEventListener('click', this.handleClickListButton);
    this.elementLinks.button.mode.addEventListener('click', this.handleClickModeButton);
    this.elementLinks.volumeController.addEventListener('mousemove', this.handleMouseVolumeController)
    this.elementLinks.volumeController.addEventListener('mousedown', this.handleMouseVolumeController)
    this.elementLinks.volumeController.addEventListener('touchmove', this.handleTouchVolumeController, {passive: true} as any)

    this.elementLinks.progress.addEventListener('mousemove', this.handleMouseProgress)
    this.elementLinks.progress.addEventListener('mousedown', this.handleMouseProgress)
    this.elementLinks.progress.addEventListener('touchmove', this.handleTouchProgress, {passive: true} as any)

    this.player.addListener('playstatechange', this.handlePlayStateChange);
    this.player.addListener('timeupdate', this.handleTimeUpdate);
    this.player.addListener('openaudio', this.handleOpenAudio);
    this.player.addListener('volumechange', this.handleVolumeChange);
    this.player.addListener('playmodechange', this.handleModeChange);
    this.player.addListener('playlistchange', this.handlePlaylistchange);
    this.player.addListener('audioelementchange', this.handleAudioElementChange);
    this.injectPlayListEventListener();
  }

  private handlePlaylistchange = () => {
    this.updatePlaylist()
  }

  private updateLyric(playedTime: number = 0) {
    if (!this.player.nowplay) {
      this.setLyric(null);
      return;
    }

    if (this.player.nowplay.lyric && typeof this.player.nowplay.lyric !== 'string' && this.player.played) {
      let lyric = this.player.nowplay.lyric.getLyric(playedTime * 1000);
      let nextLyric = this.player.nowplay.lyric.getNextLyric(playedTime * 1000);
      if (lyric) {
        let sublyric: ILyricItem;
        if (this.player.nowplay.sublyric && typeof this.player.nowplay.sublyric !== 'string') {
          sublyric = this.player.nowplay.sublyric.getLyric(playedTime * 1000);
        }
        if (nextLyric) {
          let duration = nextLyric.time - lyric.time;
          let currentTime = playedTime * 1000 - lyric.time;
          this.setLyric(buildLyric(lyric.word, sublyric ? sublyric.word : undefined, this.options.zoomOutKana), currentTime, duration);
        } else {
          let duration = this.player.duration - lyric.time;
          let currentTime = playedTime * 1000 - lyric.time;
          this.setLyric(buildLyric(lyric.word, sublyric ? sublyric.word : undefined, this.options.zoomOutKana), currentTime, duration);
        }
      } else {
        this.setLyric(buildLyric(this.player.nowplay.name, this.player.nowplay.artist, false), playedTime * 1000, nextLyric.time);
      }
    } else {
      this.setLyric(buildLyric(this.player.nowplay.name, this.player.nowplay.artist, false));
    }
  }

  private handleClickListButton = () => {
    this.toggleDropDownMenu();
  }

  private handleClickModeButton = () => {
    this.player.toggleMode();
  }

  private handleClickPlayList = (point: number, event: Event) => {
    if (this.player.nowplaypoint !== point){
      this.player.to(point);
      this.player.play();
    }
  }

  private handleClickPlayButton = () => {
    this.player.togglePlayState();
  }

  private handleClickVolumeButton = () => {
    this.toggleVolumeControllerKeepShow();
  }

  private handleOpenAudio = (audio: IAudioItem) => {
    if (audio.type !== 'video') {
      this.setPoster(audio.poster || defaultPoster);
    } else {
      this.setPoster('none');
    }
    this.setProgress(0,0,0);
    this.elementLinks.title.innerText = audio.name;
    this.elementLinks.artist.innerText = audio.artist || '';
    this.updateLyric();
    this.updatePlaylist();
  }

  private handleModeChange = (mode: string) => {
    this.setMode(mode);
  }

  private handleVolumeChange = (volume: number) => {
    this.setVolume(volume);
  };

  private handleTimeUpdate = (playedTime: number, time: number) => {
    this.setProgress(playedTime / time, playedTime, time);
    this.updateLyric(playedTime);
  }

  private handleClickPrevButton = () => {
    this.player.prev();
    this.player.play();
  }

  private handleClickNextButton = () => {
    this.player.next();
    this.player.play();
  }

  private handlePlayStateChange = (paused: boolean) => {
    this.setPlayIcon(paused);
  }

  private handleMouseVolumeController = (event: MouseEvent) => {
    this.removeVolumeControllerKeepShow()
    if (event.buttons === 1 || typeof event.buttons === 'undefined' && event.which === 1) {
      let volume = Math.max(0, Math.min(1.0,
        (event.clientX - this.elementLinks.volumeController.getBoundingClientRect().left) / this.elementLinks.volumeController.clientWidth
      ));
      this.player.setVolume(volume);
      this.setVolume(volume);
    }
  };

  private handleTouchVolumeController = (event: TouchEvent) => {
    this.removeVolumeControllerKeepShow()
    let volume = Math.max(0, Math.min(1.0,
      (event.targetTouches[0].clientX - this.elementLinks.volumeController.getBoundingClientRect().left) / this.elementLinks.volumeController.clientWidth
    ));
    this.player.setVolume(volume);
    this.setVolume(volume);
  };

  private handleAudioElementChange = (element: HTMLAudioElement | HTMLVideoElement) => {
    if (element instanceof HTMLVideoElement) {
      this.elementLinks.poster.appendChild(element);
    }
    else {
      this.elementLinks.poster.innerHTML = '';
    }
  }

  private handleMouseProgress = (event: MouseEvent) => {
    if (event.buttons === 1 || typeof event.buttons === 'undefined' && event.which === 1) {
      let progress = Math.max(0, Math.min(1.0,
        (event.clientX - this.elementLinks.progress.getBoundingClientRect().left) / this.elementLinks.progress.clientWidth
      ));
      this.player.setCurrentTime(progress * 100 + '%');
    }
  };

  private handleTouchProgress = (event: TouchEvent) => {
    let progress = Math.max(0, Math.min(1.0,
      (event.targetTouches[0].clientX - this.elementLinks.progress.getBoundingClientRect().left) / this.elementLinks.progress.clientWidth
    ));
    this.player.setCurrentTime(progress * 100 + '%');
  }

  public destroy() {
    this.rootElement.parentElement.removeChild(this.rootElement);
  }
}
