import {Plugins, AppState} from '@capacitor/core';
import {AfterViewChecked, AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {ChatMessage} from '../chat.message';
import {Setting} from '../setting';
import {ApiService} from '../api.service';
import {WebsocketListener} from '../websocket.listener';
import {WebsocketService} from '../websocket.service';
import {LocalNotifications} from '@ionic-native/local-notifications/ngx';
import {BackgroundMode} from '@ionic-native/background-mode/ngx';
import {AppComponent} from '../app.component';
import {Platform} from '@ionic/angular';
import {VersionInfo} from '../version.info';
import FullscreenError from '../fullscreen.error';
import FullscreenNotification from '../fullscreen.notification';
import AppConfig from '../app.config';
import FullscreenDialog from '../fullscreen.dialog';
import AppStorage from '../app.storage';
import {User} from '../user';

const {App} = Plugins;

@Component({
  selector: 'app-chat',
  templateUrl: './chat.component.html',
  styleUrls: ['./chat.component.scss'],
})
export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, WebsocketListener {
  public static readonly SWIPE_SENSITIVITY: number = 30;

  messages: ChatMessage[] = [];
  userToken: string;
  userId: number;
  url: string;
  chatText: string;
  userList: User[] = [];

  @ViewChild('chat') chat: ElementRef;
  @ViewChild('sidebar') sidebar: ElementRef;
  @ViewChild('chatPostArea') chatPostArea: ElementRef;
  @ViewChild('errorMessage') errorMessage: ElementRef;
  @ViewChild('chatTextArea') chatTextArea: ElementRef;
  @ViewChild('chatTypeArea') chatTypeArea: ElementRef;
  @ViewChild('buttonSend') buttonSend: ElementRef;

  private oldScrollHeight = 0;
  private messageOffset = 0;
  private messageLimit = 10;
  private hasBeenReloaded = false;
  private hasFocus = true;
  private isReconnection = false;
  private lastTouchX = null;
  private lastTouchY = null;
  private isSwiping = false;

  public constructor(
    private apiService: ApiService,
    private websocketService: WebsocketService,
    private localNotifications: LocalNotifications,
    private backgroundMode: BackgroundMode,
    private platform: Platform
  ) {
    this.userToken = AppStorage.getToken();
    this.userId = AppStorage.getUserId();
    this.url = Setting.URL;
    this.websocketService.setListener(this);
    this.websocketService.initializeSocket(AppStorage.getChatToken());
    this.backgroundMode.enable();
    this.backgroundMode.disableBatteryOptimizations();
    this.backgroundMode.disableWebViewOptimizations();
  }

  private static isHorizontalSwipe(deltaX: number, deltaY: number): boolean
  {
    return Math.abs(deltaX) / window.innerHeight > Math.abs(deltaY) / window.innerWidth;
  }

  ngAfterViewInit(): void {
    this.chatPostArea.nativeElement.scroll(0, this.chatPostArea.nativeElement.scrollHeight);
  }

  ngAfterViewChecked(): void {
    if (this.oldScrollHeight !== this.chatPostArea.nativeElement.scrollHeight) {
      const scrollTop = this.chatPostArea.nativeElement.scrollTop;
      const clientHeight = this.chatPostArea.nativeElement.clientHeight;

      if (this.hasBeenReloaded) {
        this.chatPostArea.nativeElement.scroll(0, this.chatPostArea.nativeElement.scrollHeight - this.oldScrollHeight);
        this.hasBeenReloaded = false;
      } else if (scrollTop + clientHeight > this.oldScrollHeight - 10) {
        this.chatPostArea.nativeElement.scroll(0, this.oldScrollHeight);
      }

      this.oldScrollHeight = this.chatPostArea.nativeElement.scrollHeight;
    }
  }

  ngOnInit(): void {
    if (this.userToken === null) {
      return;
    }

    this.localNotifications.requestPermission();

    this.apiService.getChatHistory(this.userToken, this.messageOffset, this.messageLimit).toPromise()
      .then(
        (response) => {
          response.forEach(
            (message: ChatMessage) => {
              message.htmlMessage = this.parseHtml(message.message);

              this.messages.push(message);
              this.messageOffset++;
            }
          );
        }
      ).catch(
        (error) => {
          const notification = new FullscreenError('Verbindung zur Web-API gescheitert!\n\nStatus-Code: ' + error.status);
        }
      );

    App.addListener('appStateChange', (state: AppState) => {
      this.hasFocus = state.isActive;
    });

    this.platform.backButton.subscribe(
      () => {
        const question = new FullscreenDialog('App beenden', 'Möchtest du wirklich ausloggen?');
        question.addButton('Ja', () => {this.onLogout(); });
        question.addButton('Nein');
      }
    );

    setInterval(
      () => {
        this.websocketService.sendKeepAliveMessage();
      }, 1000 * 60 // every minute
    );

    setTimeout(
      () => {
        this.checkVersion();
      },
      5000
    );
  }

  onScroll(): void {
    if (this.chatPostArea.nativeElement.scrollTop === 0) {
      this.apiService.getChatHistory(this.userToken, this.messageOffset, this.messageLimit).subscribe(
        (response) => {
          response.reverse();

          response.forEach(
            (message: ChatMessage) => {
              message.htmlMessage = this.parseHtml(message.message);

              this.messages = [message].concat(this.messages);
              this.messageOffset++;
            }
          );

          this.hasBeenReloaded = true;
        }
      );
    }
  }

  onInput(): void {
    this.chatTypeArea.nativeElement.style.height = '50px';
    this.chatTypeArea.nativeElement.style.height = this.chatTextArea.nativeElement.scrollHeight > 50
      ? this.chatTextArea.nativeElement.scrollHeight + 'px'
      : '50px';

    if (this.chatTypeArea.nativeElement.clientHeight > window.innerHeight) {
      this.chatTypeArea.nativeElement.style.height = window.innerHeight + 'px';
    }
  }

  postMessage(): void {
      if (this.chatTextArea.nativeElement.value.trim() === '') {
        return;
      }

      this.websocketService.sendChatMessage(this.chatTextArea.nativeElement.value);
      this.chatTextArea.nativeElement.value = '';

      this.onInput();
  }

  onLogout() {
    AppStorage.setToken(null);
    this.websocketService.destroy();

    AppComponent.token = null;
  }

  onChatMessage(message: ChatMessage): void {
    message.htmlMessage = this.parseHtml(message.message);

    this.messages.push(message);
    this.messageOffset++;

    if (!this.backgroundMode.isActive() || message.userId === this.userId) {
      return;
    }

    this.triggerNotification(message);
  }

  onConnection(): void
  {
    this.errorMessage.nativeElement.style.display = 'none';

    this.enableSendButton();

    if (this.isReconnection) {
      this.apiService.getChatMessagesMissed(this.userToken, this.messages[this.messages.length - 1].id).toPromise()
        .then(
          (messagesMissed) => {
            if (messagesMissed.length === 0) {
              return;
            }

            for (const message of messagesMissed) {
              message.htmlMessage = this.parseHtml(message.message);

              this.messages.push(message);
              this.messageOffset++;
            }
          }
        ).catch(
          (error) => {
            console.log('Failed to load messages missed after reconnect!', error);
          }
      );
    }

    this.isReconnection = true;
  }

  onReconnect(): void
  {
    this.isReconnection = true;
  }

  onChatTouch(event: TouchEvent): void
  {
    if (this.isSwiping) {
      return;
    }

    if (this.getTouchEvent(event) === -1) {
      event.preventDefault();

      this.chat.nativeElement.id = 'chat-swiped-out';
      this.sidebar.nativeElement.id = 'sidebar-swiped-in';

      this.isSwiping = true;

      setTimeout(
        () => {
          this.isSwiping = false;
        },
        500
      );
    }
  }

  onSidebarTouch(event: TouchEvent): void
  {
    if (this.isSwiping) {
      return;
    }

    if (this.getTouchEvent(event) === 1) {
      this.chat.nativeElement.id = 'chat-swiped-in';
      this.sidebar.nativeElement.id = 'sidebar-swiped-out';

      this.isSwiping = true;

      setTimeout(
        () => {
          this.isSwiping = false;
          },
        500
      );
    }
  }

  onTouchStart(event: TouchEvent): void
  {
  }

  onTouchEnd(): void
  {
    this.lastTouchX = null;
    this.lastTouchY = null;
  }

  onError(message: string): void
  {
    this.errorMessage.nativeElement.style.display = 'block';
    this.errorMessage.nativeElement.innerText = message;
    this.disableSendButton();
  }

  onUserListInit(userList: User[]): void
  {
    userList.forEach(
      (user) => {
        if (user.userId !== this.userId && !this.hasUserListUser(user)) {
          this.userList.push(user);
        }
      }
    );

    this.sortUserList();
  }

  onUserConnected(user: User): void
  {
    if (!this.hasUserListUser(user)) {
      this.userList.push(user);
      this.sortUserList();
    }
  }

  onUserDisconnected(userId: number): void
  {
    for (let u = 0; u < this.userList.length; u++) {
      if (this.userList[u].userId === userId) {
        this.userList = this.userList.slice(0, u).concat(this.userList.slice(u + 1));

        return;
      }
    }
  }

  citeUsername(username: string): void
  {
    this.insertIntoTextareaCursorPosition('@' + username, false);
  }

  citeMessage(username: string, message: string): void
  {
    const originalMessage = [];

    message.split('\n').forEach(
      (paragraph: string) => {
        if (paragraph.substr(0, 1) !== '>') {
          originalMessage.push('> @' + username + ': ' +  paragraph);
        }
      }
    );

    this.insertIntoTextareaCursorPosition(originalMessage.join('\n') + '\n');
  }

  triggerNotification(message: ChatMessage): void
  {
    this.localNotifications.schedule(
      {
        title: message.username,
        text: message.message,
        id: 1,
        priority: 2,
        lockscreen: true,
        autoClear: true,
        icon: Setting.URL + '/user/' + message.userId + '/avatar?token=' + this.userToken,
        smallIcon: 'ic_stat_notification_icon_enabled',
        led: '#ff00ff',
        trigger: { at: new Date(new Date().getTime() + 1000) },
        sound: 'file://assets/audio/murloc.wav',
        vibrate: true
      }
    );
  }

  private checkVersion(): void
  {
    const lastChecked = AppStorage.getLastVersionCheck();
    const today = new Date();

    if (lastChecked !== null) {
      const dateLastChecked = new Date(lastChecked);

      if (dateLastChecked.toDateString() === today.toDateString()) {
        return;
      }
    }

    AppStorage.setLastVersionCheck(today.getTime());

    this.apiService.getCurrentVersion().toPromise()
      .then(
        (versionInfo: VersionInfo) => {
          const isUpdateNotificationDesired = AppStorage.isUpdateNotificationDesired();

          if (isUpdateNotificationDesired && AppConfig.VERSION < versionInfo.currentVersionAndroid) {
            const notification = new FullscreenNotification('Neue Version', 'Eine neue Version von METAsocket ist verfügbar.');
            const label = document.createElement('label');
            const checkbox = document.createElement('input');
            const text = document.createElement('span');
            text.innerText = ' Nerv mich nie wieder damit';
            checkbox.type = 'checkbox';
            label.appendChild(checkbox);
            label.appendChild(text);
            label.onclick = () => {
              AppStorage.setIsUpdateNotificationDesired(!checkbox.checked);
            };

            notification.addExtendedInputElement(label);
            notification.addUrlButton('Runterladen', 'https://sabolli.de/wow/metasocket/download/android');
          }
        }
      );
  }

  private getTouchEvent(event: TouchEvent): number
  {
    if (this.lastTouchX !== null && this.lastTouchY !== null) {
      const deltaX = event.changedTouches[0].screenX - this.lastTouchX;
      const deltaY = event.changedTouches[0].screenY - this.lastTouchY;

      this.lastTouchX = null;
      this.lastTouchY = null;

      if (ChatComponent.isHorizontalSwipe(deltaX, deltaY) && Math.abs(deltaX) >= ChatComponent.SWIPE_SENSITIVITY) {
        return deltaX >= 0 ? 1 : -1;
      }

      return 0;
    }

    this.lastTouchX = event.changedTouches[0].screenX;
    this.lastTouchY = event.changedTouches[0].screenY;
  }

  private hasChatMessage(message: ChatMessage): boolean
  {
    for (const messageStored of this.messages) {
      if (messageStored.id === message.id) {
        return true;
      }
    }

    return false;
  }

  private parseHtml(messageText: string): string
  {
    let parsedText = '';
    let isInsideCite = false;
    let isInsideUsername = false;

    for (const char of messageText) {
      switch (char) {
        case '>':
          if (isInsideCite) {
            parsedText += '</div>';
          }

          parsedText += '<div class="message-cite">';
          isInsideCite = true;
          break;

        case '\n':
          parsedText += isInsideCite ? '</div>' : '<br>';
          break;

        default:
          if (char === ' ' && isInsideUsername) {
            isInsideUsername = false;
            parsedText += '</span>';
          }

          parsedText += char;
      }
    }

    if (isInsideCite) {
      parsedText += '</div>';
    }

    const matches = parsedText.match(/(?!(\b))@\w+/g);

    if (matches === null) {
      return parsedText;
    }

    matches.forEach(
      (match) => {
        parsedText = parsedText.replace(match, '<span class="message-cite-username">' + match.substr(match.indexOf('@') + 1) + '</span>');
      }
    );

    return parsedText;
  }

  private insertIntoTextareaCursorPosition(text: string, isCite: boolean = true): void
  {
    const textarea = this.chatTextArea.nativeElement;

    const selectionStartBuffer = textarea.selectionStart;
    const selectionEndBuffer = textarea.selectionEnd;

    const textLength = textarea.value.length;
    const messagePartA = textarea.value.substr(0, textarea.selectionStart) + (isCite && textarea.selectionStart > 0 ? '\n' : '');
    const messagePartB = textarea.value.substr(textarea.selectionStart, textLength);

    textarea.value = isCite ? messagePartA + text + messagePartB : messagePartA + text + messagePartB.trim();
    textarea.focus();
    textarea.selectionStart = selectionStartBuffer + text.length;
    textarea.selectionEnd = selectionEndBuffer + text.length;

    this.onInput();
  }

  private enableSendButton(): void
  {
    this.buttonSend.nativeElement.classList.add('button-send-online');
    this.buttonSend.nativeElement.classList.remove('button-send-offline');
    this.buttonSend.nativeElement.disabled = false;
  }

  private disableSendButton(): void
  {
    this.buttonSend.nativeElement.classList.remove('button-send-online');
    this.buttonSend.nativeElement.classList.add('button-send-offline');
    this.buttonSend.nativeElement.disabled = true;
  }

  private sortUserList(): void
  {
    this.userList.sort(
      (a: User, b: User) => {
        return a.username > b.username ? 1 : -1;
      }
    );
  }

  private hasUserListUser(user: User): boolean
  {
    for (const u of this.userList) {
      if (user.userId === u.userId) {
        return true;
      }
    }

    return false;
  }
}
