import { Injectable } from '@angular/core';
import { HttpClient, HttpRequest } from '@angular/common/http';
import * as Utils from '../utils/ngx-editor.utils';

@Injectable()
export class CommandExecutorService {
  /** saves the selection from the editor when focussed out */
  savedSelection: any = undefined;

  /**
   *
   * @param _http HTTP Client for making http requests
   */
  constructor(private _http: HttpClient) {}

  /**
   * executes command from the toolbar
   *
   * @param command command to be executed
   */
  execute(command: string): void {
    if (!this.savedSelection && command !== 'enableObjectResizing') {
      throw new Error('Range out of Editor');
    }

    if (command === 'enableObjectResizing') {
      document.execCommand('enableObjectResizing', true);
    }

    if (command === 'blockquote') {
      document.execCommand('formatBlock', false, 'blockquote');
    }

    if (command === 'removeBlockquote') {
      document.execCommand('formatBlock', false, 'div');
    }

    document.execCommand(command, false, null);
  }

  /**
   * inserts pre code in the editor
   *
   * @param sourceCode sample source code entered
   * @param language language of the source code
   */
  insertPreCode(sourceCode: string, language: string): void {
    if (this.savedSelection) {
      if (sourceCode) {
        const restored = Utils.restoreSelection(this.savedSelection);
        if (restored) {
          this.insertHtml(`<pre class="line-numbers language-${language}"> <code>` + sourceCode + `</code> </pre>`);
        }
      }
    } else {
      throw new Error(`Range out of the editor`);
    }
  }

  /**
   * inserts image in the editor
   *
   * @param imageURI url of the image to be inserted
   */
  insertImage(imageURI: string): void {
    if (this.savedSelection) {
      if (imageURI) {
        const restored = Utils.restoreSelection(this.savedSelection);
        if (restored) {
          alert(imageURI);
          const inserted = document.execCommand('insertImage', false, imageURI);
          if (!inserted) {
            throw new Error('Invalid URL');
          }
        }
      }
    } else {
      throw new Error('Range out of the editor');
    }
  }

  /**
   * inserts image in the editor
   *
   * @param videParams url of the image to be inserted
   */
  insertVideo(videParams: any): void {
    if (this.savedSelection) {
      if (videParams) {
        const restored = Utils.restoreSelection(this.savedSelection);
        if (restored) {
          if (this.isYoutubeLink(videParams.videoUrl)) {
            const youtubeURL =
              '<iframe width="' +
              videParams.width +
              '" height="' +
              videParams.height +
              '"' +
              'src="' +
              videParams.videoUrl +
              '"></iframe>';
            this.insertHtml(youtubeURL);
          } else if (this.checkTagSupportInBrowser('video')) {
            if (this.isValidURL(videParams.videoUrl)) {
              const videoSrc =
                '<video width="' +
                videParams.width +
                '" height="' +
                videParams.height +
                '"' +
                ' controls="true"><source src="' +
                videParams.videoUrl +
                '"></video>';
              this.insertHtml(videoSrc);
            } else {
              throw new Error('Invalid video URL');
            }
          } else {
            throw new Error('Unable to insert video');
          }
        }
      }
    } else {
      throw new Error('Range out of the editor');
    }
  }

  /**
   * checks the input url is a valid youtube URL or not
   *
   * @param url Youtue URL
   */
  private isYoutubeLink(url: string): boolean {
    const ytRegExp = /^(http(s)?:\/\/)?((w){3}.)?youtu(be|.be)?(\.com)?\/.+/;
    return ytRegExp.test(url);
  }

  /**
   * check whether the string is a valid url or not
   * @param url url
   */
  private isValidURL(url: string) {
    const urlRegExp = /(http|https):\/\/(\w+:{0,1}\w*)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/;
    return urlRegExp.test(url);
  }

  /**
   * uploads image to the server
   *
   * @param file file that has to be uploaded
   * @param endPoint enpoint to which the image has to be uploaded
   */
  uploadImage(file: File, endPoint: string): any {

    if (!endPoint) {
      throw new Error('Image Endpoint isn`t provided or invalid');
    }

    const formData: FormData = new FormData();

    if (file) {
      formData.append('file', file);

      const req = new HttpRequest('POST', endPoint, formData, {
        reportProgress: true
      });

      return this._http.request(req);
    } else {
      throw new Error('Invalid Image');
    }
  }

  /**
   * inserts link in the editor
   *
   * @param params parameters that holds the information for the link
   */
  createLink(params: any): void {
    if (this.savedSelection) {
      /**
       * check whether the saved selection contains a range or plain selection
       */
      if (params.urlNewTab) {
        const newUrl =
          '<a href="' +
          params.urlLink +
          '" target="_blank">' +
          params.urlText +
          '</a>';

        if (document.getSelection().type !== 'Range') {
          const restored = Utils.restoreSelection(this.savedSelection);
          if (restored) {
            this.insertHtml(newUrl);
          }
        } else {
          throw new Error(
            'Only new links can be inserted. You cannot edit URL`s'
          );
        }
      } else {
        const restored = Utils.restoreSelection(this.savedSelection);
        if (restored) {
          document.execCommand('createLink', false, params.urlLink);
        }
      }
    } else {
      throw new Error('Range out of the editor');
    }
  }

  /**
   * insert color either font or background
   *
   * @param color color to be inserted
   * @param where where the color has to be inserted either text/background
   */
  insertColor(color: string, where: string): void {
    if (this.savedSelection) {
      const restored = Utils.restoreSelection(this.savedSelection);
      if (restored && this.checkSelection()) {
        if (where === 'textColor') {
          document.execCommand('foreColor', false, color);
        } else {
          document.execCommand('hiliteColor', false, color);
        }
      }
    } else {
      throw new Error('Range out of the editor');
    }
  }

  /**
   * set font size for text
   *
   * @param fontSize font-size to be set
   */
  setFontSize(fontSize: string): void {
    if (this.savedSelection && this.checkSelection()) {
      const deletedValue = this.deleteAndGetElement();

      if (deletedValue) {
        const restored = Utils.restoreSelection(this.savedSelection);

        if (restored) {
          if (this.isNumeric(fontSize)) {
            const fontPx =
              '<span style="font-size: ' +
              fontSize +
              'px;">' +
              deletedValue +
              '</span>';
            this.insertHtml(fontPx);
          } else {
            const fontPx =
              '<span style="font-size: ' +
              fontSize +
              ';">' +
              deletedValue +
              '</span>';
            this.insertHtml(fontPx);
          }
        }
      }
    } else {
      throw new Error('Range out of the editor');
    }
  }

  /**
   * set font name/family for text
   *
   * @param fontName font-family to be set
   */
  setFontName(fontName: string): void {
    if (this.savedSelection && this.checkSelection()) {
      const deletedValue = this.deleteAndGetElement();

      if (deletedValue) {
        const restored = Utils.restoreSelection(this.savedSelection);

        if (restored) {
          if (this.isNumeric(fontName)) {
            const fontFamily =
              '<span style="font-family: ' +
              fontName +
              'px;">' +
              deletedValue +
              '</span>';
            this.insertHtml(fontFamily);
          } else {
            const fontFamily =
              '<span style="font-family: ' +
              fontName +
              ';">' +
              deletedValue +
              '</span>';
            this.insertHtml(fontFamily);
          }
        }
      }
    } else {
      throw new Error('Range out of the editor');
    }
  }

  /** insert HTML */
  private insertHtml(html: string): void {
    const isHTMLInserted = document.execCommand('insertHTML', false, html);

    if (!isHTMLInserted) {
      throw new Error('Unable to perform the operation');
    }
  }

  /**
   * check whether the value is a number or string
   * if number return true
   * else return false
   */
  private isNumeric(value: any): boolean {
    return /^-{0,1}\d+$/.test(value);
  }

  /** delete the text at selected range and return the value */
  private deleteAndGetElement(): any {
    let slectedText;

    if (this.savedSelection) {
      slectedText = this.savedSelection.toString();
      this.savedSelection.deleteContents();
      return slectedText;
    }

    return false;
  }

  /** check any slection is made or not */
  private checkSelection(): any {
    const slectedText = this.savedSelection.toString();

    if (slectedText.length === 0) {
      throw new Error('No Selection Made');
    }

    return true;
  }

  /**
   * check tag is supported by browser or not
   *
   * @param tag HTML tag
   */
  private checkTagSupportInBrowser(tag: string): boolean {
    return !(document.createElement(tag) instanceof HTMLUnknownElement);
  }
}
