//
// Utility functions for the HTML notebook's CasperJS tests.
//

// Get the URL of a notebook server on which to run tests.
casper.get_notebook_server = function () {
    port = casper.cli.get("port")
    port = (typeof port === 'undefined') ? '8888' : port;
    return 'http://127.0.0.1:' + port
};

// Create and open a new notebook.
casper.open_new_notebook = function () {
    var baseUrl = this.get_notebook_server();
    this.start(baseUrl);
    this.thenClick('button#new_notebook');
    this.waitForPopup('');

    this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
    this.then(function () {
        this.open(this.popups[0].url);
    });

    // Make sure the kernel has started
    this.waitFor( this.kernel_running  );
    // track the IPython busy/idle state
    this.thenEvaluate(function () {
        $([IPython.events]).on('status_idle.Kernel',function () {
            IPython._status = 'idle';
        });
        $([IPython.events]).on('status_busy.Kernel',function () {
            IPython._status = 'busy';
        });
    });
};

// Return whether or not the kernel is running.
casper.kernel_running = function kernel_running() {
    return this.evaluate(function kernel_running() {
        return IPython.notebook.kernel.running;
    });
};

// Shut down the current notebook's kernel.
casper.shutdown_current_kernel = function () {
    this.thenEvaluate(function() {
        IPython.notebook.kernel.kill();
    });
    // We close the page right after this so we need to give it time to complete.
    this.wait(1000);
};

// Delete created notebook.
casper.delete_current_notebook = function () {
    // For some unknown reason, this doesn't work?!?
    this.thenEvaluate(function() {
        IPython.notebook.delete();
    });
};

casper.wait_for_busy = function () {
    this.waitFor(function () {
        return this.evaluate(function () {
            return IPython._status == 'busy';
        });
    });
};

casper.wait_for_idle = function () {
    this.waitFor(function () {
        return this.evaluate(function () {
            return IPython._status == 'idle';
        });
    });
};

// wait for the nth output in a given cell
casper.wait_for_output = function (cell_num, out_num) {
    this.wait_for_idle();
    out_num = out_num || 0;
    this.then(function() {
        this.waitFor(function (c, o) {
            return this.evaluate(function get_output(c, o) {
                var cell = IPython.notebook.get_cell(c);
                return cell.output_area.outputs.length > o;
            },
            // pass parameter from the test suite js to the browser code js
            {c : cell_num, o : out_num});
        });
    },
    function then() { },
    function timeout() {
        this.echo("wait_for_output timed out!");
    });
};

// wait for a widget msg que to reach 0
//
// Parameters
// ----------
// widget_info : object
//      Object which contains info related to the widget.  The model_id property
//      is used to identify the widget.
casper.wait_for_widget = function (widget_info) {
    this.waitFor(function () {
        var pending = this.evaluate(function (m) {
            return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
        }, {m: widget_info.model_id});

        if (pending == 0) {
            return true;
        } else {
            return false;
        }
    });
}

// return an output of a given cell
casper.get_output_cell = function (cell_num, out_num) {
    out_num = out_num || 0;
    var result = casper.evaluate(function (c, o) {
        var cell = IPython.notebook.get_cell(c);
        return cell.output_area.outputs[o];
    },
    {c : cell_num, o : out_num});
    if (!result) {
        var num_outputs = casper.evaluate(function (c) {
            var cell = IPython.notebook.get_cell(c);
            return cell.output_area.outputs.length;
        },
        {c : cell_num});
        this.test.assertTrue(false,
            "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
        );
    } else {
        return result;
    }
};

// return the number of cells in the notebook
casper.get_cells_length = function () {
    var result = casper.evaluate(function () {
        return IPython.notebook.get_cells().length;
    })
    return result;
};

// Set the text content of a cell.
casper.set_cell_text = function(index, text){
    this.evaluate(function (index, text) {
        var cell = IPython.notebook.get_cell(index);
        cell.set_text(text);
    }, index, text);
};

// Inserts a cell at the bottom of the notebook
// Returns the new cell's index.
casper.insert_cell_at_bottom = function(cell_type){
    cell_type = cell_type || 'code';

    return this.evaluate(function (cell_type) {
        var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
        return IPython.notebook.find_cell_index(cell);
    }, cell_type);
};

// Insert a cell at the bottom of the notebook and set the cells text.
// Returns the new cell's index.
casper.append_cell = function(text, cell_type) { 
    var index = this.insert_cell_at_bottom(cell_type);
    if (text !== undefined) {
        this.set_cell_text(index, text);
    }
    return index;
};

// Asynchronously executes a cell by index.
// Returns the cell's index.
casper.execute_cell = function(index){
    var that = this;
    this.then(function(){
        that.evaluate(function (index) {
            var cell = IPython.notebook.get_cell(index);
            cell.execute();
        }, index);
    });
    return index;
};

// Synchronously executes a cell by index.
// Optionally accepts a then_callback parameter.  then_callback will get called
// when the cell  has finished executing.
// Returns the cell's index.
casper.execute_cell_then = function(index, then_callback) {
    var return_val = this.execute_cell(index);

    this.wait_for_idle();

    var that = this;
    this.then(function(){ 
        if (then_callback!==undefined) {
            then_callback.apply(that, [index]);
        }
    });        

    return return_val;
};

// Utility function that allows us to easily check if an element exists 
// within a cell.  Uses JQuery selector to look for the element.
casper.cell_element_exists = function(index, selector){
    return casper.evaluate(function (index, selector) {
        var $cell = IPython.notebook.get_cell(index).element;
        return $cell.find(selector).length > 0;
    }, index, selector);
};

// Utility function that allows us to execute a jQuery function on an 
// element within a cell.
casper.cell_element_function = function(index, selector, function_name, function_args){
    return casper.evaluate(function (index, selector, function_name, function_args) {
        var $cell = IPython.notebook.get_cell(index).element;
        var $el = $cell.find(selector);
        return $el[function_name].apply($el, function_args);
    }, index, selector, function_name, function_args);
};

// Wrap a notebook test to reduce boilerplate.
casper.notebook_test = function(test) {
    this.open_new_notebook();
    this.then(test);

    // Kill the kernel and delete the notebook.
    this.shutdown_current_kernel();
    // This is still broken but shouldn't be a problem for now.
    // this.delete_current_notebook();
    
    // This is required to clean up the page we just finished with. If we don't call this
    // casperjs will leak file descriptors of all the open WebSockets in that page. We
    // have to set this.page=null so that next time casper.start runs, it will create a
    // new page from scratch.
    this.then(function () {
        this.page.close();
        this.page = null;
    });
    
    // Run the browser automation.
    this.run(function() {
        this.test.done();
    });
};

casper.wait_for_dashboard = function () {
    // Wait for the dashboard list to load.
    casper.waitForSelector('.list_item');
}

casper.open_dashboard = function () {
    // Start casper by opening the dashboard page.
    var baseUrl = this.get_notebook_server();
    this.start(baseUrl);
    this.wait_for_dashboard();
}

casper.dashboard_test = function (test) {
    // Open the dashboard page and run a test.
    this.open_dashboard();
    this.then(test);

    this.then(function () {
        this.page.close();
        this.page = null;
    });
    
    // Run the browser automation.
    this.run(function() {
        this.test.done();
    });
}

casper.options.waitTimeout=10000
casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
    this.echo("Timeout for " + casper.get_notebook_server());
    this.echo("Is the notebook server running?");
});

// Pass `console.log` calls from page JS to casper.
casper.printLog = function () {
    this.on('remote.message', function(msg) {
        this.echo('Remote message caught: ' + msg);
    });
};