import { is } from '@pilotlab/lux-is';
import {AudioManager} from '@pilotlab/lux-audio';
import {Animation, IAnimationEaseFunction, AnimationEaseQuartic} from '@pilotlab/lux-animation';
import {Initializable} from '@pilotlab/lux-initializable';
import {CommandManager, ThrottlingCommand} from '@pilotlab/lux-commands';
import Debug from '@pilotlab/lux-debug';
import Detector from '@pilotlab/lux-detector';
import {IFileDirectory} from '@pilotlab/lux-files';
import {FontManager} from '@pilotlab/lux-fonts';
import {IElement, ElementRoot, IElementRenderer} from '@pilotlab/lux-elements';
import Result from '@pilotlab/lux-result';
import {Signal} from '@pilotlab/lux-signals';
import {Color} from '@pilotlab/lux-types';
import {IAppContext, AppContextDesktop, AppContextMobile,DeviceType} from './appContext';
import {BuildEnvironment, Orientation} from './appEnums';



export class AppManager extends Initializable {
    constructor() { super(); }


    /*====================================================================*
     START: Properties
     *====================================================================*/
    get isOnline():boolean { return this._isOnline; }
    private _isOnline:boolean = false;


    get appContext():IAppContext { return this._appContext; }
    private _appContext:IAppContext;


    get viewport():any { return this._viewport; }
    private _viewport:any;


    get buildEnvironment():BuildEnvironment { return this._buildEnvironment; }
    set buildEnvironment(value:BuildEnvironment) {
        this._buildEnvironment = value;

        Debug.isProduction = (value !== BuildEnvironment.DEVELOPMENT && value !== BuildEnvironment.TESTING);
    }
    private _buildEnvironment:BuildEnvironment = BuildEnvironment.DEVELOPMENT;


    //===== App layout properties (will be set automatically)
    get appWidth():number { return this._appWidth; }
    private _appWidth:number = 1920;


    get appHeight():number { return this._appHeight; }
    private _appHeight:number = 1080;


    get appScale():number { return this._appScale; }
    private _appScale:number = 1;


    //===== App controls
    get root():ElementRoot { return this._root; }
    private _root:ElementRoot;


    get deviceScreen():IElement { return this._deviceScreen; }
    private _deviceScreen:IElement = new Element();


    get appRootElement():IElement {  return this._appRootElement; }
    private _appRootElement:IElement;


    get commands():CommandManager { return this.p_commands; }
    protected p_commands:CommandManager = new CommandManager(true);


    get fonts():FontManager { return this.p_fonts; }
    protected p_fonts = new FontManager();


    get audio():AudioManager { return this.p_audio; }
    protected p_audio = new AudioManager();
    isAudioOn:boolean = false;

    private _deviceScaleFactor:number = null;


    //===== Animation Settings
    durationQuick:number = 0.5;
    durationNormal:number = 1.0;
    durationLong:number = 1.5;
    animEaseDefault:IAnimationEaseFunction = AnimationEaseQuartic.out;


    //===== Colors
    get colorVeryLight():Color { return new Color(255, 255, 255); }
    get colorLight():Color { return new Color(240, 240, 240); }
    get colorDark():Color { return new Color(80, 80, 80); }
    get colorVeryDark():Color { return new Color(35, 31, 32); }
    get colorGrayLight():Color { return Color.fromHex('#e2e2e2'); }
    get colorGrayMediumLight():Color { return Color.fromHex('#c5c5c5'); }
    get colorGray():Color { return Color.fromHex('#9e9e9e'); }
    get colorGrayMediumDark():Color { return Color.fromHex('#6f6f6f'); }
    get colorGrayDark():Color { return Color.fromHex('#9e9e9e'); }
    get colorAccent():Color { return new Color(25, 230, 140); }


    //===== App component Colors
    get colorBody():Color { return this.colorVeryLight; }
    get colorAppBackground():Color { return new Color(0, 0, 0); }


    assetDirectory:IFileDirectory;
    assetDirectoryRemote:IFileDirectory;


    /*====================================================================*
     START: Signals
     *====================================================================*/
    initialized:Signal<IAppContext> = new Signal<IAppContext>(true);
    layoutChanged:Signal<AppManager> = new Signal<AppManager>(false);
    onlineStarted:Signal<AppManager> = new Signal<AppManager>(false);
    offlineStarted:Signal<AppManager> = new Signal<AppManager>(false);


    /*====================================================================*
     START: Static Properties and Methods
     *====================================================================*/
    //===== Fullscreen
    static get isFullscreen():boolean {
        let doc:any = document;
        let isFullscreen:boolean = false;

        if (
            doc.fullscreenElement ||
            doc.webkitFullscreenElement ||
            doc.mozFullScreenElement ||
            doc.msFullscreenElement
        ) isFullscreen = true;
        else if( window.innerHeight == screen.height) isFullscreen = true;
        else if( screen.availHeight <= window.innerHeight) isFullscreen = true;

        return isFullscreen;
    }


    static fullscreenToggle():void {
        if (AppManager.isFullscreen) AppManager.fullscreenExit();
        else AppManager.fullscreenEnter();
    }


    static fullscreenEnter():void {
        //----- Either not Chrome, or not as an app window
        let docElem:any = document.documentElement;

        if (Detector.isChromeApp) {
            let win:any = window;
            win.chrome.app.window.current().fullscreen();
        } else if (docElem.requestFullscreen)  docElem.requestFullscreen();
        else if (docElem.mozRequestFullScreen) docElem.mozRequestFullScreen();
        else if (docElem.webkitRequestFullScreen) docElem.webkitRequestFullScreen();
    }


    static fullscreenExit():void {
        let doc:any = document;

        if (Detector.isChromeApp) {
            let win:any = window;
            win.chrome.app.window.current().restore();
        } else if (doc.exitFullscreen) doc.exitFullscreen();
        else if (doc.webkitExitFullscreen) doc.webkitExitFullscreen();
        else if (doc.webkitCancelFullscreen) doc.webkitCancelFullscreen();
        else if (doc.mozCancelFullScreen) doc.mozCancelFullScreen();
        else if (doc.msExitFullscreen) doc.msExitFullscreen();
    }


    /*====================================================================*
     START: Public Methods
     *====================================================================*/
    initialize(renderer?:IElementRenderer, appContext?:IAppContext, appRootElement?:IElement):Result<any> {
        return super.initialize(renderer, appContext, appRootElement);
    }


    protected p_onInitializeStarted(result:Result<any>, args:any[]):Result<any> {
        this.p_initializeApp(args[0], args[1], args[2]);
        result.resolve();
        return result;
    }


    protected p_initializeApp(renderer?:IElementRenderer, appContext?:IAppContext, appRootElement?:IElement):boolean {
        if (this.isInitialized) return false;

        Animation.initialize();

        if (is.empty(renderer)) renderer = new RendererCanvas();
        if (is.empty(appContext)) appContext = new AppContextDesktop();
        if (is.empty(appRootElement)) appRootElement = new Element();

        this._appContext = appContext;

        if (is.notEmpty(this._appContext) && Detector.isMobile) {
            //---- Browser supports 'deviceorientation' events
            if (Detector.isSupportedDeviceOrientationEvent) {
                window.addEventListener('deviceorientation', () => { this.p_layoutChanged(); }, false);
            } else window.addEventListener('orientationchange', () => { this.p_layoutChanged(); });
        }

        //----- listen for resize event
        let resizeCommand:ThrottlingCommand = new ThrottlingCommand(() => {
                //----- Android doesn't guarantee an orientation event so we need to listen to the resize event
                if (Detector.isAndroid) {
                    let dif = window.innerWidth / this.root.size.x;
                    let round = Math.round(dif * 100) / 100;
                    if (
                        (window.orientation !== undefined && window.orientation !== this.deviceScreen.rotation.z)
                        || round !== 1
                    ) this.p_layoutChanged();
                } else this.p_layoutChanged();
            },
            null, this, 100 /* throttle to about 10 times per second */
        );

        if (!Detector.isAppleMobile) { // Don't use for iOS devices as it will cause the app to crash
            window.addEventListener('resize', (e) => resizeCommand.throttle());
        }

        document.body.style.backgroundColor = this.colorBody.toHex();

        //===== root
        this._root = new ElementRoot(renderer, this);
        this.root.mouseMoveInterval = Detector.isMobile ? 1 : 0;
        this.root.style.background.color = Color.empty;
        this.root.isPropagateUserInputEvents = true;
        document.body.appendChild(this.root.element);

        //===== viewport
        if (Detector.isMobile) {
            let viewport:any = document.head.querySelector('meta[name=viewport]');
            if (is.notEmpty(viewport)) this._viewport = viewport;
            else {
                this._viewport = document.createElement('meta');
                this._viewport.name = 'viewport';
                document.head.appendChild(this._viewport);
            }
        }

        //===== deviceScreen
        this.deviceScreen.pivot.x = 0.5;
        this.deviceScreen.pivot.y = 0.5;
        this.deviceScreen.isClipChildren = true;
        this.deviceScreen.isPropagateUserInputEvents = true;
        this.deviceScreen.style.background.color = Color.empty;
        this.root.children.addByKey(this.deviceScreen, 'deviceScreen');

        //===== appRootElement
        this._appRootElement = appRootElement;
        this.deviceScreen.children.addByName(this.appRootElement, 'appRoot');

        //===== audio
        this.root.animate.ticked.listen((elapsedMilliseconds:number) => {
            if (!this.isAudioOn) return;
            this.p_audio.tick(elapsedMilliseconds);
        });

        //===== online/offline
        window.addEventListener('online', () => {
            this._isOnline = true;
            this.p_online();
            this.onlineStarted.dispatch(this);
        }, false);

        window.addEventListener('offline', () => {
            this._isOnline = false;
            this.p_offline();
            this.offlineStarted.dispatch(this);
        }, false);

        //----- set the layout
        this.p_layoutChanged();

        return true;
    }


    setLayout(
        appContext:IAppContext,
        w:number,
        h:number,
        browserZoom:number,
        screenOrientation:Orientation
    ):void {
        if (is.empty(this.root) || is.empty(appContext)) return;

        //===== Is the device target desktop or mobile?
        if (appContext.deviceTarget === DeviceType.MOBILE) {
            //----- mobile app target
            let appContextMobile:AppContextMobile = <AppContextMobile>appContext;

            //===== Are we locked to portrait, landscape, or neither?
            if (appContextMobile.orientationMode === Orientation.VERTICAL) {
                //----- Locked to portrait.
                //===== Is the mobile device in portrait or landscape mode?
                if (screenOrientation === Orientation.VERTICAL) {
                    //----- portrait mode
                    this.setMobileLayout(appContextMobile, w, h, false);
                } else {
                    //----- landscape mode
                    this.setMobileLayout(appContextMobile, w, h, true);
                }
            } else if (appContextMobile.orientationMode === Orientation.HORIZONTAL) {
                //----- Locked to landscape.
                //===== Is the mobile device in portrait or landscape mode?
                if (screenOrientation === Orientation.HORIZONTAL) {
                    //----- landscape mode
                    this.setMobileLayout(appContextMobile, w, h, false);
                } else {
                    //----- portrait mode
                    this.setMobileLayout(appContextMobile, w, h, true);
                }
            } else {
                //----- Orientation isn't locked.
                this.setMobileLayout(appContextMobile, w, h, false);
            }

            //===== Are we running inside a mobile image shell?
            if (!Detector.isMobile && is.notEmpty(appContextMobile.emulationContext)) {
                //----- We are running inside a mobile app image shell.
                this._appScale = 0.5;
            }
        } else {
            //----- desktop app target
            this.setDesktopRootLayout(<AppContextDesktop>appContext, w, h, browserZoom, true);
        }
    }


    setDesktopRootLayout(
        appContextDesktop:AppContextDesktop,
        screenWidth:number,
        screenHeight:number,
        browserZoom:number = 1,
        isSetAppDimensions:boolean = false
    ):void {
        //===== Fit to parent or fixed aspect ratio for the root control?
        if (
            is.empty(appContextDesktop)
            || (is.empty(appContextDesktop.widthPreferred)
            && is.empty(appContextDesktop.heightPreferred))
        ) {
            //----- layout mode: fit parent
            this.fitRoot(screenWidth, screenHeight, browserZoom);

            if (isSetAppDimensions) {
                this._appWidth = screenWidth;
                this._appHeight = screenHeight;
            }
        } else if (
            is.notEmpty(appContextDesktop.widthPreferred)
            && is.notEmpty(appContextDesktop.heightPreferred)
        ) {
            //----- layout mode: fixed aspect ratio
            let rootAspectRatio:number = appContextDesktop.heightPreferred / appContextDesktop.widthPreferred;
            this.forceRootAspectRatio(screenWidth, screenHeight, rootAspectRatio, browserZoom);

            if (isSetAppDimensions) {
                this._appWidth = appContextDesktop.widthPreferred;
                this._appHeight = appContextDesktop.heightPreferred;
            }
        } else if (is.notEmpty(appContextDesktop.widthPreferred)) {
            //----- layout mode: fixed width, height fit to parent
            let widthFinal:number = this.appContext.widthPreferred;

            if (this.appContext.widthPreferred <= screenWidth) widthFinal = this.appContext.isConstrainMax ? this.appContext.widthPreferred : screenWidth;
            else widthFinal = this.appContext.isConstrainMin ? this.appContext.widthPreferred : screenWidth;

            this.fitRoot(widthFinal, screenHeight, browserZoom);

            //----- IMPORTANT: No need to set viewport attributes for this mode, if we're actually running on a mobile device?

            if (isSetAppDimensions) {
                this._appWidth = widthFinal;
                this._appHeight = screenHeight;
            }
        }  else if (is.notEmpty(appContextDesktop.heightPreferred)) {
            //----- layout mode: fixed height, width fit to parent
            let heightFinal:number = this.appContext.heightPreferred;

            if (this.appContext.heightPreferred <= screenHeight) heightFinal = this.appContext.isConstrainMax ? this.appContext.heightPreferred : screenHeight;
            else heightFinal = this.appContext.isConstrainMin ? this.appContext.heightPreferred : screenHeight;

            this.fitRoot(screenWidth, heightFinal, browserZoom);

            //----- IMPORTANT: No need to set viewport attributes for this mode, if we're actually running on a mobile device?

            if (isSetAppDimensions) {
                this._appWidth = screenWidth;
                this._appHeight = heightFinal;
            }
        }
    }


    setMobileLayout(
        appContextMobile:AppContextMobile,
        screenWidth:number,
        screenHeight:number,
        isRotated:boolean = false,
        browserZoom:number = 1
    ):void {
        //----- Preferred Width is set
        if (is.notEmpty(appContextMobile.widthPreferred)) {
            this._appWidth = appContextMobile.widthPreferred;
            if (is.notEmpty(appContextMobile.heightPreferred)) {
                this._appHeight = appContextMobile.heightPreferred
            } else {
                this._appHeight = isRotated
                    ? screenWidth * (this._appWidth / screenHeight)
                    : screenHeight * (this._appWidth / screenWidth);
            }
        } else {
            this._appWidth = isRotated ? screenHeight : screenWidth;
            this._appHeight = isRotated ? screenWidth : screenHeight;
        }
        this._appScale = 1;

        //===== Mobile
        if (Detector.isMobile) {
            this.p_initializeViewport(screenWidth, screenHeight, isRotated);

            if (isRotated) this.fitRoot(this._appHeight, appContextMobile.widthPreferred, browserZoom);
            else this.fitRoot(appContextMobile.widthPreferred, this._appHeight, browserZoom);
        } else {
        //===== Desktop
            if (is.notEmpty(appContextMobile.emulationContext)) {
                this._appWidth = appContextMobile.emulationContext.appWidthPreferred;
                this._appHeight = appContextMobile.emulationContext.appHeightPreferred;
                this._appScale = 1;
            } else {
                if (is.notEmpty(appContextMobile.widthPreferred)) {
                    this._appScale = (isRotated ? screenHeight : screenWidth) / appContextMobile.widthPreferred;
                    if (is.empty(appContextMobile.heightPreferred)) {
                        this._appHeight /= this._appScale;
                    }
                }
            }

            this.setDesktopRootLayout(appContextMobile.emulationContext, screenWidth, screenHeight, browserZoom, false);
        }

        //===== Set rotation
        if (
            Detector.isMobile
            || is.empty(appContextMobile.emulationContext)
            || appContextMobile.emulationContext.mobileOrientationMode === Orientation.BOTH
        ) {
            //----- Mobile
            this.deviceScreen.rotation.z = isRotated ? 90 : 0;
        } else {
            //----- Desktop
            this.deviceScreen.rotation.z = appContextMobile.emulationContext.mobileOrientationMode === Orientation.HORIZONTAL ? 90 : 0;
        }
    }


    protected p_initializeViewport(screenWidth:number, screenHeight:number, isRotated:boolean):void {
        if (this.viewport) {
            //----- Width relative to orientation
            let width = isRotated ? this._appHeight : this._appWidth;
            //----- Scale relative to orientation
            let scale = (isRotated ? screenHeight : screenWidth) / this._appWidth;

            let viewScale:number;
            if (is.empty(this._deviceScaleFactor)) viewScale = scale;
            else if (Math.round(scale * 10) / 10 === 1) { // Round to nearest tenth
                viewScale = 1;
            } else {
                viewScale = Math.round(scale * this._deviceScaleFactor * 10) / 10; // Round to nearest tenth
            }

            //---- Set the device scale factor on the first pass
            if (this._deviceScaleFactor == null) this._deviceScaleFactor = scale;

            this._viewport.setAttribute('content',
                'width=' + width
                + ', minimum-scale=' + viewScale
                + ', maximum-scale=' + viewScale
                + ', user-scalable=no');
        }
    }


    fitRoot(w:number, h:number, browserZoom:number):void {
        this.root.size.x = w / browserZoom;
        this.root.size.y = h / browserZoom;
        this.root.position.left = (w / 2) - (this.root.size.x / 2);
        this.root.position.top = (h / 2) - (this.root.size.y / 2);
    }


    forceRootAspectRatio(w:number, h:number, rootAspectRatio:number, browserZoom:number = 1):void {
        if (h / w > rootAspectRatio) {
            //----- parent area aspect ratio is taller than the root aspect ratio.
            //----- Set the root width to the actual width of the browser's viewable area.
            this.root.position.left = 0;

            let widthFinal:number = w / browserZoom;

            if (this.appContext.widthPreferred <= widthFinal) this.root.size.x = this.appContext.isConstrainMax ? this.appContext.widthPreferred : widthFinal;
            else this.root.size.x = this.appContext.isConstrainMin ? this.appContext.widthPreferred : widthFinal;

            this.root.size.y = this.root.size.x * rootAspectRatio;
            this.root.position.top = (h / 2) - (this.root.size.y / 2);

            this._appScale = this.root.size.x / this._appContext.widthPreferred;
        } else {
            //----- parent area aspect ratio is wider than the root aspect ratio.
            //----- Set the root height to the actual height of the browser's viewable area.
            this.root.position.top = 0;

            let heightFinal:number = h / browserZoom;

            if (this.appContext.heightPreferred <= heightFinal) this.root.size.y = this.appContext.isConstrainMax ? this.appContext.heightPreferred : heightFinal;
            else this.root.size.y = this.appContext.isConstrainMin ? this.appContext.heightPreferred : heightFinal;

            this.root.size.x = this.root.size.y / rootAspectRatio;
            this.root.position.left = (w / 2) - (this.root.size.x / 2);

            this._appScale = this.root.size.y / this._appContext.heightPreferred;
        }
    }


    /*====================================================================*
     START: Protected Methods
     *====================================================================*/
    protected p_layoutChanged(eventData?:any):void {
        let w:number = window.innerWidth
            || document.documentElement.clientWidth
            || document.body.clientWidth;

        let h:number = window.innerHeight
            || document.documentElement.clientHeight
            || document.body.clientHeight;

        //----- Detect whether the browser window is zoomed.
        //----- NOTE: This may not work on all browsers.
        let browserZoom:number = 1; // window.innerWidth / window.outerWidth;

        let screenOrientation:Orientation = w > h
            ? Orientation.HORIZONTAL
            : Orientation.VERTICAL;

        this.setLayout(this._appContext, w, h, browserZoom, screenOrientation);

        //===== deviceScreen
        if (is.notEmpty(this.deviceScreen)) {
            this.deviceScreen.size.x = this._appWidth;
            this.deviceScreen.size.y = this._appHeight;

            this.deviceScreen.scale = this._appScale;
            this.deviceScreen.position.x = this.root.size.x / 2;
            this.deviceScreen.position.y = this.root.size.y / 2;
        }

        //===== appRoot
        if (is.notEmpty(this.appRootElement)) {
            this.appRootElement.size.x = this._appWidth;
            this.appRootElement.size.y = this._appHeight;
        }

        this.p_onLayoutChanged();
        this.layoutChanged.dispatch(this);

        //----- center, if necessary
        if (this.root.size.x < w) this.root.left = (w / 2) - (this.root.size.x / 2);
        if (this.root.size.y < h) this.root.top = (h / 2) - (this.root.size.y / 2);
    }


    protected p_onLayoutChanged():void {}


    protected p_online():void {}
    protected p_offline():void {}
} // End of class


export default AppManager;
