define(["require", "jquery", "base/js/namespace", 'services/config',
    'base/js/events', 'base/js/utils', 'notebook/js/codecell', 'notebook/js/outputarea'
], function(require, $, Jupyter, configmod, events, utils, codecell, outputarea) {

    var Notebook = require('notebook/js/notebook').Notebook;
    "use strict";
    var mod_name = "dataclean";
    var log_prefix = '[' + mod_name + '] ';

    var n_dataframes = 0

    // ...........Parameters configuration......................
    // define default values for config parameters if they were not present in general settings (notebook.json)
    var cfg = {
    	'position' : {
    		top: '50px'
    	},
        'window_display': false,
        'python': {
            varRefreshCmd: (`try:
                print(_datacleaner.dataframe_metadata())
            except:
                print([])`)
        .replace(/^            /gm, '')
        },
    };

    //.....................global variables....


    var st = {};
    st.config_loaded = false;
    st.extension_initialized = false;

    function read_config(cfg, callback) { // read after nb is loaded
        // create config object to load parameters
        var config = Jupyter.notebook.config;
        config.loaded.then(function() {

            cfg = $.extend(true, cfg, config.data.datacleaner);
            // then update cfg with some vars found in current notebook metadata
            // and save in nb metadata (then can be modified per document)

            // window_display is taken from notebook metadata
            if (Jupyter.notebook.metadata.datacleaner) {
                if (Jupyter.notebook.metadata.datacleaner.window_display)
                    cfg.window_display = Jupyter.notebook.metadata.datacleaner.window_display;
               	if (Jupyter.notebook.metadata.datacleaner.position)
                    cfg.position = Jupyter.notebook.metadata.datacleaner.position;
            }

            cfg = Jupyter.notebook.metadata.datacleaner = $.extend(true,
            cfg, Jupyter.notebook.metadata.datacleaner);

            // but cols and kernels_config are taken from system (if defined)
            if (config.data.datacleaner) {
                if (config.data.datacleaner.kernels_config) {
                    cfg.kernels_config = $.extend(true, cfg.kernels_config, config.data.datacleaner.kernels_config);
                }
            }

            // call callbacks
            callback && callback();
            st.config_loaded = true;
        });
        config.load();
        return cfg;
    }

    function toggledatacleaner() {
        toggle_datacleaner(cfg, st);
    }

    var datacleaner_button = function() {
        if (!Jupyter.toolbar) {
            events.on("app_initialized.NotebookApp", datacleaner_button);
            return;
        }
        if ($("#datacleaner_button").length === 0) {
            Jupyter.toolbar.add_buttons_group([{
                'label': 'Data Cleaner',
                'icon': 'fa-bar-chart-o',
                'callback': toggledatacleaner,
                'id': 'datacleaner_button'
            }]);
        }

        require(['nbextensions/sherlockml-dataclean/iosbadge'],
        function() {
            if ($("#datacleaner_button").find('.iosb').length === 0) {
                $("#datacleaner_button").iosbadge({ theme: 'grey', size: 20 });
            }
            $("#datacleaner_button").find('.iosb').addClass('hidden');
        });
    };

    var load_css = function() {
        var link = document.createElement("link");
        link.type = "text/css";
        link.rel = "stylesheet";
        link.href = require.toUrl("./main.css");
        document.getElementsByTagName("head")[0].appendChild(link);
    };


    function html_table(jsonDataframes) {
        var dfList = JSON.parse(String(jsonDataframes));
        var table = '<div class="inspector">'
            +'<table class="'
                +'tablesorter tablesorter-default table '
                +'fixed table-condensed table-nonfluid ">'
            +'<col /><col  /><col /><thead><tr>'
            +'<th >Name</th><th >Shape</th><th >Columns</th>'
            +'</tr></thead>';
        n_dataframes = dfList.length;

        for (var i = 0; i < n_dataframes; i++) {
            table +=
                '<tr class="tablesorter-hasChildRow">'
                +'<td><a href="#" class="toggleDataframe" '
                +'data-frame-id="' + dfList[i].dfId + '">'
                + dfList[i].dfName + '</a></td><td>'
                + dfList[i].dfShape + '</td><td>'
                + dfList[i].dfColnames + '</td></tr>'
                + '<tr class=tablesorter-childRow>'
                + '<td colspan="3" class="'

                //remember if pipeline widget was hidden or shown
                if ($('#' + dfList[i].dfId + '_row').attr('class') === undefined){
                    table += 'pipeline_widget hidden';
                } else {
                    table += $('#' + dfList[i].dfId + '_row').attr('class');
                }

            table += '" id="' + dfList[i].dfId + '_row">'
                + 'Loading widget...</td></tr>'
                +'<tr class="tablesorter-childRow">'
                + '<td colspan="3" class="'

                //remember if sub-table was hidden or shown
                if ($('#table_' + dfList[i].dfId).attr('class') === undefined){
                    table += 'hidden';
                } else {
                    table += $('#table_' + dfList[i].dfId).attr('class');
                }

            table += '" id="table_' + dfList[i].dfId + '">'
                +'<table class="tablesorter'
                + '" width="100%" id="' + dfList[i].dfId + '">'
                +'<thead class="sorter-false"><tr>'
                + '<th>Column</th>'
                + '<th>Pandas dtype</th>'
                + '<th>Nulls</th>'
                + '<th># Distinct</th>'
                +'</tr></thead>'
                +'<tbody>';
            var n_cols = dfList[i].dfCols.length;
            for (var j = 0; j < n_cols; j++) {
                col = dfList[i].dfCols[j];
                table +=
                    '<tr class="tablesorter-hasChildRow">'
                    +'<td class="childColumn"><a href="#" class="toggleColumn" '
                    +'data-frame-id="' + dfList[i].dfId +'" '
                    +'id="' + col.colId + '">'
                    + col.colname + '</a></td><td>'
                    + col.description.dtype + '</td><td>'
                    + col.description.null_percentage + '</td><td>'
                    + col.description.distinct + '</td><td>'
                    + '<tr class="tablesorter-childRow"><td colspan="4"'
                    + 'id="' + col.colId + '_row" class="';

                //remember if colwidget was hidden or shown
                if ($('#'+col.colId+'_row').attr('class') === undefined){
                    table += 'hidden';
                } else {
                    table += $('#'+col.colId+"_row").attr('class');
                }

                table += '">Loading widget...</td></tr>';
            }
            table +=
                '</tbody>'
                +'</table>'
                +'</td></tr>';
        }
        var full_table = table + '</table></div>';

        return full_table;
    }

    function display_widgets(msg, output_wrapper) {

        if (msg.header.msg_type == 'display_data') {

            var output_area = new outputarea.OutputArea({
                config: Jupyter.notebook.config,
                selector: output_wrapper,
                prompt_area: false,
                events: Jupyter.notebook.events,
                keyboard_manager: Jupyter.notebook.keyboard_manager,
            });

            output_area.handle_output(msg);
        }

        if (msg.header.msg_type == 'error') {
            console.warn(log_prefix + msg.content.evalue);
            console.warn(log_prefix + msg.content.traceback);
        }
    }

    function display_column_widget(selector) {
        if($('#datacleaner-wrapper').is(':visible')){

            var dataframe_id = $(selector).attr('data-frame-id');

            var column_id = $(selector).attr('id');

            var col_output_wrapper;

            if ($('#'+column_id+'_widget').find('.output').length===0){
                col_output_wrapper = $('<div id="'+column_id+'_widget"></div>');
                $('#'+column_id+'_row').html(col_output_wrapper);

                Jupyter.notebook.kernel.execute('_datacleaner.dataframe_managers['+dataframe_id+'].column_widget('+column_id+')',
                {iopub: { output: function(msg){display_widgets(msg, col_output_wrapper)} } }, { silent: false });
            }
        }

    }

    function display_pipeline_widget(selector) {
        if($('#datacleaner-wrapper').is(':visible')){

            var dataframe_id = $(selector).attr('data-frame-id');

            var pipeline_output_wrapper;

            if ($('#'+dataframe_id+'_widget').find('.output').length===0){
                pipeline_output_wrapper = $('<div id="'+dataframe_id+'_widget"></div>');

                $('#'+dataframe_id+'_row').html(pipeline_output_wrapper);

                Jupyter.notebook.kernel.execute('_datacleaner.dataframe_managers['+dataframe_id+'].dataframe_widget',
                    {iopub: { output: function(msg){display_widgets(msg,pipeline_output_wrapper)} } }, { silent: false });
            }
        }
    }

    //runs after every code cell execution in case dataframes have been updated
    function code_exec_callback(msg) {
        if (msg.header.msg_type == 'stream') {
            var jsonDataframes = msg.content.text;
            if (jsonDataframes === undefined)
                datacleaner_init();
            else {
            	//redraw table
                $('#datacleaner').html(html_table(jsonDataframes));

                if (n_dataframes > 0) {
                    $("#datacleaner_button").iosbadge({content: n_dataframes});
                    $("#datacleaner_button").find('.iosb').removeClass('hidden');
                } else {
                    $("#datacleaner_button").find('.iosb').addClass('hidden');
                }

           		//add click events
                $('.tablesorter').delegate('.toggleColumn', 'click' ,function(){
                    $(this).closest('tr').nextUntil('tr:not(.tablesorter-childRow)').children('td').toggleClass('hidden');
                    display_column_widget(this)
                    return false;
                });

                $('.tablesorter').on('click', '.toggleDataframe' ,function(){
                    $(this).closest('tr').nextUntil('tr:not(.tablesorter-childRow)').children('td').toggleClass('hidden');
                    display_pipeline_widget(this)
                    return false;
                });

                //redisplay already open widgets
                $('.toggleColumn').each(function(){
                    if (!($(this).closest('tr').nextUntil('tr:not(.tablesorter-childRow)').children('td').hasClass('hidden'))){
                        display_column_widget(this)
                    }
                });

                $('.toggleDataframe').each(function(){
                    if (!($(this).closest('tr').next('tr').find('.pipeline_widget').hasClass('hidden'))){
                        display_pipeline_widget(this)
                    }
                });

            }
            require(['nbextensions/sherlockml-dataclean/jquery.tablesorter.min'],
            function() {
                setTimeout(function() { if ($('#datacleaner').length>0)
                $('#datacleaner table').tablesorter()}, 100);
            });
        }

        if (msg.header.msg_type == 'error') {
            console.warn(log_prefix + msg.content.evalue);
            console.warn(log_prefix + msg.content.traceback);
        }
    }

    var varRefresh = function() {
        require(['nbextensions/sherlockml-dataclean/jquery.tablesorter.min'],
            function() {
                Jupyter.notebook.kernel.execute(
                    cfg.python.varRefreshCmd, { iopub: { output: code_exec_callback } }, { silent: false }
                );
            });
    };


    var datacleaner_init = function() {

        cfg = read_config(cfg, function() {
            if (typeof Jupyter.notebook.kernel !== "undefined" && Jupyter.notebook.kernel !== null) {
                datacleaner_button();
            } else {
                console.warn(log_prefix + "Kernel not available?");
            }
        });

        data_cleaner(cfg, st);

        //CREATE DATACLEANER PYTHON OBJECT
        Jupyter.notebook.kernel.execute((
            `try:
                _datacleaner
            except NameError:
                from dataclean.manager import DataCleaner as _DataCleaner
                _datacleaner = _DataCleaner()`)
        .replace(/^            /gm, ''))

        events.on('execute.CodeCell', varRefresh);
        events.on('varRefresh', varRefresh);
    };


    var create_datacleaner_div = function(cfg, st) {
        function save_position(){
            Jupyter.notebook.metadata.datacleaner.position = {
                'left': $('#datacleaner-wrapper').css('left'),
                'top': $('#datacleaner-wrapper').css('top'),
                'width': $('#datacleaner-wrapper').css('width'),
                'height': $('#datacleaner-wrapper').css('height'),
                'right': $('#datacleaner-wrapper').css('right')
            };
        }
        var datacleaner_wrapper = $('<div id="datacleaner-wrapper"/>')
            .append(
                $('<div id="datacleaner-header"/>')
                .addClass("header")
                .text("Data Cleaner ")
                .append(
                    $("<a/>")
                    .attr("href", "#")
                    .text("[x]")
                    .addClass("kill-btn")
                    .attr('title', 'Close window')
                    .click(function() {
                    	save_position();
                        toggledatacleaner();
                        return false;
                    })
                )
                .append(
                    $("<a/>")
                    .attr("href", "#")
                    .addClass("hide-btn")
                    .attr('title', 'Hide Data Cleaner')
                    .text("[-]")
                    .click(function() {
                        $('#datacleaner-wrapper').css('position', 'fixed');
                        $('#datacleaner').slideToggle({
                            'complete': function() {
                                    Jupyter.notebook.metadata.datacleaner['datacleaner_section_display'] = $('#datacleaner').css('display');
                                    save_position();
                                    Jupyter.notebook.set_dirty();
                            }
                        });
                        $('#datacleaner-wrapper').toggleClass('closed');
                        if ($('#datacleaner-wrapper').hasClass('closed')) {
                            cfg.oldHeight = $('#datacleaner-wrapper').height(); //.css('height');
                            $('#datacleaner-wrapper').css({ height: 40 });
                            $('#datacleaner-wrapper .hide-btn')
                                .text('[+]')
                                .attr('title', 'Show Data Cleaner');
                        } else {
                            $('#datacleaner-wrapper').height(cfg.oldHeight); //css({ height: cfg.oldHeight });
                            $('#datacleaner').height(cfg.oldHeight - $('#datacleaner-header').height() - 30 )
                            $('#datacleaner-wrapper .hide-btn')
                                .text('[-]')
                                .attr('title', 'Hide Data Cleaner');
                        }
                        return false;
                    })
                ).append(
                    $("<a/>")
                    .attr("href", "#")
                    .text("  \u21BB")
                    .addClass("reload-btn")
                    .attr('title', 'Reload Data Cleaner')
                    .click(function() {
                        varRefresh();
                        return false;
                    })
                ).append(
                    $("<span/>")
                    .html("&nbsp;&nbsp")
                ).append(
                    $("<span/>")
                    .html("&nbsp;&nbsp;")
                )
            ).append(
                $("<div/>").attr("id", "datacleaner").addClass('datacleaner')
            )

        $("body").append(datacleaner_wrapper);
        // Ensure position is fixed
        $('#datacleaner-wrapper').css('position', 'fixed');

        // enable dragging and save position on stop moving
        $('#datacleaner-wrapper').draggable({
            handle:'#datacleaner-header',
            drag: function(event, ui) {}, //end of drag function
            start: function(event, ui) {
                $(this).width($(this).width());
            },
            stop: function(event, ui) { // on save, store window position
                $(this).offset({top:Math.max($(this).offset().top,0)});
                save_position();
                Jupyter.notebook.set_dirty();
                // Ensure position is fixed (again)
                $('#datacleaner-wrapper').css('position', 'fixed');
            },
        });

        $('#datacleaner-wrapper').resizable({
            resize: function(event, ui) {
                $('#datacleaner').height($('#datacleaner-wrapper').height() - $('#datacleaner-header').height());
            },
            start: function(event, ui) {
                $(this).css('position', 'fixed');
            },
            stop: function(event, ui) {
                    save_position();
                    $('#datacleaner').height($('#datacleaner-wrapper').height() - $('#datacleaner-header').height())
                    Jupyter.notebook.set_dirty();
            }
        })

        if (Jupyter.notebook.metadata.datacleaner !== undefined) {
            if (Jupyter.notebook.metadata.datacleaner.position !== undefined) {
                $('#datacleaner-wrapper').css(Jupyter.notebook.metadata.datacleaner.position);
            }
        }

        // Ensure position is fixed
        $('#datacleaner-wrapper').css('position', 'fixed');

        // Restore window display
            if (Jupyter.notebook.metadata.datacleaner !== undefined) {
                if (Jupyter.notebook.metadata.datacleaner['datacleaner_section_display'] !== undefined) {
                    $('#datacleaner').css('display', Jupyter.notebook.metadata.datacleaner['datacleaner_section_display'])
                    if (Jupyter.notebook.metadata.datacleaner['datacleaner_section_display'] == 'none') {
                        $('#datacleaner-wrapper').addClass('closed');
                        $('#datacleaner-wrapper').css({ height: 40 });
                        $('#datacleaner-wrapper .hide-btn')
                            .text('[+]')
                            .attr('title', 'Show Data Cleaner');
                    }
                }
                if (Jupyter.notebook.metadata.datacleaner['window_display'] !== undefined) {
                    console.log(log_prefix + "Restoring Data Cleaner window");
                    $('#datacleaner-wrapper').css('display','none');
                    if ($('#datacleaner-wrapper').hasClass('closed')){
                        $('#datacleaner').height(cfg.oldHeight - $('#datacleaner-header').height())
                    }else{
                        $('#datacleaner').height($('#datacleaner-wrapper').height() - $('#datacleaner-header').height()-30)
                    }

                }
            } else {
                $('#datacleaner-wrapper').toggle();
            }

        if ($('#datacleaner-wrapper').css('display') == undefined) $('#datacleaner-wrapper').css('display', "none") //block

        datacleaner_wrapper.addClass('datacleaner-float-wrapper');

    }

    var data_cleaner = function(cfg, st) {
        var datacleaner_wrapper = $("#datacleaner-wrapper");
        if (datacleaner_wrapper.length === 0) {
            create_datacleaner_div(cfg, st);
        }

        $(window).resize(function() {
            $('#datacleaner').css({ maxHeight: $(window).height() - 30 });
            $('#datacleaner-wrapper').css({ maxHeight: $(window).height() - 10 });
        });

        $(window).trigger('resize');
        varRefresh();
    };

    var toggle_datacleaner = function(cfg, st) {
        // toggle draw (first because of first-click behavior)
        $("#datacleaner-wrapper").toggle({
            'progress': function() {},
            'complete': function() {
                    Jupyter.notebook.metadata.datacleaner['window_display'] = $('#datacleaner-wrapper').css('display') == 'block';
                    Jupyter.notebook.set_dirty();
                // recompute:
                data_cleaner(cfg, st);
            }
        });
    };


    var load_jupyter_extension = function() {
        load_css();
        datacleaner_button();

        // If a kernel is available,
        if (typeof Jupyter.notebook.kernel !== "undefined" && Jupyter.notebook.kernel !== null) {
            datacleaner_init();
        }

        events.on("kernel_ready.Kernel", function(evt, data) {
            datacleaner_init();
        });

    };

    return {
        load_ipython_extension: load_jupyter_extension,
        varRefresh: varRefresh
    };

});
