class EasyAccess {
    constructor() {
        // Implement mouse drag with Ctrl and left button pressed to scroll.
        this.lastMouseClientX = 0;
        this.lastMouseClientY = 0;
        this.readyToScroll = false;
        this.scrolled = false;

        // Vi-like navigation.
        // Pending keys for keydown.
        this.pendingKeys = [];
        // The repeat token from user input.
        this.repeatToken =  0;

        window.vxcore.on('ready', () => {
            this.setupMouseMove();
            this.setupViNavigation();
            this.setupZoomOnWheel();
        });
    }

    setupMouseMove() {
        window.addEventListener('mousedown', (e) => {
            e = e || window.event;
            let isCtrl = window.vxcore.os === 'Mac' ? e.metaKey : e.ctrlKey;
            // Left button and Ctrl key.
            if (e.buttons == 1
                && isCtrl
                && window.getSelection().type != 'Range'
                && !window.vxImageViewer.isViewingImage()) {
                this.lastMouseClientX = e.clientX;
                this.lastMouseClientY = e.clientY;
                this.readyToScroll = true;
                this.scrolled = false;
                e.preventDefault();
            } else {
                this.readyToScroll = false;
                this.scrolled = false;
            }
        });

        window.addEventListener('mouseup', (e) => {
            e = e || window.event;
            if (this.scrolled || this.readyToScroll) {
                // Have been scrolled, restore the cursor style.
                document.body.style.cursor = "auto";
                e.preventDefault();
            }

            this.readyToScroll = false;
            this.scrolled = false;
        });

        window.addEventListener('mousemove', (e) => {
            e = e || window.event;
            if (this.readyToScroll) {
                let deltaX = e.clientX - this.lastMouseClientX;
                let deltaY = e.clientY - this.lastMouseClientY;

                let threshold = 5;
                if (Math.abs(deltaX) >= threshold || Math.abs(deltaY) >= threshold) {
                    this.lastMouseClientX = e.clientX;
                    this.lastMouseClientY = e.clientY;

                    if (!this.scrolled) {
                        this.scrolled = true;
                        document.body.style.cursor = "all-scroll";
                    }

                    let scrollX = -deltaX;
                    let scrollY = -deltaY;
                    window.scrollBy(scrollX, scrollY);
                }

                e.preventDefault();
            }
        });
    }

    setupViNavigation() {
        document.addEventListener('keydown', (e) => {
            // Need to clear pending kyes.
            let needClear = true;

            // This event has been handled completely. No need to call the default handler.
            let accepted = true;

            e = e || window.event;
            let key = null;
            let shift = null;
            let ctrl = null;
            let meta = null;
            if (e.which) {
                key = e.which;
            } else {
                key = e.keyCode;
            }

            shift = !!e.shiftKey;
            ctrl = !!e.ctrlKey;
            meta = !!e.metaKey;
            let isCtrl = window.vxcore.os === 'Mac' ? e.metaKey : e.ctrlKey;
            switch (key) {
            // Skip Ctrl, Shift, Alt, Supper.
            case 16:
            case 17:
            case 18:
            case 91:
            case 92:
                needClear = false;
                break;

            // 0 - 9.
            case 48:
            case 49:
            case 50:
            case 51:
            case 52:
            case 53:
            case 54:
            case 55:
            case 56:
            case 57:
            case 96:
            case 97:
            case 98:
            case 99:
            case 100:
            case 101:
            case 102:
            case 103:
            case 104:
            case 105:
            {
                if (this.pendingKeys.length != 0 || ctrl || shift || meta) {
                    accepted = false;
                    break;
                }

                let num = key >= 96 ? key - 96 : key - 48;
                this.repeatToken = this.repeatToken * 10 + num;
                needClear = false;
                break;
            }

            case 74: // J
                if (!shift && (!ctrl || isCtrl) && (!meta || isCtrl)) {
                    EasyAccess.scroll(true);
                    break;
                }

                accepted = false;
                break;

            case 75: // K
                if (!shift && (!ctrl || isCtrl) && (!meta || isCtrl)) {
                    EasyAccess.scroll(false);
                    break;
                }

                accepted = false;
                break;

            case 72: // H
                if (!ctrl && !shift && !meta) {
                    window.scrollBy(-100, 0);
                    break;
                }

                accepted = false;
                break;

            case 76: // L
                if (!ctrl && !shift && !meta) {
                    window.scrollBy(100, 0);
                    break;
                }

                accepted = false;
                break;

            case 71: // G
                if (shift) {
                    if (this.pendingKeys.length == 0) {
                        let scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft || window.pageXOffset;
                        let scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
                        window.scrollTo(scrollLeft, scrollHeight);
                        break;
                    }
                } else if (!ctrl && !meta) {
                    if (this.pendingKeys.length == 0) {
                        // First g, pend it.
                        this.pendingKeys.push({
                            key: key,
                            ctrl: ctrl,
                            shift: shift
                        });

                        needClear = false;
                        break;
                    } else if (this.pendingKeys.length == 1) {
                        let pendKey = this.pendingKeys[0];
                        if (pendKey.key == key && !pendKey.shift && !pendKey.ctrl) {
                            let scrollLeft = document.documentElement.scrollLeft
                                             || document.body.scrollLeft
                                             || window.pageXOffset;
                            window.scrollTo(scrollLeft, 0);
                            break;
                        }
                    }
                }

                accepted = false;
                break;

            case 85: // U
                if (ctrl) {
                    let clientHeight = document.documentElement.clientHeight;
                    window.scrollBy(0, -clientHeight / 2);
                    break;
                }

                accepted = false;
                break;

            case 68: // D
                if (ctrl) {
                    let clientHeight = document.documentElement.clientHeight;
                    window.scrollBy(0, clientHeight / 2);
                    break;
                }

                accepted = false;
                break;

            case 219: // [ or {
            {
                let repeat = this.repeatToken < 1 ? 1 : this.repeatToken;
                if (shift) {
                    // {
                    if (this.pendingKeys.length == 1) {
                        let pendKey = this.pendingKeys[0];
                        if (pendKey.key == key && !pendKey.shift && !pendKey.ctrl) {
                            // [{, jump to previous title at a higher level.
                            this.jumpTitle(false, -1, repeat);
                            break;
                        }
                    }
                } else if (!ctrl && !meta) {
                    // [
                    if (this.pendingKeys.length == 0) {
                        // First [, pend it.
                        this.pendingKeys.push({
                            key: key,
                            ctrl: ctrl,
                            shift: shift
                        });

                        needClear = false;
                        break;
                    } else if (this.pendingKeys.length == 1) {
                        let pendKey = this.pendingKeys[0];
                        if (pendKey.key == key && !pendKey.shift && !pendKey.ctrl) {
                            // [[, jump to previous title.
                            this.jumpTitle(false, 1, repeat);
                            break;
                        } else if (pendKey.key == 221 && !pendKey.shift && !pendKey.ctrl) {
                            // ][, jump to next title at the same level.
                            this.jumpTitle(true, 0, repeat);
                            break;
                        }
                    }
                }

                accepted = false;
                break;
            }

            case 221: // ] or }
            {
                let repeat = this.repeatToken < 1 ? 1 : this.repeatToken;
                if (shift) {
                    // }
                    if (this.pendingKeys.length == 1) {
                        let pendKey = this.pendingKeys[0];
                        if (pendKey.key == key && !pendKey.shift && !pendKey.ctrl) {
                            // ]}, jump to next title at a higher level.
                            this.jumpTitle(true, -1, repeat);
                            break;
                        }
                    }
                } else if (!ctrl && !meta) {
                    // ]
                    if (this.pendingKeys.length == 0) {
                        // First ], pend it.
                        this.pendingKeys.push({
                            key: key,
                            ctrl: ctrl,
                            shift: shift
                        });

                        needClear = false;
                        break;
                    } else if (this.pendingKeys.length == 1) {
                        let pendKey = this.pendingKeys[0];
                        if (pendKey.key == key && !pendKey.shift && !pendKey.ctrl) {
                            // ]], jump to next title.
                            this.jumpTitle(true, 1, repeat);
                            break;
                        } else if (pendKey.key == 219 && !pendKey.shift && !pendKey.ctrl) {
                            // [], jump to previous title at the same level.
                            this.jumpTitle(false, 0, repeat);
                            break;
                        }
                    }
                }

                accepted = false;
                break;
            }

            default:
                accepted = false;
                break;
            }

            if (needClear) {
                this.repeatToken = 0;
                this.pendingKeys = [];
            }

            if (accepted) {
                e.preventDefault();
            } else {
                window.vxcore.setKeyPress(key, ctrl, shift, meta);
            }
        });
    }

    // @forward: jump forward or backward.
    // @relativeLevel: 0 for the same level as current header;
    //                 negative value for upper level;
    //                 positive value is ignored.
    jumpTitle(forward, relativeLevel, repeat) {
        let headings = window.vxcore.nodeLineMapper.headingNodes;
        if (headings.length == 0) {
            return;
        }

        let currentHeadingIdx = window.vxcore.nodeLineMapper.currentHeadingIndex();
        if (currentHeadingIdx == -1) {
            // At the beginning, before any headings.
            if (relativeLevel < 0 || !forward) {
                return;
            }
        }

        let targetIdx = -1;
        // -1: skip level check.
        let targetLevel = 0;

        let delta = 1;
        if (!forward) {
            delta = -1;
        }

        let scrollTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset;
        for (targetIdx = (currentHeadingIdx == -1 ? 0 : currentHeadingIdx);
             targetIdx >= 0 && targetIdx < headings.length;
             targetIdx += delta) {
            let level = parseInt(headings[targetIdx].tagName.substr(1));
            if (targetLevel == 0) {
                targetLevel = level;
                if (relativeLevel < 0) {
                    targetLevel += relativeLevel;
                    if (targetLevel < 1) {
                        // Invalid level.
                        return;
                    }
                } else if (relativeLevel > 0) {
                    targetLevel = -1;
                }
            }

            if (targetLevel == -1 || level == targetLevel) {
                if (targetIdx == currentHeadingIdx) {
                    // If current heading is visible, skip it.
                    // Minus 2 to tolerate some margin.
                    if (forward || scrollTop  - 2 <= headings[targetIdx].offsetTop) {
                        continue;
                    }
                }

                if (--repeat == 0) {
                    break;
                }
            } else if (level < targetLevel) {
                return;
            }
        }

        if (targetIdx < 0 || targetIdx >= headings.length) {
            return;
        }

        window.vxcore.nodeLineMapper.scrollToNode(headings[targetIdx], false, false);
        window.setTimeout(function() {
            window.vxcore.nodeLineMapper.updateCurrentHeading();
        }, 1000);
    };

    setupZoomOnWheel() {
        window.addEventListener('wheel', (e) => {
            e = e || window.event;
            let isCtrl = window.vxcore.os === 'Mac' ? e.metaKey : e.ctrlKey;
            if (isCtrl) {
                if (e.deltaY != 0) {
                    window.vxcore.zoom(e.deltaY < 0);
                }
                e.preventDefault();
            }
        });
    }

    static scroll(p_up) {
        let delta = 100;
        if (p_up) {
            window.scrollBy(0, delta);
        } else {
            window.scrollBy(0, -delta);
        }
    }
}

window.vxEasyAccess = new EasyAccess;
