/**
 * @author Martin Karkowski
 * @email m.karkowski@zema.de
 * @create date 2019-02-20 09:19:06
 * @modify date 2020-10-29 09:55:30
 * @desc [description]
 * 
 * A Basic Layout.
 * I uses a Toolbar, Sidebar
 * 
 * Toast are implemented by https://fkhadra.github.io/react-toastify/introduction
 */

import React from 'react';
import { Col, Container, Modal, Nav, Row } from 'react-bootstrap';
import { FaTimesCircle } from 'react-icons/fa';
import { toast, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { rgetattr, rsetattr } from '../../../lib/helpers/objectMethods';
import DynamicRenderer from '../dynamic/dynamicRenderer';
import { IHotKeyAction } from './interfaces/IHotkeyAction';
import { IModalSettings } from './interfaces/IModalSettings';
import { ITab } from './interfaces/ITab';
import Selection, { SelectionProps } from './selection';
import Toolbar, { ToolbarProps } from './toolbar';

const mainStyle = {
    height: '89vh',
}

const sidebarStyle = {
    padding: '10px',
    // background: "primary",
    overflow: 'scroll'
}

const contentStyle = {
    // padding: '10px',
}

export interface ILayoutProps<Template, CallbackData> extends SelectionProps<Template>, ToolbarProps<CallbackData> {
    onResize?: () => void;
    onMount?: (ref: React.RefObject<any>, mainRef: React.RefObject<any>, layout: Layout<Template,CallbackData>) => void;
    onUnmount?: () => void;
    onNewTab?: () => Promise<ITab | false>;
    onTabSelect?: (oldTabId: ITab, newTabId: ITab) => Promise<boolean>;
    onTabDelete?: (tabId: ITab, forced?: boolean) => Promise<boolean>;
    onNoTabSelected?: () => Promise<void>;
    tabs?: {
        allowNewTabs?: boolean;
        active: string,
        items: ITab[]
    }
    hotkeys?: IHotKeyAction<CallbackData>[]
}



export interface ILayoutState<Template, CallbackData> extends ILayoutProps<Template, CallbackData> {
    modalSettings?: IModalSettings
    modalVisible?: boolean
}

class Layout<Template,CallbackData> extends React.Component<ILayoutProps<Template, CallbackData>, ILayoutState<Template, CallbackData>> {

    protected _mainRef: React.RefObject<any>;
    protected _ref: React.RefObject<any>;
    protected _handleResize: () => void;
    protected _handleKeyDown: (event) => void;
    protected _handleKeyUp: (event) => void;
    protected _handleMouse: (event: MouseEvent) => void;

    public hotkeysEnabled = true;
    public pressedKey: string = null;
    public currentMousePosition: MouseEvent;

    /**
     * Function will be called if the Item has been rendered sucessfully.
     */
    componentDidMount() {

        const _this = this;

        // Listening to resizing Events
        function handleResize() {
            if (typeof _this.state.onResize === 'function') {
                _this.state.onResize();
            }
        }

        function handleKeyDown(event){
            console.log(event.code)     
            if (!_this.hotkeysEnabled || _this.state.hotkeys?.length === 0) {
                return;
            }
    
            if (_this.pressedKey !== event.code) {
                _this.pressedKey = event.code;
    
                try {
                    for (const hotkey of _this.state.hotkeys || []) {
                        if (hotkey.key === event.code) {
                            hotkey.onPress(_this.state.generateData(),event)
                            break;
                        }
                    }
                } catch (e) {
                }
            }        
        }

        function handleKeyUp(event){
            if (!_this.hotkeysEnabled || _this.state.hotkeys?.length === 0) {
                return;
            }
            _this.pressedKey = '';
            try {
                for (const hotkey of _this.state.hotkeys || []) {
                    if (hotkey.key === event.code && hotkey.onRelease) {
                        hotkey.onRelease(_this.state.generateData(),event)
                        break;
                    }
                }
            } catch (e) {
                
            }        
        }

        function handleMouse(event){
            _this.currentMousePosition = event;
        }

        if (typeof _this.state.onMount === 'function') {
            _this.state.onMount(_this._ref, _this._mainRef, _this);
        }

        this._handleResize = handleResize;
        this._handleKeyDown = handleKeyDown;
        this._handleKeyUp = handleKeyUp;
        this._handleMouse = handleMouse;
        window.addEventListener('resize', this._handleResize);
        window.addEventListener('keydown', this._handleKeyDown);
        window.addEventListener('keyup',this._handleKeyUp);
        window.addEventListener('mousemove',this._handleMouse);
    }

    /**
     * Function, that will be called before the network fails.
     */
    componentWillUnmount() {
        window.removeEventListener('resize', this._handleResize);
        window.removeEventListener('keydown', this._handleKeyDown);
        window.removeEventListener('keyup',this._handleKeyUp);
        window.removeEventListener('mousemove',this._handleMouse);

        // Call the unmount
        if (typeof this.state.onUnmount === 'function') {
            this.state.onUnmount();
        }
    }

    constructor(props) {
        super(props);
        this._mainRef = React.createRef();
        this._ref = React.createRef();

        this.state = Object.assign({
            modalSettings: null,
            modalVisible : false
        },this.props);        
    }

    public openPopup(settings: IModalSettings) {
        const modalSettings = settings;

        const _this = this;

        const _close = () => {
            _this.setState({
                modalVisible: false
            });
        };

        const _originalFunction = modalSettings.onHide;
        modalSettings.onHide = (...args) => {
            if (typeof _originalFunction === 'function') {
                _originalFunction(_close);
            } else {
                _close();
            }
        }

        _this.setState({
            modalVisible: true,
            modalSettings: modalSettings
        });

        // Return the Close Method.
        return _close;
    }

    public getDialogData<T = any>(settings: IModalSettings) {
        const _this = this;
        let close: () => void;

        return new Promise<T>((resolve, reject) => {
            // Adapt the Settings:
            const _onSubmit = rgetattr<(data) => Promise<void>>(settings, 'content.props.onSubmit', async (data) => { });
            const _onCancel = rgetattr<(err) => Promise<void>>(settings, 'content.props.onCancel', async (err) => { });

            const onSubmit = async (data) => {
                close();
                _onSubmit(data);
                resolve(data);
            }

            const onCancel = async (data) => {
                close();
                _onCancel(data);
                reject(data);
            }

            rsetattr(settings, 'content.props.onSubmit', onSubmit);
            rsetattr(settings, 'content.props.onCancel', onCancel);

            close = _this.openPopup(settings);
        });
    }

    /**
     * Helper Function to render a Toast.
     * @param message The Message of the Toast
     * @param type The Type.
     * @param timeout A Timeout in [ms], after which the Toast should disapear. 0 = infinity
     */
    public showToast(message: string, type?: 'info' | 'warning' | 'success' | 'error' | 'default' | 'dark' | 'light' | 'dark', autoClose = 5000) {
        if (type !== undefined && type !== null) {
            toast[type](message, {
                position: "top-center",
                autoClose,
                hideProgressBar: false,
                closeOnClick: true,
                pauseOnHover: true,
                draggable: true,
                progress: undefined,
            });
        } else {
            toast(message, {
                position: "top-center",
                autoClose,
                hideProgressBar: false,
                closeOnClick: true,
                pauseOnHover: true,
                draggable: true,
                progress: undefined,
            });
        }
    }

    public get selectedTab(){
        for (const tab of this.state.tabs.items) {
            if (tab.id === this.state.tabs.active) {
                return tab;
            }
        }

        return null;
    }

    public async requestTab(){
        if (typeof this.state.onNewTab === 'function') {
            const tab = await this.state.onNewTab();

            if (tab && typeof tab === 'object') {
                this.state.tabs.items.push(tab);

                this.selectTab(tab);
            }
        }
    }

    public async createTab(){

        if (typeof this.state.onNewTab === 'function') {
            const tab = await this.props.onNewTab();

            if (tab && typeof tab === 'object') {
                const tabs = this.tabs;
                
                tabs.push(tab);
                
                this.tabs = tabs;

                await this.selectTab(tab);
            }
        }
    }

    public get tabs(): ITab[]{
        return this.state.tabs?.items || [];
    }

    public set tabs(value: ITab[]){
        this.setState({
            tabs: {
                active: this.activeTab,
                items: value,
                allowNewTabs: this.state.tabs.allowNewTabs
            }
        })
    }

    public get activeTab(): string{
        return this.state.tabs.active;
    }

    public set activeTab(value: string){
        this.setState({
            tabs: {
                active: value,
                items: this.props.tabs.items,
                allowNewTabs: this.props.tabs.allowNewTabs
            }
        })
    }

    /**
     * Function to Select a Tab
     * @param tab 
     */
    public async selectTab(tab: ITab | string) {
        let newTabToDisplay: ITab;
        
        if (typeof tab === "string"){
            for (const _t of this.tabs){
                if (_t.id = tab){
                    newTabToDisplay = _t;
                    break;
                }
            }
        } else {
            newTabToDisplay = tab;
        }

        if (typeof this.state.onTabSelect === 'function') {

            const currentlySelected = this.selectedTab;

            // Call the Async Function to select a Tab.
            if (await this.state.onTabSelect(currentlySelected, newTabToDisplay)) {
                
                this.activeTab = newTabToDisplay.id;

                return true;
            }

            return false;
        } else {
            // Assign the Tab ID
            this.activeTab = newTabToDisplay.id;

            return true;
        }
    }

    public async delteTab(tab: ITab | string, forced = false) {

        let idx: number = -1;
        let _tab: ITab = null;

        const tabs = this.state.tabs.items;

        for (const [_idx, _t] of tabs.entries()){
            if (_t.id === (typeof tab === 'string' ? tab : tab.id)) {
                idx = _idx;
                _tab = _t;
                break;
            }
        }

        if (_tab != null) {
            let removed = false;
            if (typeof this.state.onTabDelete === 'function') {
                // Call the Async Function to select a Tab.
                if (await this.state.onTabDelete(_tab, forced)) {
                    removed = true;
                }
            } else {
                removed = true;
            }

            if (removed) {
                // Remove the Items.
                tabs.splice(idx, 1);
    
                // Check if the active element has been removed:
                if (_tab.id == this.state.tabs.active) {
    
                    // Assign the ID of the Element.
                    const _idx = tabs.length > idx ? idx : tabs.length - 1;
    
                    if (_idx !== -1) {
                        // Assign the First ID.
                        const _newTab = tabs.length > _idx ? tabs[_idx] : null;
    
                        if (_newTab !== null) {
                            if (!await this.selectTab(_newTab) && typeof this.state.onNoTabSelected === 'function') {
                                // Send a Warning, that no Tabs has been selected
                                await this.state.onNoTabSelected();
                            }
                        } else if (typeof this.state.onNoTabSelected === 'function') {
                            // Send a Warning, that no Tabs has been selected
                            await this.state.onNoTabSelected();
                        }
                    } else if (typeof this.state.onNoTabSelected === 'function') {
                        // Send a Warning, that no Tabs has been selected
                        await this.state.onNoTabSelected();
                    }
                }
                this.tabs = tabs;
            }
        }
    }

    public render() {
        return (<>
            <Toolbar toolbar={this.state.toolbar} generateData={this.state.generateData} brand={{
                label: 'nopeBackend',
                ref: '/',
                type: 'link'
            }}></Toolbar>
            <Container fluid style={mainStyle}>
                <Row style={mainStyle}>
                    <Col className="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse" style={{paddingTop: '10px', paddingLeft: '10px', paddingRight: '10px'}}>
                        <div style={{ overflow: 'scroll', height: '90vh'}}>
                            <Selection selection={this.state.selection} allowUserSelect={this.state.allowUserSelect} onItemSelected={this.state.onItemSelected}></Selection>
                        </div>
                    </Col>
                    <Col role="main" style={contentStyle} ref={this._mainRef}>
                        {this.state.tabs ?
                            <Nav variant="tabs" activeKey={this.state.tabs.active}>
                                {this.state.tabs.items.map((tab, idx) => {
                                    if (tab.delteable && this.state.tabs.allowNewTabs) {
                                        return (
                                            <Nav.Item key={tab.id}>
                                                <Nav.Link onSelect={_ => {
                                                    this.selectTab(tab);
                                                }} eventKey={tab.id}>{tab.label + ' '}
                                                    <FaTimesCircle onClick={
                                                        (e) => {
                                                            e.preventDefault();
                                                            e.stopPropagation();

                                                            this.delteTab(tab);
                                                        }
                                                    } ></FaTimesCircle>
                                                </Nav.Link>
                                            </Nav.Item>
                                        );
                                    } else {
                                        return (
                                            <Nav.Item key={tab.id}>
                                                <Nav.Link onSelect={_ => {
                                                    this.selectTab(tab);
                                                }} eventKey={tab.id}>{tab.label}</Nav.Link>
                                            </Nav.Item>
                                        );
                                    }
                                })}

                                {this.state.tabs.allowNewTabs ?
                                    <Nav.Item>
                                        <Nav.Link eventKey='_NEW_ITEM' onSelect={_ => this.requestTab()}>
                                            +
                                </Nav.Link>
                                    </Nav.Item> : ''
                                }

                            </Nav>
                            : ''
                        }

                        {/* style={{height: '100%'}} */}
                        <div ref={this._ref}></div>
                    </Col>
                    <Col className="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse" style={{paddingTop: '10px', paddingLeft: '10px', paddingRight: '10px'}}>
                        <div style={{ overflow: 'scroll', height: '90vh'}}>
                            <Selection selection={this.state.selection} allowUserSelect={this.state.allowUserSelect} onItemSelected={this.state.onItemSelected}></Selection>
                        </div>
                    </Col>
                </Row>
            </Container>
            {/* Render a Modal dynamically. */}
            {this.state.modalSettings != undefined ?
                <Modal
                    show={this.state.modalVisible}
                    backdrop={typeof this.state.modalSettings.backdrop !== undefined ? this.state.modalSettings.backdrop : 'static'}
                    keyboard={false}
                    size={this.state.modalSettings.size}
                    centered={this.state.modalSettings.centered || false}
                    onHide={this.state.modalSettings.onHide}
                    scrollable
                >
                    {
                        this.state.modalSettings.header ?
                            <Modal.Header>
                                <Modal.Title>{this.state.modalSettings.header}</Modal.Title>
                            </Modal.Header> :
                            ''
                    }

                    <Modal.Body>
                        {/* Render the dynamic Component */}
                        <DynamicRenderer component={this.state.modalSettings.content.component} props={this.state.modalSettings.content.props} />
                    </Modal.Body>
                </Modal>
                : ''}
            {/* Container for the Toasts */}
            <ToastContainer
                position="top-center"
                autoClose={5000}
                hideProgressBar={false}
                newestOnTop={false}
                closeOnClick
                rtl={false}
                pauseOnFocusLoss
                draggable
                pauseOnHover
            />
        </>);
    }
}

export default Layout;