import { Component, HostListener, OnInit, ViewChild } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { MatSidenav } from '@angular/material/sidenav';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Title } from '@angular/platform-browser';
import { ActivationEnd, NavigationEnd, Router } from '@angular/router';

import { TranslateService } from '@ngx-translate/core';
import { filter } from 'rxjs/operators';

import { navItemAnim } from '../shared/animations';
import { OfflineService } from 'app/core/core-services/offline.service';
import { LoginDataService } from 'app/core/ui-services/login-data.service';
import { OverlayService } from 'app/core/ui-services/overlay.service';
import { UpdateService } from 'app/core/ui-services/update.service';
import { DEFAULT_AUTH_TYPE } from 'app/shared/models/users/user';
import { langToLocale } from 'app/shared/utils/lang-to-locale';
import { AuthService } from '../core/core-services/auth.service';
import { BaseComponent } from '../base.component';
import { MainMenuService } from '../core/core-services/main-menu.service';
import { OpenSlidesStatusService } from '../core/core-services/openslides-status.service';
import { OperatorService } from '../core/core-services/operator.service';
import { TimeTravelService } from '../core/core-services/time-travel.service';
import { ViewportService } from '../core/ui-services/viewport.service';

/**
 * Interface to describe possible routing data
 */
interface RoutingData {
    basePerm?: string;
    noInterruption?: boolean;
}

@Component({
    selector: 'os-site',
    animations: [navItemAnim],
    templateUrl: './site.component.html',
    styleUrls: ['./site.component.scss']
})
export class SiteComponent extends BaseComponent implements OnInit {
    /**
     * HTML element of the side panel
     */
    @ViewChild('sideNav', { static: true })
    public sideNav: MatSidenav;

    /**
     * Get the username from the operator (should be known already)
     */
    public username = '';

    public authType = DEFAULT_AUTH_TYPE;

    /**
     * is the user logged in, or the anonymous is active.
     */
    public isLoggedIn: boolean;

    /**
     * Indicates, whether the user is offline or not.
     */
    public isOffline: boolean;

    /**
     * Holds the typed search query.
     */
    public searchform: FormGroup;

    /**
     * Hold the current routing data to make certain checks
     */
    private routingData: RoutingData;

    /**
     * Set to true if an update was suppressed
     */
    private delayedUpdateAvailable = false;

    public samlChangePasswordUrl: string | null = null;

    /**
     * Constructor
     *
     * @param authService
     * @param route
     * @param operator
     * @param vp
     * @param translate
     * @param dialog
     * @param mainMenuService
     * @param OSStatus
     * @param timeTravel
     */
    public constructor(
        title: Title,
        protected translate: TranslateService,
        offlineService: OfflineService,
        private updateService: UpdateService,
        private authService: AuthService,
        private router: Router,
        public operator: OperatorService,
        public vp: ViewportService,
        public dialog: MatDialog,
        public mainMenuService: MainMenuService,
        public OSStatus: OpenSlidesStatusService,
        public timeTravel: TimeTravelService,
        private matSnackBar: MatSnackBar,
        private overlayService: OverlayService,
        private loginDataService: LoginDataService
    ) {
        super(title, translate);
        overlayService.showSpinner(translate.instant('Loading data. Please wait...'));

        this.operator.getViewUserObservable().subscribe(user => {
            if (!operator.isAnonymous) {
                this.username = user ? user.short_name : '';
                this.isLoggedIn = true;
            } else {
                this.username = translate.instant('Guest');
                this.isLoggedIn = false;
            }
        });
        this.operator.authType.subscribe(authType => (this.authType = authType));

        offlineService.isOffline().subscribe(offline => {
            this.isOffline = offline;
        });

        this.loginDataService.samlSettings.subscribe(
            samlSettings => (this.samlChangePasswordUrl = samlSettings ? samlSettings.changePasswordUrl : null)
        );

        this.searchform = new FormGroup({ query: new FormControl([]) });

        // detect routing data such as base perm and noInterruption
        this.router.events
            .pipe(filter(event => event instanceof ActivationEnd && event.snapshot.children.length === 0))
            .subscribe((event: ActivationEnd) => {
                this.routingData = event.snapshot.data as RoutingData;

                // if the current route has no "noInterruption" flag and an update is available, show the update
                if (this.delayedUpdateAvailable && !this.routingData.noInterruption) {
                    this.showUpdateNotification();
                }
            });
    }

    /**
     * Initialize the site component
     */
    public ngOnInit(): void {
        this.vp.checkForChange();

        // observe the mainMenuService to receive toggle-requests
        this.mainMenuService.toggleMenuSubject.subscribe((value: void) => this.toggleSideNav());

        // get a translation via code: use the translation service
        // this.translate.get('Motions').subscribe((res: string) => {
        //      console.log('translation of motions in the target language: ' + res);
        //  });

        // TODO: Remove this, when the ESR version of Firefox >= 64.
        const agent = navigator.userAgent.toLowerCase();
        if (agent.indexOf('firefox') > -1) {
            const index = agent.indexOf('firefox') + 8;
            const version = +agent.slice(index, index + 2);

            if (version < 64) {
                const sideNav = document.querySelector(
                    'mat-sidenav.side-panel > div.mat-drawer-inner-container'
                ) as HTMLElement;
                sideNav.style.overflow = 'hidden';
                sideNav.addEventListener('MozMousePixelScroll', (event: any) => {
                    sideNav.scrollBy(0, event.detail);
                });
            }
        }

        this.router.events.subscribe(event => {
            // Scroll to top if accessing a page, not via browser history stack
            if (event instanceof NavigationEnd) {
                const contentContainer = document.querySelector('.mat-sidenav-content');
                if (contentContainer) {
                    contentContainer.scrollTo(0, 0);
                }
            }
        });

        // check for updates
        this.updateService.updateObservable.subscribe(() => {
            if (this.routingData.noInterruption) {
                this.delayedUpdateAvailable = true;
            } else {
                this.showUpdateNotification();
            }
        });
    }

    /**
     * Shows the update notification
     */
    private showUpdateNotification(): void {
        const ref = this.matSnackBar.open(
            this.translate.instant('A new update is available!'),
            this.translate.instant('Refresh'),
            {
                duration: 0
            }
        );

        // Enforces an update
        ref.onAction().subscribe(() => {
            this.updateService.applyUpdate();
        });
    }

    /**
     * Toggles the side nav
     */
    public toggleSideNav(): void {
        this.sideNav.toggle();
    }

    /**
     * Automatically close the navigation in while navigating in mobile mode
     */
    public mobileAutoCloseNav(): void {
        if (this.vp.isMobile) {
            this.sideNav.close();
        }
    }

    /**
     * Let the user change the language
     * @param lang the desired language (en, de, cs, ...)
     */
    public selectLang(selection: string): void {
        this.translate.use(selection).subscribe();
    }

    /**
     * Get the name of a Language by abbreviation.
     *
     * @param abbreviation The abbreviation of the languate or null, if the current
     * language should be used.
     */
    public getLangName(abbreviation?: string): string {
        if (!abbreviation) {
            abbreviation = this.translate.currentLang;
        }

        if (abbreviation === 'en') {
            return 'English';
        } else if (abbreviation === 'de') {
            return 'Deutsch';
        } else if (abbreviation === 'cs') {
            return 'Čeština';
        } else if (abbreviation === 'ru') {
            return 'русский';
        }
    }

    /**
     * Function to log out the current user
     */
    public logout(): void {
        if (this.operator.guestsEnabled) {
            this.overlayService.showSpinner(null, true);
        }
        this.authService.logout();
        this.overlayService.logout();
    }

    /**
     * Handle swipes and gestures
     */
    public swipe(e: TouchEvent, when: string): void {
        const coord: [number, number] = [e.changedTouches[0].pageX, e.changedTouches[0].pageY];
        const time = new Date().getTime();

        if (when === 'start') {
            this.swipeCoord = coord;
            this.swipeTime = time;
        } else if (when === 'end') {
            const direction = [coord[0] - this.swipeCoord[0], coord[1] - this.swipeCoord[1]];
            const duration = time - this.swipeTime;
            if (
                duration < 1000 &&
                Math.abs(direction[0]) > 30 && // swipe length to be detected
                Math.abs(direction[0]) > Math.abs(direction[1] * 3) // 30° should be "horizontal enough"
            ) {
                // definition of a "swipe right" gesture to move in the navigation
                // only works in the far left edge of the screen
                if (
                    direction[0] > 0 && // swipe left to right
                    this.swipeCoord[0] < 20
                ) {
                    this.sideNav.open();
                }

                // definition of a "swipe left" gesture to remove the navigation
                // should only work in mobile mode to prevent unwanted closing of the nav
                // works anywhere on the screen
                if (
                    direction[0] < 0 && // swipe left to right
                    this.vp.isMobile
                ) {
                    this.sideNav.close();
                }
            }
        }
    }

    /**
     * Get the timestamp for the current point in history mode.
     * Tries to detect the ideal timestamp format using the translation service
     *
     * @returns the timestamp as string
     */
    public getHistoryTimestamp(): string {
        return this.OSStatus.getHistoryTimeStamp(langToLocale(this.translate.currentLang));
    }

    /**
     * Function to open the global `super-search.component`.
     *
     * @param event KeyboardEvent to listen to keyboard-inputs.
     */
    @HostListener('document:keydown', ['$event']) public onKeyNavigation(event: KeyboardEvent): void {
        if (event.altKey && event.shiftKey && event.code === 'KeyF') {
            event.preventDefault();
            event.stopPropagation();
            this.overlayService.showSearch();
        }
    }
}
