/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

const socket = io('/recorder');

function getNthChild(el) {
    let i = 1;
    let elTagName = el.tagName;
    let elClassName = el.className;
    let nthSameClass = 1;
    while (el.previousSibling) {
        el = el.previousSibling;
        if (el.tagName === elTagName && el.className === elClassName) {
            nthSameClass++;
        }
        i++;
    }
    return [i, nthSameClass];
}
function getUniqueSelector(el) {
    if (el.tagName.toLowerCase() === 'body') {
        return '';
    }
    let selector = '';
    if (el.id) {
        // id has highest priority.
        return el.id;
    }
    else {
        selector = el.tagName.toLowerCase();
        for (let className of el.classList) {
            selector += '.' + className;
        }
        let [idx, nthSameClass] = getNthChild(el);
        if (nthSameClass > 1) {
            selector += `:nth-child(${idx})`;
        }
    }
    let parentSelector = el.parentNode && getUniqueSelector(el.parentNode);
    if (parentSelector) {
        selector = parentSelector + '>' + selector;
    }
    return selector;
}

const app = new Vue({
    el: '#app',
    data: {
        tests: [],

        currentTestName: '',
        actions: [],
        currentAction: null,
        recordingAction: null,

        recordingTimeElapsed: 0,

        config: {
            screenshotAfterMouseUp: true,
            screenshotDelay: 400
        },

        drawerVisible: true
    },
    computed: {
        url() {
            if (!this.currentTestName) {
                return '';
            }
            return window.location.origin + '/test/' + this.currentTestName + '.html';
        }
    },
    methods: {
        refreshPage() {
            const $iframe = getIframe();
            if ($iframe.contentWindow) {
                $iframe.contentWindow.location.reload();
            }
        },
        newAction() {
            this.currentAction = {
                name: 'Action ' + (this.actions.length + 1),
                ops: []
            };
            this.actions.push(this.currentAction);
        },
        select(actionName) {
            this.currentAction = this.actions.find(action => {
                return action.name === actionName;
            });
            if (this.currentAction) {
                const $iframe = getIframe();
                if ($iframe.contentWindow) {
                    $iframe.contentWindow.scrollTo({
                        left: this.currentAction.scrollX,
                        top: this.currentAction.scrollY,
                        behavior: 'smooth'
                    });
                }
            }
        },

        doDelete(actionName) {
            app.$confirm('Aure you sure?', 'Delete this action', {
                confirmButtonText: 'Yes',
                cancelButtonText: 'No',
                type: 'warning'
            }).then(() => {
                this.deletePopoverVisible = false;
                let idx = _.findIndex(this.actions, action => action.name === actionName);
                if (idx >= 0) {
                    if (this.currentAction === this.actions[idx]) {
                        this.currentAction = this.actions[idx + 1] || this.actions[idx - 1];
                    }
                    this.actions.splice(idx, 1);
                    saveData();
                }
            }).catch(e => {});
        },

        clearOps(actionName) {
            app.$confirm('Aure you sure?', 'Clear this action', {
                confirmButtonText: 'Yes',
                cancelButtonText: 'No',
                type: 'warning'
            }).then(() => {
                this.deletePopoverVisible = false;
                let action = this.actions.find(action => action.name === actionName);
                if (action) {
                    action.ops = [];
                }
                saveData();
            }).catch(e => {});
        },

        run() {
            socket.emit('runSingle', {
                testName: app.currentTestName
            });
        }
    }
});

let time = Date.now();
function updateTime() {
    let dTime = Date.now() - time;
    time += dTime;
    if (app.recordingAction) {
        app.recordingTimeElapsed += dTime;
    }
    requestAnimationFrame(updateTime);
}
requestAnimationFrame(updateTime);

function getIframe() {
    return document.body.querySelector('#test-view');
}

function saveData() {
    // Save
    if (app.currentTestName) {
        socket.emit('saveActions', {
            testName: app.currentTestName,
            actions: app.actions
        });

        let test = app.tests.find(testOpt => testOpt.name === app.currentTestName);
        test.actions = app.actions.length;
    }
}

function getEventTime() {
    return Date.now() - app.recordingAction.timestamp;
}
function notify(title, message) {
    app.$notify.info({
        title,
        message,
        position: 'top-left',
        customClass: 'op-notice'
    });
}

function keyboardRecordingHandler(e) {
    if (e.keyCode === 82 && e.altKey) {
        let $iframe = getIframe();
        if (!app.recordingAction) {
            // Create a new action if currentAction has ops.
            if (!app.currentAction || app.currentAction.ops.length > 0) {
                app.newAction();
            }

            app.recordingAction = app.currentAction;
            if (app.recordingAction) {
                app.recordingAction.scrollY = $iframe.contentWindow.scrollY;
                app.recordingAction.scrollX = $iframe.contentWindow.scrollX;
                app.recordingAction.timestamp = Date.now();

                app.recordingTimeElapsed = 0;
            }
        }
        else {
            if (app.recordingAction &&
                (app.recordingAction.scrollY !== $iframe.contentWindow.scrollY
             || app.recordingAction.scrollX !== $iframe.contentWindow.scrollX)) {
                app.recordingAction.ops = [];
                app.$alert('You can\'t scroll the page during the action recording. Please create another action after scrolled to the next demo.', 'Recording Fail', {
                    confirmButtonText: 'Get!'
                });

            }
            else {
                saveData();
            }
            app.recordingAction = null;
        }
        // Get scroll
    }
    else if (e.keyCode === 83 && e.altKey) {
        if (app.recordingAction) {
            app.recordingAction.ops.push({
                type: 'screenshot',
                time: getEventTime()
            });
            notify('screenshot', '');
        }
    }
}

function sign(value) {
    return value > 0 ? 1 : -1;
}

function recordIframeEvents(iframe, app) {
    let innerDocument = iframe.contentWindow.document;


    function addMouseOp(type, e) {
        if (app.recordingAction) {
            let time = getEventTime();
            let op = {
                type,
                time: time,
                x: e.clientX,
                y: e.clientY
            };
            app.recordingAction.ops.push(op);
            if (type === 'mousewheel') {
                // TODO Sreenshot after mousewheel?
                op.deltaY = e.deltaY;

                // In a reversed direction.
                // When creating WheelEvent, the sign of wheelData and deltaY are same
                if (sign(e.wheelDelta) !== sign(e.deltaY)) {
                    op.deltaY = -op.deltaY;
                }
            }
            if (type === 'mouseup' && app.config.screenshotAfterMouseUp) {
                // Add a auto screenshot after mouseup
                app.recordingAction.ops.push({
                    time: time + 1,
                    delay: app.config.screenshotDelay,
                    type: 'screenshot-auto'
                });
            }
            notify(type, `(x: ${e.clientX}, y: ${e.clientY})`);
        }
    }

    innerDocument.addEventListener('keyup', keyboardRecordingHandler);

    let preventRecordingFollowingMouseEvents = false;
    innerDocument.body.addEventListener('mousemove', _.throttle(e => {
        if (!preventRecordingFollowingMouseEvents) {
            addMouseOp('mousemove', e);
        }
    }, 200), true);
    innerDocument.body.addEventListener('mousedown', e => {
        // Can't recording mouse event on select.
        // So just prevent it and add a specific 'select' change event.
        if (e.target.tagName.toLowerCase() === 'select') {
            preventRecordingFollowingMouseEvents = true;
            return;
        }
        addMouseOp('mousedown', e);
    }, true);
    innerDocument.body.addEventListener('mouseup', e => {
        if (!preventRecordingFollowingMouseEvents) {
            addMouseOp('mouseup', e);
        }
        preventRecordingFollowingMouseEvents = false;
    }, true);
    iframe.contentWindow.addEventListener('mousewheel', e => {
        addMouseOp('mousewheel', e);
    }, true);


    innerDocument.body.addEventListener('change', e => {
        if (app.recordingAction) {
            let selector = getUniqueSelector(e.target);
            let time = getEventTime();
            let commonData = {
                type: 'valuechange',
                selector,
                value: e.target.value,
                time: time
            };
            if (e.target.tagName.toLowerCase() === 'select') {
                commonData.target = 'select';
                notify('valuechange', `select(${commonData.value})`);
            }
            if (commonData.target) {
                app.recordingAction.ops.push(commonData);

                if (app.config.screenshotAfterMouseUp) {
                    // Add a auto screenshot after mouseup
                    app.recordingAction.ops.push({
                        time: time + 1,
                        delay: app.config.screenshotDelay,
                        type: 'screenshot-auto'
                    });
                }
            }
        }
    });
}


function init() {
    app.$el.style.display = 'block';

    document.addEventListener('keyup', keyboardRecordingHandler);

    socket.on('updateActions', data => {
        if (data.testName === app.currentTestName) {
            app.actions = data.actions;
            if (!app.currentAction) {
                app.currentAction = app.actions[0];
            }
        }
    });
    socket.on('getTests', ({tests}) => {
        app.tests = tests;
    });

    let $iframe = getIframe();
    $iframe.onload = () => {
        recordIframeEvents($iframe, app);
    };

    function updateTestHash() {
        app.currentTestName = window.location.hash.slice(1);
        // Reset
        app.actions = [];
        app.currentAction = null;
        app.recordingAction = null;

        socket.emit('changeTest', {testName: app.currentTestName});

    }
    updateTestHash();
    window.addEventListener('hashchange', updateTestHash);
}

socket.on('connect', () => {
    console.log('Connected');
    init();
});