text:
var tbl_payment_schedule_list; //Bos dens, nag global nako para mabilis.. hehe
var tbl_customer_ledger;
var selected_account_id;
var tbl_apprehended_consumer_list;
var tbl_deliquent_list;

var tbl_item_cart;

/**
 * returns specific dom element on item cart table
 * @type {{id: string, body: string, footer: string}}
 */
var oTable={
    "id"        :   "#tbl_item_cart",
    "body"      :   "#tbl_item_cart > tbody",
    "footer"    :   "#tbl_item_cart > tfoot"
};
/**
 * cell index of item cart details
 * @type {{desc: string, kwh: string, qty: string, hrs: string, total: string, action: string}}
 */
var oTBody={
    "desc"          :   "td:eq(0)",
    "kwh"           :   "td:eq(1)",
    "qty"           :   "td:eq(2)",
    "hrs"           :   "td:eq(3)",
    "total"         :   "td:eq(4)",
    "action"        :   "td:eq(5)"
};

/**
 * cell index of item cart list footer
 * @type {{amount: string, dp: string, days: string}}
 */
var oTFoot={
    "amount"        :   "tr:eq(0) > td:eq(1)",
    "totalKWH"      :   "tr:eq(0) > td:eq(3)",
    "dp"            :   "tr:eq(1) > td:eq(1)",
    "days"          :   "tr:eq(1) > td:eq(3)",
    "net"           :   "tr:eq(2) > td:eq(1)"
};

$(document).ready(function(){
    /**********************************************************************************************************************************************************/
    //consumer apprehended list module and table
    var apprehendedConsumerModule=(function(){

        var bindEventHandlers=(function(){
            /**
             *
             *	fires when edit invoice button on selected row is clicked
             *
             */
            $('#tbl_apprehended_consumer_list tbody').on('dblclick','tr',function(){
                $(this).find('button[name="edit_account"]').click();
            });

            $('#tbl_apprehended_consumer_list tbody').on('click','button[name="edit_account"]',function(){
                var row=$(this).closest('tr');

                apprehendedInfoModalModule.setMode("edit"); //set mode to editing
                //alert(row.find('td:eq(0) input[type="checkbox"]').val());
                apprehendedInfoModalModule.setSelectedID(row.find('td:eq(0) input[type="checkbox"]').val()); //what is the id of the invoice we are going to update
                apprehendedInfoModalModule.setConsumerID(row.find('td').eq(3).attr('data-consumer-id')); //what is the id of the invoice we are going to update
                apprehendedInfoModalModule.setSelectedRow(row); //remember the row we are going to update

                //object details of modal
                apprehendedInfoModalModule.setDetails({
                    "bill_account_id"	:		row.find('td:eq(0) input[type="checkbox"]').val(),
                    "house_no"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-house-no'),
                    "street_no"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-street-no'),
                    "barangay"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-barangay'),
                    "municipality"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-municipality'),
                    "zip_code"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-zip-code'),
                    "province"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-province'),
                    "email"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-email'),
                    "down_payment"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-down-payment'),
                    "no_of_days"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-no-of-days'),
                    "payment_start_date"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-payment-start-date'),
                    "duration"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-duration'),
                    "payment_schedule_remarks"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-payment-schedule-remarks'),

                    "amount_kwh"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-amount-kwh'),

                    /***
                     * WRONG ACCESS NAME!
                     * ***/
                    //"total_back_bill_amount"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-total-bill-amount'),
                    "total_back_bill_amount"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-total-back-bill_amount'),
                    "total_account_kwh"	:		row.find('td:eq(0) input[type="checkbox"]').attr('data-total-account-kwh'),
                    "reference_no"	    :		row.find('td').eq(1).text(),
                    "account_no"		    :		row.find('td').eq(2).text(),
                    "consumer_id"	    :		row.find('td').eq(3).attr('data-consumer-id'),
                    "consumer_name"		    :		row.find('td').eq(3).text(),
                    "contact_no"		    :		row.find('td').eq(4).text()
                });

                //show invoice info modal
                apprehendedInfoModalModule.showModal();

            });

            $('#tbl_apprehended_consumer_list tbody').on('click','tr',function(){

                highlightRow(this);

                var row=$(this);
                var checbox= row.find('td:eq(0) input[type="checkbox"]');
                selected_account_id=checbox.val();
                showPaymentSchedule(selected_account_id);
                showConsumerLedger();

                $('#cell_reference_no').text(
                    row.find('td').eq(1).text()
                );

                $('#cell_account_no').text(
                    row.find('td').eq(2).text()
                );

                $('#cell_consumer').text(
                    row.find('td').eq(3).text()
                );

                $('#cell_total_amount').text(
                    row.find('td').eq(6).text()
                );

                $('#cell_start_date').text(
                    row.find('td:eq(0) input[type="checkbox"]').attr('data-payment-start-date')
                );

                $('#cell_duration').text(
                    row.find('td:eq(0) input[type="checkbox"]').attr('data-duration')+" month(s)"
                );

                $('#btn_print_ledger,#print_schedule').prop('disabled',false);

            });

        })();

        var showPaymentSchedule=function(account_id){
            $('a[href="#tab-2"]').html("  [ Loading details...  ]" );

            $('#tbl_payment_schedule_list tbody').html('

');

            $.getJSON('ApprehendedConsumerController/ActionGetCurrentAccountSchedule',{"id":account_id}, function(response){
                //tbl_apprehended_consumer_list.clear().draw(); //make sure apprehended consumer datatable has no rows
                //console.log(response);
                var total=0;
                tbl_payment_schedule_list.clear().draw(); //to make sure old rows are cleared

                $.each(response,function(index,value){
                    tbl_payment_schedule_list.row.add([
                            value.sched_payment_date,
                            value.bill_description,
                            accounting.formatNumber(value.due_amount,2)
                    ]).draw();

                    total+=parseFloat(value.due_amount);

                });

                $('#cell_total_back_bill_amount').html('
'+accounting.formatNumber(total,2)+'
');
                var account_no=$('#tbl_apprehended_consumer_list > tbody tr.active').find('td:eq(2)').text();
                $('a[href="#tab-2"]').html(" Account Info [ "+account_no+" ]");

                //$('a[href="#tab-2"]').html("Account Info");

            });
        };

        var highlightRow=function(row){
            $(row).siblings()
                .removeClass('active')
                .find('td:eq(0) input[type="checkbox"]')
                .prop('checked',false); //remove highlights of other rows

            $(row).attr('class','active')
                .find('td:eq(0) input[type="checkbox"]')
                .prop('checked',true); //highlight the row that fires the event
        };

        var initializeApprehendedConsumerDatatable=(function(){
            tbl_apprehended_consumer_list=$('#tbl_apprehended_consumer_list').DataTable({
                "bLengthChange":false,
                "order": [[ 0, "desc" ]],
                "oLanguage": {
                    "sSearch": "Search: ",
                    "sProcessing": "Please wait..."
                },
                "dom": '<"toolbar">frtip',
                "columnDefs": [
                    {//column 1
                        'bSortable': false,
                        'targets': [0],
                        'render': function(data, type, full, meta){
                            var _arrData=data.split('|');
                            return '<input type="checkbox" value="'+_arrData[0]+'"' +
                            'data-consumer-id="'+_arrData[1]+'"' +
                            'data-house-no="'+_arrData[2]+'"' +
                            'data-street-no="'+_arrData[3]+'"' +
                            'data-barangay="'+_arrData[4]+'"' +
                            'data-municipality="'+_arrData[5]+'"' +
                            'data-zip-code="'+_arrData[6]+'"' +
                            'data-province="'+_arrData[7]+'"' +
                            'data-email="'+_arrData[8]+'"' +
                            'data-down-payment="'+_arrData[9]+'"' +
                            'data-no-of-days="'+_arrData[10]+'"' +
                            'data-payment-start-date="'+_arrData[11]+'"' +
                            'data-duration="'+_arrData[12]+'"' +
                            'data-payment-schedule-remarks="'+_arrData[13]+'"' +
                            'data-amount-kwh="'+_arrData[14]+'"' +
                            'data-total-back-bill_amount="'+_arrData[15]+'"' +
                            'data-total-account-kwh="'+_arrData[16]+'"' +
                            '>';
                        }
                    },//column 1
                    {//column 3

                        'bSortable': false,
                        'targets': [3],
                        'render': function(data, type, full, meta){
                            return data.split('|')[1];
                        }
                    },//column 3,

                  {//column 7

                        'bSortable': false,
                        'targets': [8],
                        'render': function(data, type, full, meta){
                            var btn_edit='<button name="edit_account" class="btn btn-default btn-sm" style="margin-left:-15px;" data-toggle="tooltip" data-placement="top" title="Adjust Invoice"> </button>';
                            var btn_trash='<button name="remove_account" class="btn btn-default btn-sm" style="margin-right:-15px;" data-toggle="tooltip" data-placement="top" title="Move to trash"> </button>';

                            return ''+btn_edit+btn_trash+'';
                        }
                    }//column 7
                ],
                "rowCallback":function( row, data, index ){

                    $('td:eq(6),td:eq(7)',row).attr({
                        "align":"right"
                    });

                    $(row).find('td').eq(3).attr({
                        "data-consumer-id": data[3].split('|')[0]
                    });
                }

            });

        })();

        var showApprehendedConsumerList=function(){
            $('#tbl_apprehended_consumer_list tbody').html('

');
            $.getJSON('ApprehendedConsumerController/ActionGetApprehendedConsumerList', function(response){
                tbl_apprehended_consumer_list.clear().draw(); //make sure apprehended consumer datatable has no rows
                //console.log(response);
                $.each(response,function(index,value){
                    tbl_apprehended_consumer_list.row.add([
                        value.record_info,
                        value.reference_no,
                        value.account_no,
                        value.consumer,
                        value.contact_no,
                        value.period,
                        accounting.formatNumber(value.total_back_bill_amount,2),
                        accounting.formatNumber(value.TotalBalance,2),
                        ""
                    ]).draw();
                });

            });
        };

        var lastPage=function(){
            $('#tbl_apprehended_consumer_list ul li:nth-last-child(2) a').click(); //trigger 2nd to the last link, the last page number
        };

        var addRow=function(data){
            tbl_apprehended_consumer_list
                .row
                .add(data)
                .draw();
        };

        //create toolbar buttons
        var createToolBarButton=function(_buttons){
            $("div.toolbar").html(_buttons);
        };

        //get the invoice table object instance
        var getTableInstance=function(){
            return tbl_apprehended_consumer_list;
        };

        //update row
        var updateRow=function(row,data){
            tbl_apprehended_consumer_list
                .row(row)
                .data(data)
                .draw();
        };

        //return objects as functions
        return {
            getTableInstance            : 	getTableInstance,
            createToolBarButton         : 	createToolBarButton,
            showApprehendedConsumerList :   showApprehendedConsumerList,
            addRow                      : 	addRow,
            updateRow                   :	updateRow,
            lastPage                    : 	lastPage
        };

    })();
    /**********************************************************************************************************************************************************/

    var dtDeliquentList=(function(){

        var bindEventHandlers=function(){
            $(document).on('click','#btn_refresh_deliquent',function(){
                showDelinquentList();
            });
        }();

        var initializeCustomerLedger=(function(){
            tbl_deliquent_list=$('#tbl_deliquent_list').DataTable({
                "bLengthChange": false,
                "bPaginate":false,
                "dom": '<"print_deliquent">frtip',
                "rowCallback":function( row, data, index ){
                    $('td:eq(2),td:eq(3),td:eq(4),td:eq(5),td:eq(6),td:eq(7)',row).attr({
                        "align" :   "right"
                    });

                }
            });
        })();

        var createPrintButton=(function(){
            var _btnPrint='<button class="btn btn-default btn-sm"  id="btn_print_deliquent" data-placement="left" data-toggle="modal" title="Print Deliquent Consumer" > Print Delinquent Consumer</button>';
            var _btnRefresh='<button class="btn btn-default btn-sm"  id="btn_refresh_deliquent" data-placement="left" data-toggle="modal" title="Print Deliquent Consumer" > Refresh</button>';
            $("div.print_deliquent").html(_btnPrint+_btnRefresh);
        })();

    })();

    var dtAccountInfoSchedule=(function(){

        var initializeAccountInfoScheduleDatatable=(function(){
            tbl_payment_schedule_list=$('#tbl_payment_schedule_list').DataTable({
                "bLengthChange": false,
                "order": [[0, "desc"]],
                "dom": '<"print_schedule">frtip',
                "oLanguage": {
                    "sSearch": "Search: ",
                    "sProcessing": "Please wait..."
                },

                "rowCallback":function( row, data, index ){

                    $(row).find('td').eq(2).attr({
                        "align":"right"
                    });

                }

            });
        })();

        var createPrintButton=(function(){
            var _btnPrint='<button class="btn btn-default btn-sm"  id="print_schedule" data-placement="left" data-toggle="modal" title="Print Consumer Payment Schedule" > Print Payment Schedule</button>';
            $("div.print_schedule").html(_btnPrint);
        })();

    })();

    var dtCustomerLedger=(function(){

        var initializeCustomerLedger=(function(){
            tbl_customer_ledger=$('#tbl_customer_ledger').DataTable({
                "bLengthChange": false,
                "bPaginate":false,
                "dom": '<"print_ledger">frtip',
                "rowCallback":function( row, data, index ){
                    $('td:eq(4),td:eq(5),td:eq(6)',row).attr({
                        "align" :   "right"
                    });

                }
            });
        })();

        var createPrintButton=(function(){
            var _btnPrint='<button class="btn btn-default btn-sm"  id="btn_print_ledger" data-placement="left" data-toggle="modal" title="Print Consumer Ledger" > Print Consumer Ledger</button>';
            $("div.print_ledger").html(_btnPrint);
        })();

    })();

    var dtItemCartModule=(function(){

        var bindEventHandlers=(function(){

            /**
             * fires everytime kwh, qty, hours cell has key being pressed
             */
            $(oTable.body).on(
                'keypress',
                'td',
                function(event) {

                    if(event.keyCode==13){ //if enter is pressed
                        $('#plu_typehead').focus();

                        var row=$(this).closest('tr');
                        updateCachedData(this); //update cached data of current cell
                        setLineTotal(row);
                        reComputeDetails();
                    }else{
                        return isNumber(event,this);
                    }

                }
            );

            /**
             * fires everytime kwh, qty, hours cell has lost focus
             */
            $(oTable.body).on(
                'blur',
                'td',
                function(event) {
                    var row=$(this).closest('tr');
                    updateCachedData(this); //update cached data of current cell
                    setLineTotal(row);
                    reComputeDetails();
                }
            );

            /**
             * initialize default attributes of footer details
             */
            $([oTFoot.amount,oTFoot.days,oTFoot.dp].join(),oTable.footer)
                .attr({
                    "align"             :   "right",
                    "contenteditable"   :   "true"
                });

            /**
             * restrict invalid characters
             */
            $(oTable.footer).on('keypress',[oTFoot.amount,oTFoot.days,oTFoot.dp].join(), function(event){
                if(event.keyCode==13){
                    $('#plu_typehead').focus();
                    reComputeDetails();
                    return false;
                }else{
                    return isNumber(event,this);
                }
            });

            $([oTFoot.amount,oTFoot.days,oTFoot.dp].join(),oTable.footer).blur(function(){
                reComputeDetails();
            });

            /**
             * fires everytime 'trash' button on selected row is clicked
             */
            $(oTable.body).on('click','button[name="remove_row"]',function(){

                var row=$(this).closest('tr');
                remove( row ); //remove row
                reComputeDetails(); //recompute details

            });

            /**********************ERROR FOUND: 2 event listeners on Save Button***********************************/
            /*$('#btn_create_app_account').click(function(){
                tbl_item_cart.rows().eq(0).each(function(index){
                    var row = tbl_item_cart.row(index);
                    var data = row.data();

                    alert(data[getCellIndex(oTBody.total)]);

                });
            });*/

        })();

        //initialization of item cart
        var initItemCartTable=(function(){
            tbl_item_cart=$('#tbl_item_cart').DataTable({
                "iDisplayLength":7,
                "bLengthChange":false,
                "order": [[ 0, "desc" ]],
                "oLanguage": {
                    "sSearch": "Search: ",
                    "sProcessing": "Please wait..."
                },
                "columnDefs": [
                    //column 1
                    {

                        'targets':[0]
                        ,
                        'render': function(data, type, full, meta){
                            return data.split('|')[1];
                        }

                    },
                    {//column 6
                        'bSortable': false,
                        'targets': [5],
                        'render': function(data, type, full, meta){

                            var btn_trash='<button name="remove_row" class="btn btn-white btn-sm" style="margin-right:-15px;" data-toggle="tooltip" data-placement="top" title="Move to trash"> </button>';

                            return ''+btn_trash+'';
                        }
                    }//column 6
                ],
                "rowCallback":function( row, data, index ){

                    //align and set each column to editable and format column
                    $(([oTBody.kwh,oTBody.qty,oTBody.hrs]).join(),row)
                        .attr({
                            "align": "right",
                            "contenteditable": "true"
                        }).each(function(){
                            $(this).html(
                                accounting.formatNumber(
                                    data[
                                        getCellIndex($(this)) //returns index of column
                                        ], 2)
                            );
                        });

                    //align and format total column, not editable
                    $(oTBody.total,row)
                        .attr({
                            "align": "right"
                        })
                        .html(
                        accounting.formatNumber(
                            data[
                                getCellIndex($(oTBody.total)) //returns index of column(object total)
                                ], 2)
                    );

                }

            });
        })();

        var remove=function(row){
            tbl_item_cart.row( row ).remove().draw();
        };

        /**
         * returns the index of td cell pass as argument
         * @param cell
         * @returns {*|jQuery}
         */
        var getCellIndex=function(cell){
            return $( cell,oTable.body ).index();
        };

        var reComputeDetails=function(){
            var _totalKWH=0;

            tbl_item_cart.rows().eq(0).each(function(index){
                var row = tbl_item_cart.row(index);
                var data = row.data();
                _totalKWH+= parseFloat(data[
                    getCellIndex(  $(oTBody.total)  )
                    ]);
            });

            var _amountPerHour=parseFloat( accounting.unformat($(oTFoot.amount,oTable.footer).text()) );
            var _daysNo=parseFloat( accounting.unformat($(oTFoot.days,oTable.footer).text()) );
            var _downpayment=parseFloat( accounting.unformat( $(oTFoot.dp,oTable.footer).text() ) );
            var _grossAmount=_amountPerHour*_daysNo*_totalKWH;
            var _netAmount=_grossAmount-_downpayment;

            //set total KWH on footer
            $(oTFoot.totalKWH,oTable.footer).html(  accounting.formatNumber(_totalKWH,2)  );
            //set net amount
            $(oTFoot.net,oTable.footer).html(  '
'+accounting.formatNumber( _netAmount,2 )+'
'  );
            //always recreate schedule when net changes
            dtPaymentScheduleModule.createSchedule();

        };

        //function that returns true if element character is valid(number and 1 decimal point)
        var isNumber=function (evt, element) {
            var charCode = (evt.which) ? evt.which : evt.keyCode;

            if(
                /*(charCode != 45 || $(element).text().indexOf('-') != -1) &&      */
            (charCode != 46 || $(element).text().indexOf('.') != -1) &&      // "." check DOT, and only one
            (charCode < 48 || charCode > 57)) {
                return false;
            } else {
                return true;
            }
        };

        var setLineTotal=function(row){
            var _totalAmount=parseFloat(accounting.unformat($(oTBody.kwh,row).text()))*parseFloat(accounting.unformat($(oTBody.qty,row).text()))*parseFloat(accounting.unformat($(oTBody.hrs,row).text()));

            $(oTBody.total,row).html( _totalAmount ); //write total amount

            updateCachedData( $(oTBody.total,row) ); //update line total on cached data
        };

        var addRow=function(data){
            tbl_item_cart.row.add(data).draw();
        };

        var removeRows=function(){
            tbl_item_cart.clear().draw();
        };

        var updateCachedData=function(cell){
            tbl_item_cart
                .cell($(cell))
                .data( accounting.unformat( $(cell).text() ) );
        };

        return {

            addRow : addRow,
            reComputeDetails    :       reComputeDetails,
            getNetAmount        :       function(){
                return accounting.unformat($(oTFoot.net,oTable.footer).text());
            },

            getItemCartInstance : function(){
                return tbl_item_cart;
            },

            removeRows: removeRows

        };

    })();

    var dtPaymentScheduleModule=(function(){
        var tbl_payment_schedule;

        var oTable={
            'id'        :   '#tbl_payment_schedule',
            'body'      :   '#tbl_payment_schedule > tbody',
            'footer'    :   '#tbl_payment_schedule > tfoot'
        };

        var oTBody={
            "date"      :       "td:eq(0)",
            "amount"    :       "td:eq(1)",
            "notes"     :       "td:eq(2)"
        };

        var bindEventHandlers=(function(){
            $(oTable.body).on(
                'keypress',
                'td',
                function(event) {
                    if(event.keyCode==13){ //if enter is pressed
                        updateCachedData(this); //update cached data of current cell
                    }
                }
            );

            /**
             * fires everytime editable cell lost focus
             */
            $(oTable.body).on(
                'blur',
                'td',
                function(event) {
                    updateCachedData(this); //update cached data of current cell
                }
            );

        })();

        var initPaymentTable=(function(){
            tbl_payment_schedule=$('#tbl_payment_schedule').DataTable({
                "iDisplayLength": 7,
                "bLengthChange": false,
                "bPaginate":true,
                "bSortable":false,
                "oLanguage": {
                    "sSearch": "Search: ",
                    "sProcessing": "Please wait..."
                },
                "columnDefs": [
                    {'bSortable': false, 'targets': [0], 'width':'25%'} ,
                    {'bSortable': false, 'targets': [1], 'width':'25%'} ,
                    {'bSortable': false, 'targets': [2]}

                ],
                "rowCallback":function( row, data, index ){
                    $(oTBody.notes,row).attr({
                        "align"                 :       "left",
                        "contenteditable"       :       "true"
                    });
                }
            });

        })();

        var createSchedule=function(){
            var _max=parseFloat( $('#txt_duration').val() );

            var _netAmount=parseFloat(dtItemCartModule.getNetAmount());
            var _installment=_netAmount/_max;

            var _startDate=new Date(  $('#payment_start').val()  );
            Date.today().set({
                year: _startDate.getFullYear(),
                month: _startDate.getMonth(),
                day: _startDate.getDate()
            });

            clear(); //clear rows of schedule table

            for(var i=0;i<=_max-1;i++){
                addRow(
                    [
                        Date.today().addMonths(i).toString("MM/dd/yyyy"),
                        accounting.formatNumber(_installment,2),
                        ""
                    ]
                );
            }
        };

        var updateCachedData=function(cell){

            tbl_payment_schedule
                .cell($(cell))
                .data( $(cell).text() );
        };

        var clear=function() {
            tbl_payment_schedule
                .clear()
                .draw();
        };

        var addRow=function(data){
            tbl_payment_schedule.row.add( data ).draw();
        };

        var removeRows=function(){
            tbl_payment_schedule.clear().draw();
        };

        return {
            addRow          :       addRow,
            clear           :       clear,
            createSchedule  :   createSchedule,
            getPaymentScheduleInstance : function(){
                return tbl_payment_schedule;},
            removeRows: removeRows

        };

    })();

    var typeHeadPLUmodule=(function(){
        /***********************typehead*************************/
        var $input = $('#plu_typehead');
        var initPLUTypeHead=(function(){
            //get product list
            $.get('ApprehendedConsumerController/ActionGetUnitItemList', function(response){
                //console.log(response);
                //initialize typehead after data request is completed
                $input.typeahead({
                    source:response,
                    updater: function(data){
                        //alert(data.id);
                        dtItemCartModule.addRow([
                            data.id+"|"+data.description,
                            data.kwh,1,8,
                            data.kwh*8,""
                        ]);
                        dtItemCartModule.reComputeDetails();
                        return "";
                    }
                });
            },'json').fail(function(xhr){
                //console.log(xhr);
            });
        })();
    })();

    /**********************************************************************************************************************************************************/

    var apprehendedInfoModalModule=(function(){
        var _mode;		var _selectedID;	 var _selectedRow;  var _consumerID;

        //binds all events of invoice modal
        var bindEventHandlers=(function(){

            /**
             *
             * fires everytime the record invoice button on modal is clicked
             *
             **/
            $('#btn_create_app_account').click(function(){

                if(validateRequiredFields()){ //if true, all required fields are supplied

                    if(_mode=="new"){ //if current mode is new

                        createApprehendedConsumerAccount()
                            .success(function(response){ //if request is successful
                                //console.log(response);

                                PNotify.removeAll(); //remove all notifications
                                new PNotify({
                                    title: 'Success!',
                                    text:  response.msg,
                                    type:  response.stat
                                }); //create new notification base on server response

                                var row=response.row[0];
                                var data=[
                                    row.record_info,
                                    row.reference_no,
                                    row.account_no,
                                    row.consumer,
                                    row.contact_no,
                                    row.period,
                                    accounting.formatNumber(row.total_back_bill_amount,2),
                                    ""];
                                apprehendedConsumerModule.addRow(data); //add the info of recent invoice
                                apprehendedConsumerModule.lastPage(); //go to last page

                                dtItemCartModule.removeRows(); //remove all rows of cart datatable
                                clearFields(); //clear fields

                            })
                            .error(function(xhr,stat,error){ //if error occurs
                                alert(xhr.responseText);
                                //console.log(xhr);
                            });

                    }else{		//if current mode is update
                        //alert("Update Account!");
                        updateApprehendedAccount()
                            .success(function(response){ //if request is successful
                                //console.log(response);
                                //alert(response.test);
                                hideModal();

                                PNotify.removeAll(); //remove all notifications
                                new PNotify({
                                    title: 'Success!',
                                    text:  response.msg,
                                    type:  response.stat
                                }); //create new notification base on server response

                                var row=response.row[0];
                                var data=[
                                        row.record_info,
                                        row.reference_no,
                                        row.account_no,
                                        row.consumer,
                                        row.contact_no,
                                        row.period,
                                        accounting.formatNumber(row.total_back_bill_amount,2),
                                        ""];
                                apprehendedConsumerModule.updateRow(_selectedRow,data);

                                dtItemCartModule.removeRows(); //remove all rows of cart datatable
                                clearFields(); //clear fields

                            })
                            .error(function(xhr,stat,error){ //if error occurs
                                //alert(xhr.responseText);
                                //console.log(xhr);
                            });
                    }

                }

            });

            /**
             *
             * fires everytime the modal is shown
             *
             **/
            $('#invoice_modal').on('shown.bs.modal', function() {
                $('#plu_typehead').focus();
            });

        })();

        //function that validates all required fields, returns true if all required fields are supplied
        var validateRequiredFields=function(){
            var	stat=1;

            $('input[required]').each(function(){
                if($(this).val()==""){

                    $(this).focus()
                        .tooltip('show');

                    PNotify.removeAll();
                    new PNotify({
                        title: 'Missing!',
                        text: $(this).data('message'),
                        type: 'error'
                    });

                    stat=0;
                    return false; //this will exit on function inside 'each'
                }
            });

            $('textarea[required]').each(function(){ //selectpicker does not support tooltip, just show notification
                if($(this).val()==""){
                    PNotify.removeAll();
                    new PNotify({
                        title: 'Missing!',
                        text: $(this).data('message'),
                        type: 'error'
                    });

                    stat=0;
                    return false; //this will exit on function inside 'each'
                }
            });

            var rowCount = dtItemCartModule.getItemCartInstance().rows()[0].length;
            if(rowCount==0){ //if not item in cart
                PNotify.removeAll();
                new PNotify({
                    title: 'Missing!',
                    text: 'No item found. Please enter atleast one item.',
                    type: 'error'
                });

                stat=0;
            }

            return stat; //this will always be executed and return current state
        }; //end of validateRequiredFields

        //add new invoice
        var createApprehendedConsumerAccount=function(){

            var _cartTable=dtItemCartModule.getItemCartInstance(); //intance of datatable
            var _paymentTable = dtPaymentScheduleModule.getPaymentScheduleInstance()

            var amount_kwh=$("#tbl_item_cart > tfoot tr:eq(0) > td:eq(1)").text();
            var total_kwh=$("#tbl_item_cart > tfoot tr:eq(0) > td:eq(3)").text();
            var down_payment=$("#tbl_item_cart tfoot   > tr:eq(1) > td:eq(1)").text();
            var no_of_days =$("#tbl_item_cart tfoot > tr:eq(1) > td:eq(3)").text();
            var total_back_bill_amount=$("#tbl_item_cart tfoot > tr:eq(2) > td:eq(1)").text();

            var serialData=$('#frm_account_details,#frm_payment_sched_details').serializeArray();

            console.log(serialData);

            _cartTable.rows().eq(0).each(function(index){
                var row = _cartTable.row(index);
                var data = row.data();
                serialData.push(
                    {name:"unit_id[]",value:data[0].split("|")[0]},
                    {name:"line_kwh[]",value:accounting.unformat(data[1])},
                    {name:"unit_qty[]",value:accounting.unformat(data[2])},
                    {name:"hours[]",value:accounting.unformat(data[3])}
                );

            });

            _paymentTable.rows().eq(0).each(function(index){
                var row = _paymentTable.row(index);
                var data = row.data();

                serialData.push(
                    {name:"sched_payment_date[]",value:data[0]},
                    {name:"due_amount[]",value:accounting.unformat(data[1])},
                    {name:"bill_description[]",value:data[2]}
                );

                serialData.push(
                    {name:"amount_kwh",value:amount_kwh},
                    {name:"down_payment",value:accounting.unformat(down_payment)},
                    {name:"total_back_bill_amount",value:accounting.unformat(total_back_bill_amount)},
                    {name:"no_of_days",value:no_of_days},
                    {name:"total_kwh",value:total_kwh}
                );

            });
            //console.log(serialData);
            return $.ajax({
                "dataType":"json",
                "type":"POST",
                "url":"ApprehendedConsumerController/ActionSaveApprehendedConsumerInfo",
                "data":serialData
            });

        }; //end of saveInvoiceInfo

        //update invoice
        var updateApprehendedAccount=function(){
            var _cartTable=dtItemCartModule.getItemCartInstance(); //intance of datatable
            var _paymentTable = dtPaymentScheduleModule.getPaymentScheduleInstance();
            var serialData=$('#frm_account_details,#frm_payment_sched_details').serializeArray();

            //console.log(serialData);
            var amount_kwh=$("#tbl_item_cart > tfoot tr:eq(0) > td:eq(1)").text();
            var total_kwh=$("#tbl_item_cart > tfoot tr:eq(0) > td:eq(3)").text();
            var down_payment=$("#tbl_item_cart tfoot   > tr:eq(1) > td:eq(1)").text();
            var no_of_days =$("#tbl_item_cart tfoot > tr:eq(1) > td:eq(3)").text();
            var total_back_bill_amount=$("#tbl_item_cart tfoot > tr:eq(2) > td:eq(1)").text();

            /*****ERROR FOUND! Wrong Push Object Syntax*****/
            /*serialData.push({
                name:"id",value: _selectedID,
                name:"consumer_id",value:_consumerID
            });*/

            serialData.push(
                {name:"id",value: _selectedID},
                {name:"consumer_id",value:_consumerID}
            )

            //console.log(serialData);

            _cartTable.rows().eq(0).each(function(index){
                var row = _cartTable.row(index);
                var data = row.data();
                serialData.push(
                    {name:"unit_id[]",value:data[0].split("|")[0]},
                    {name:"line_kwh[]",value:accounting.unformat(data[1])},
                    {name:"unit_qty[]",value:accounting.unformat(data[2])},
                    {name:"hours[]",value:accounting.unformat(data[3])}
                );

            });
                _paymentTable.rows().eq(0).each(function(index){
                    var row = _paymentTable.row(index);
                    var data = row.data();
                    serialData.push(
                        {name:"sched_payment_date[]",value:data[0]},
                        {name:"due_amount[]",value:accounting.unformat(data[1])},
                        {name:"bill_description[]",value:data[2]}
                    );

                    /**
                     * MISSING! total kwh
                     */
                    serialData.push(
                        {name:"amount_kwh",value:amount_kwh},
                        {name:"down_payment",value:accounting.unformat(down_payment)},
                        {name:"total_back_bill_amount",value:accounting.unformat(total_back_bill_amount)},
                        {name:"no_of_days",value:no_of_days},
                        {name:"total_kwh",value:total_kwh}
                    );

                });

        //console.log(serialData);

            return $.ajax({
                "dataType":"json",
                "type":"POST",
                "url":"ApprehendedConsumerController/ActionUpdateApprehendedAccountInfo",
                "data":serialData
            });

        };

        //set mode of modal, are we going to add new or update??
        var setCurrentMode=function(status){
            _mode=status.toLowerCase();
        };

        //returns the current mode of the modal, add new or update
        var getCurrentMode=function(){
            return _mode;
        };

        //set selected id, the invoice
        var setSelectedID=function(id){
            _selectedID=id;
        };

        var setConsumerID=function(id){
            _consumerID=id;
        };
        //get selected id, the invoice
        var getSelectedID=function(){
            return _selectedID;
        };

        //set selected row, the tr element
        var setSelectedRow=function(row){
            _selectedRow=row;
        };

        //get selected row, the tr element
        var getSelectedRow=-function(){
            return _selectedRow;
        }

        var clearFields=function(){
            $('#plu_head').val('');

            $('#frm_account_details input:not([id="dt_end_date"]) ,#frm_payment_sched_details input:not([id="payment_start"])').val('');

            $('#frm_account_details textarea,#frm_payment_sched_details textarea').val('');

            $('#payment_start').val(Date.today().toString("MM/dd/yyyy"));
        };

        var showModal=function(){
            $('#apprehended_consumer_modal').modal('show');
        };

        var hideModal=function(){
            $('#apprehended_consumer_modal').modal('hide');
        };

        //set invoice modal details
        var setApprehendedModalDetails=function(data){

            $( "input[name='reference_no']").val(data.reference_no);
            $( "input[name='account_no']").val(data.account_no);
            $( "input[name='consumer_name']").val(data.consumer_name);
            $( "input[name='contact_no']").val(data.contact_no);
            $( "input[name='email']").val(data.email);
            $( "input[name='house_no']").val(data.house_no);
            $( "input[name='street_no']").val(data.street_no);
            $( "input[name='barangay']").val(data.barangay);
            $( "input[name='municipality']").val(data.municipality);
            $( "input[name='email']").val(data.email);
            $( "input[name='payment_start']").val(data.payment_start_date);
            $( "input[name='duration']").val(data.duration);
            $( "textarea[name='payment_schedule_remarks']").val(data.payment_schedule_remarks);

            /**
             * FOOTER DETAILS!
             */
            //alert(data.amount_kwh);
            $("#tbl_item_cart > tfoot tr:eq(0) > td:eq(1)").text(data.amount_kwh);

            $("#tbl_item_cart > tfoot tr:eq(0) > td:eq(3)").text(
                accounting.formatNumber(data.total_account_kwh,2)
            );

            $("#tbl_item_cart tfoot   > tr:eq(1) > td:eq(1)").text(data.down_payment);
            $("#tbl_item_cart tfoot > tr:eq(1) > td:eq(3)").text(data.no_of_days);
            //alert(data.total_back_bill_amount);
            $("#tbl_item_cart tfoot > tr:eq(2) > td:eq(1)").text(
                accounting.formatNumber(data.total_back_bill_amount,2)
            );

            //set  cart items

            $('#tbl_item_cart tbody').html('

');

            $.getJSON('ApprehendedConsumerController/ActionGetAccountCartItems',{id:data.bill_account_id}, function(response){
                //console.log(response);
                dtItemCartModule.removeRows(); //remove rows
                $.each(response,function(index,data){
                        dtItemCartModule.addRow([
                        data.item,
                        data.estimated_kwh,
                        data.unit_qty,
                        data.hours,
                        data.line_total
                    ]);

                });

            });

            $.getJSON('ApprehendedConsumerController/ActionGetPaymentScheduleItem',{id:data.bill_account_id}, function(response){
                //console.log(response);
                dtPaymentScheduleModule.removeRows(); //remove rows
                $.each(response,function(index,data){
                    dtPaymentScheduleModule.addRow([
                        data.sched_payment_date,
                        accounting.formatNumber(data.due_amount,2),
                        data.bill_description
                    ]);

                });

            });

        };

        //return value of this invoice modal object module
        return {
            setMode: 		setCurrentMode,
            getMode:		getCurrentMode,
            clearFields: 	clearFields,
            showModal: 		showModal,
            hideModal:		hideModal,
            setDetails: 	setApprehendedModalDetails,
            setSelectedID: 	setSelectedID,
            getSelectedID: 	getSelectedID,
            setConsumerID: 	setConsumerID,
            setSelectedRow: setSelectedRow

        }; //end of return value

    })();

    //sparkline graph
    $("#sparkline8").sparkline([2,5, 6, 7, 2, 0, 4, 2, 4, 5, 7, 2, 4, 12, 14, 4, 2, 14, 12, 7,5,4,3,4], {
        type: 'bar',
        barWidth: 8,
        height: '80px',
        barColor: '#1ab394',
        negBarColor: '#c6c6c6'
    });

    $('#btn_print_ledger,#print_schedule').prop('disabled',true);

    //write toolbar on datatable
    var _btnNew='<button class="btn btn-white btn-sm"  id="btn_new" data-placement="left" data-toggle="modal" data-target="#apprehended_consumer_modal" title="Record Back Bill Account" > Record Back Bill Account</button>';
    $("div.toolbar").html(_btnNew);

    $(document).on('click','#btn_print_ledger',function(){
        $(this).closest('a').attr('href',"ApprehendedConsumerController/ActionPreviewConsumerLedger?accid="+selected_account_id);
    });

    $(document).on('click','#print_schedule',function(){
        $(this).closest('a').attr('href',"ApprehendedConsumerController/ActionPreviewSchedule?accid="+selected_account_id);
    });

    $('#txt_duration').keyup(function(){
        dtPaymentScheduleModule.createSchedule();
    });

    $('#txt_duration').change(function(){
        $('#txt_duration').trigger('keyup');
    });

    /**************fires element with required attribute when key is no longer being pressed*******************/
    $('input[required]').keyup(function(){
        $(this).tooltip('destroy');
    });

    /**********************************************************************************************************************************************************/

    apprehendedConsumerModule.showApprehendedConsumerList();

    //new apprehended account
    $('#btn_new').click(function(){
        apprehendedInfoModalModule.setMode("new");

        apprehendedInfoModalModule.clearFields(); //clear fields
        dtItemCartModule.removeRows(); //remove all rows of cart datatable
        dtPaymentScheduleModule.removeRows();

    });

    $('#txt_downpayment').change(function(event){
        $(oTFoot.dp,oTable.footer).html($(this).val()).blur();

    });

    $('#dt_end_date,#payment_start').daterangepicker({
        singleDatePicker: true,
        calender_style: "picker_4"
    }, function (start, end, label) {
        console.log(start.toISOString(), end.toISOString(), label);
    });

    var showConsumerLedger=function(){
        $('a[href="#tab-3"]').html("  [ Loading details...  ]" );
        $('#tbl_customer_ledger tbody').html('

');
        $.getJSON('ApprehendedConsumerController/ActionGetConsumerLedger',{"id":selected_account_id}, function(response){
            tbl_customer_ledger.clear().draw(); //make sure apprehended consumer datatable has no rows
            //console.log(response);
            $.each(response,function(index,value){
                tbl_customer_ledger.row.add([
                    value.TransDate,
                    value.account_no,
                    value.receipt_no,
                    value.Description,
                    accounting.formatNumber(value.Debit,2),
                    accounting.formatNumber(value.Credit,2),
                    accounting.formatNumber(value.Balance,2)
                ]).draw();
            });

            var consumer=$('#tbl_apprehended_consumer_list > tbody tr.active').find('td:eq(3)').text();
            $('a[href="#tab-3"]').html(" Consumer Ledger [ "+consumer+" ]");
        });
    };

    var showDelinquentList=function(){

        $('#tbl_deliquent_list tbody').html('

');

        $.getJSON('ApprehendedConsumerController/ActionGetDelinquentList', function(response){
            tbl_deliquent_list.clear().draw(); //make sure apprehended consumer datatable has no rows
            //console.log(response);
            $.each(response,function(index,value){
                tbl_deliquent_list.row.add([
                    value.account_no,
                    value.consumer_name,
                    accounting.formatNumber(value.ApprehendedAmount,2),
                    accounting.formatNumber(value.TotalPayment,2),
                    accounting.formatNumber(value.TotalBalance,2),
                    accounting.formatNumber(value.PaymentsMade,0),
                    accounting.formatNumber(value.DelayedMonths,0),
                    accounting.formatNumber(value.PreviousBalance,2)
                ]).draw();
            });

        });
    };

    showDelinquentList();
    /************************************************************************************/

});


text:
/**
 * \file hmc-tessellate.hpp
 *
 * \author 2015-2016 Sandia Clinic Team
 *
 * \brief
 *   Contains interface classes for the hmc-tessellate
 *   Voronoi tessellation package.
 */

#pragma once

#include <iostream>
#include <vector>
#include <limits>
#include <assert.h>

#include "vectormath.hpp"
#include "polyhedron.hpp"
#include "verification.hpp"
#include "sds.hpp"

namespace hmc {
    using SizeType = unsigned int;
    using SDS = CellArray<SizeType, SizeType>;
    // using SDS = TrivialSDS<SizeType>;
    // using SDS = ShellArray<SizeType>;

	using TargetGroup = std::unordered_set<int>;
	using SourceGroup = std::vector<int>;

    class Diagram;
    class Cell;
	class VoronoiFace;

	/// Value used to signify that no group has been specified.
	constexpr int DEFAULT_GROUP = -1;

	/// Value used to signify that no search radius is known for building a Cell.
    constexpr double NO_RADIUS = -1;

	/// Value used to signify that a particle does not exist in a Diagram.
	constexpr SizeType NO_INDEX = std::numeric_limits<SizeType>::max();

	/**
	 * \struct CellConstructor
	 *
	 * \brief
	 *   Contains all the information for constructing a Cell in a Diagram.
	 */
    struct CellConstructor {
		/// A pointer to the Diagram that contains the Cell.
        const Diagram* diagram;

		/// The index of the point in this Cell in the Diagram.
        SizeType index;

		/// The position of the point in this Cell.
        Vector3 position;

		/// The search radius for neighbors of the particle in the Cell. If no radius is known, set to NO_RADIUS.
        double searchRadius;

		/// The target group of the Cell.
		const TargetGroup* targetGroup;

		/// Constructor
        CellConstructor(const Diagram& diagram, SizeType index, Vector3 position, double searchRadius = NO_RADIUS)
            : diagram(&diagram), index(index), position(position), searchRadius(searchRadius), targetGroup(nullptr)
        { /* Done */ }

		/// Constructor
        CellConstructor(const Diagram& diagram, SizeType index, Vector3 position, const TargetGroup& targetGroup, double searchRadius = NO_RADIUS)
            : diagram(&diagram), index(index), position(position), searchRadius(searchRadius), targetGroup(&targetGroup)
        { /* Done */ }

        CellConstructor& withSearchRadius(double searchRadius)
        {
            this->searchRadius = searchRadius;
            return *this;
        }

        CellConstructor& withTargetGroup(const TargetGroup& targetGroup)
        {
            this->targetGroup = &targetGroup;
            return *this;
        }
    };

	/**
	 * \class Cell
	 *
	 * \brief
	 *   A Voronoi cell. Used to retrieve various parameters.
	 *
	 * \remarks
	 *   Each cell is only computed once, and the information is cached if it is needed later.
	 */
    class Cell {
        friend class Diagram;

    public:
		/// A pointer to the Diagram that contains this Cell.
        const Diagram* diagram_;

		/// The index of the point in this Cell in the Diagram.
        SizeType index_;

		/// The position of the point in this Cell.
        Vector3 position_;

		/// The Polyhedron representing the Voronoi cell.
        Polyhedron poly_;

        /// A flag that tells whether the Polyhedron has been computed.
        bool polyComputed_;

        /// The search radius for neighbors of the particle in the Cell. If no radius is known, set to NO_RADIUS.
        double searchRadius_;

		/// The target group of the Cell.
		const TargetGroup* targetGroup_;

		/// The ExpandingSearch object used to search for neighbors of the point in this Cell.
        SDS::ExpandingSearch search_;

    public:
		/// Default constructor
        Cell() : diagram_(nullptr), polyComputed_(false), targetGroup_(nullptr) {}

		/**
		 * \brief Constructor
		 *
		 * \param[in]  diagram       A reference to the Diagram that contains this Cell.
		 * \param[in]  index         The index of the point in this Cell in the Diagram.
		 * \param[in]  position      The position of the point in this Cell.
		 * \param[in]  searchRadius  The search radius for neighbors of the particle in the Cell. Optional.
		 *
		 * \remarks
		 *   If no search radius is given, neighbors will be found using an ExpaningSearch.
		 */
        Cell(const Diagram& diagram, SizeType index, Vector3 position, double searchRadius = NO_RADIUS)
            : diagram_(&diagram), index_(index),
              position_(position), polyComputed_(false), searchRadius_(searchRadius), targetGroup_(nullptr)
        { /* Done */ }

		/**
		 * \brief Constructor
		 *
		 * \param[in]  diagram       A reference to the Diagram that contains this Cell.
		 * \param[in]  index         The index of the point in this Cell in the Diagram.
		 * \param[in]  position      The position of the point in this Cell.
		 * \param[in]  searchRadius  The search radius for neighbors of the particle in the Cell. Optional.
		 * \param[in]  targetGroup   The target group of the Cell.
		 *
		 * \remarks
		 *   If no search radius is given, neighbors will be found using an ExpaningSearch.
		 */
        Cell(const Diagram& diagram, SizeType index, Vector3 position, const TargetGroup& targetGroup, double searchRadius = NO_RADIUS)
            : diagram_(&diagram), index_(index),
              position_(position), polyComputed_(false), searchRadius_(searchRadius),
			  targetGroup_(&targetGroup)
        { /* Done */ }

		/**
		 * \brief Constructor
		 *
		 * \param[in]  info  A CellConstructor object
		 *
		 * \remark
		 *   Creates the Cell specified by the CellConstructor.
		 */
		Cell(const CellConstructor info)
			: Cell()
		{
			*this = info;
		}

		/// Assignment operator
        Cell& operator=(const CellConstructor info)
        {
            clear();
            diagram_ = info.diagram;
            index_ = info.index;
            position_ = info.position;
			targetGroup_ = info.targetGroup;
            searchRadius_ = info.searchRadius;
            return *this;
        }

		/// Clear the Cell
        void clear()
        {
            // diagram_ = nullptr;
            poly_.clear();
            polyComputed_ = false;
        }

        /**
         * \brief
         *   Get the position of the particle this
         *   Voronoi cell is constructed around.
         *
         * \return
         *   The position, as a Vector3.
         */
        inline const Vector3& getPosition() const;

        /**
         * \brief
         *   Gets the original index of the particle
         *   this cell is based on. The original index
         *   is the index position the particle was originally
         *   added to the diagram in.
         *   If the cell is not based on a particle in
         *   the Diagram, returns NO_INDEX instead
         *
         * \return
         *   The original index, or NO_INDEX.
         */
        inline SizeType getOriginalIndex() const;

        /**
         * \brief
         *   Gets the internal index of the particle
         *   this cell is based on. The internal index
         *   is actual index position the particle
         *   within the Diagram.
         *   If the cell is not based on a particle in
         *   the Diagram, returns NO_INDEX instead
         *
         * \return
         *   The internal index, or NO_INDEX.
         */
        inline SizeType getInternalIndex() const;

		/**
		 * \brief
		 *   Compute the volume of the Voronoi cell.
		 *
		 * \return
		 *   The volume of the Voronoi cell.
		 */
        double computeVolume()
        {
            ensurePolyComputed();
            return poly_.computeVolume();
        }

		/**
		 * \brief
		 *   Find the neighbors of the point in the Voronoi cell.
		 *
		 * \param[out]  result  A container for storing the neighbors
		 */
        template <typename Collection>
        void computeNeighbors(Collection& result)
        {
            ensurePolyComputed();
            poly_.computeNeighbors(result);
        }

		/**
		 * \brief
		 *   Find all of the points within a raidus of the particle contained in the Cell.
		 *
		 * \param[in]   radius  The radius to use for the search
		 * \param[out]  result  A vector for storing the indices of neighbors
		 *
		 * \remark
		 *   If a TargetGroup has been specified for the cell, only finds neighbors in the
		 *   TargetGroup.
		 */
		inline void computeNeighborCloud(double radius, std::vector<SizeType>& result);

		/**
		 * \brief
		 *   Compute the vertices of the Voronoi cell.
		 *
		 * \param[out]  result  A container of VoronoiFace objects for storing the neighbors
		 */
		template<typename Collection>
		void computeVertices(Collection& result)
		{
			ensurePolyComputed();
			poly_.computeVertices(result);
		}

		/**
		 * \brief
		 *   Compute the faces of the Voronoi cell.
		 *
		 * \param[out]  result  A container for storing the faces
		 */
		template<typename Collection>
		inline void computeFaces(Collection& result);

		/**
		 * \brief
		 *   Checks whether the Polyhedron representing the Voronoi cell has been computed.
		 *   If it has not, performs the computation.
		 */
        void ensurePolyComputed()
        {
            VERIFY_EXIT(polyComputed_);

            if (!polyComputed_) {
				computeVoronoiCell();
                polyComputed_ = true;
            }
        }

    protected:
        inline void computeVoronoiCell();
    };

	/**
	 * \class VoronoiFace
	 *
	 * \brief
	 *   Allows access to data for a single face of a Voronoi Cell.
	 */
	class VoronoiFace
	{
	private:

		// The FaceIndex corresponding to this face in the Polyhedron for the Voronoi Cell.
		FaceIndex fi_;

		// A pointer to the Voronoi Cell that contains this face.
		Cell* cell_;

	public:

		/**
		 * \brief
		 *   Constructor
		 *
		 * \param[in]  fi    The face index of this face in the cell Polyhedron
		 * \param[in]  cell  The cell that contains this face
		 */
		VoronoiFace(FaceIndex fi, Cell& cell)
			: fi_(fi), cell_(&cell)
		{}

		/**
		 * \brief
		 *   Get the vertices of the VoronoiFace.
		 *
		 * \param[out]  result  A container of doubles, stored sequentially as x, y, z coordinates of each vertex
		 */
		template<typename Collection>
		void computeVertices(Collection& result)
		{
			cell_->poly_.computeFaceVertices(fi_, result);
		}

		/**
		 * \brief
		 *   Get the area of the VoronoiFace.
		 *
		 * \return
		 *   The area of the VoronoiFace.
		 */
		double computeArea()
		{
			return 0.5 * mag(cell_->poly_.weightedNormal(fi_));
		}

		/**
		 * \brief
		 *   Get the neighbor of the Voronoi Cell which cut the polyhedron to make this face.
		 *
		 * \return
		 *  The index of the neighbor in the Diagram that contains the Voronoi Cell.
		 */
		SizeType getNeighbor()
		{
			return cell_->poly_.faces_[fi_].id;
		}
	};

	/**
	 * \class Diagram
	 *
	 * \brief
	 *   A class that represents a Voronoi diagram.
	 *
	 * \details
	 *   Contains a vector of Particle objects, a spatial data structure, and a Polyhedron
	 *   representing the container.
	 */
    class Diagram {
        friend class Cell;

        // using SDS = TrivialSDS<SizeType>;
        // using SDS = ShellArray<SizeType>;

    protected:
        /// Whether or not the diagram has been readied for Voronoi computations
        bool initialized_ = false;

		/// The Polyhedron that represents the container of the points.
        Polyhedron containerShape_;

        /// A bounding box enclosing all the points
        BoundingBox boundingBox_;

		/// The vector of Particle objects in the Diagram.
        std::vector<Vector3> particles_;

        /// A vector mapping internal indices to original indices
        std::vector<SizeType> originalIndices_;

        /// A vector mapping original indices to internal indices
        std::vector<SizeType> internalIndices_;

		/// The vector of groups that contain each particle. The group at an index corresponds
		/// to the particle at that index.
		std::vector<int> groups_;

		/// The spatial data structure used to determine spatial relationships between points.
        SDS spatialStructure_;

    public:
		/// Default constructor
        Diagram() = default;

		/**
		 * \brief Constructor
		 *
		 * \param[in]  containerShape  A Polyhedron represeting the shape of the container of points.
		 */
        Diagram(Polyhedron containerShape)
            : containerShape_(containerShape)
        { /* Done */ }

		/**
		 * \brief
		 *   Makes a Diagram in the shape of a cube.
		 *
		 * \param[in]  xmin  The lower x boundary of the Diagram
		 * \param[in]  xMAX  The upper x boundary of the Diagram
		 * \param[in]  ymin  The lower y boundary of the Diagram
		 * \param[in]  yMAX  The upper y boundary of the Diagram
		 * \param[in]  zmin  The lower z boundary of the Diagram
		 * \param[in]  zMAX  The upper z boundary of the Diagram
		 *
		 * \return
		 *   A Diagram with the specified boundaries.
		 */
        static Diagram cube(double xmin, double xMAX,
                            double ymin, double yMAX,
                            double zmin, double zMAX)
        {
            Diagram diagram;
            diagram.containerShape_.buildCube(xmin, xMAX, ymin, yMAX, zmin, zMAX);
            return diagram;
        }

		/**
		 * \brief
		 *   Returns the number of Particle objects in the Diagram.
		 *
		 * \return
		 *   The number of Particle objects in the Diagram.
		 */
        SizeType size() const
        {
            return particles_.size();
        }

        void clear()
        {
            initialized_ = false;
            boundingBox_.reset();

            particles_.clear();
            groups_.clear();
            originalIndices_.clear();
            internalIndices_.clear();
            spatialStructure_.clear();
        }

		/**
		 * \brief
		 *   Add a Particle to the Diagram.
		 *
		 * \param[in]  x      The x-coordinate of the Particle
		 * \param[in]  y      The y-coordinate of the Particle
		 * \param[in]  z      The z-coordinate of the Particle
		 * \param[in]  group  The group containing the Particle. Optional.
		 *
		 * \remarks
		 *   Should only be used before the initialize function is called.
		 */
        void addParticle(double x, double y, double z, int group = DEFAULT_GROUP)
        {
            addParticle(Vector3 {x, y, z}, group);
        }

		/**
		 * \brief
		 *   Add a Particle to the Diagram.
		 *
		 * \param[in]  position  A Vector3 containing the {x,y,z} coordinates of the particle.
		 * \param[in]  group     The group containing the Particle. Optional.
		 *
		 * \remarks
		 *   Should only be used before the initialize function is called.
		 */
        void addParticle(Vector3 position, int group = DEFAULT_GROUP)
        {
            particles_.push_back(position);
            boundingBox_.adjustToContain(position);
            groups_.push_back(group);
        }

		/**
		 * \brief
		 *   Initialize the Diagram.
		 *
		 * \remarks
		 *   Should only be called after all Particles have been added to the Diagram.
		 */
        void initialize()
        {
            // We are not certain whether or not padding
            // is a good or a bad idea.
            // It does not appear to be necessary, so,
            // we are saying that one should supply
            // their own bounding box if padding is desired.
            // However, here's the relevant call:
            //boundingBox_.pad(1e-6);

            assert(!initialized_ && "Diagram does not support multiple initializations");

            sortParticlesByMortonIndex();

            if (containerShape_.isClear()) {
                containerShape_.buildCube(boundingBox_);
            }

            spatialStructure_.initialize(0, particles_.size(),
                [this](SizeType index) -> Vector3 {
                    return particles_[index];
                }
            );

            initialized_ = true;
        }

		/**
		 * \brief
		 *   Sort particles in Morton order.
		 *
		 * \details
		 *   Morton order (or Z-order) is a method of sorting multidimensional data according
		 *   to a single dimension which preserves locality of data points.
		 */
        void sortParticlesByMortonIndex()
        {
            const BoundingBox& bb = boundingBox_;

            constexpr unsigned int numLevels = 10;
            constexpr double numCellsPerSide = 1 << numLevels;

            const Vector3 sideLengths = (bb.high - bb.low) / numCellsPerSide;

            using Index3 = Vector3_of<SizeType>;
            auto cellIndices = [&](SizeType i) -> Index3 {
                auto offset = particles_[i] - bb.low;
                return Index3(
                    offset.x / sideLengths.x,
                    offset.y / sideLengths.y,
                    offset.z / sideLengths.z
                );
            };

            auto mortonIndex = [&](SizeType i) -> SizeType {
                Index3 indices = cellIndices(i);

                SizeType morton = 0;
                unsigned int bit = 0;

                for (unsigned int level = 0; level < numLevels; ++level) {
                    morton |= ((indices.x >> level) & 1u) << bit++;
                    morton |= ((indices.y >> level) & 1u) << bit++;
                    morton |= ((indices.z >> level) & 1u) << bit++;
                }
                return morton;
            };

            // -----------------------------------------------------

            const SizeType n = particles_.size();

            std::vector<SizeType>& permutation = internalIndices_;
            std::vector<SizeType>& morton = originalIndices_;

            permutation.resize(n);
            morton.resize(n);

            for (SizeType i = 0; i < n; ++i) {
                permutation[i] = i;
                morton[i] = mortonIndex(i);
            }

            std::sort(permutation.begin(), permutation.end(),
                [&morton](SizeType i, SizeType j) -> bool {
                    return morton[i] < morton[j];
                });

            constexpr SizeType COMPLETED = std::numeric_limits<SizeType>::max();
            for (SizeType cycleStart = 0; cycleStart < n; ++cycleStart) {
                if (permutation[cycleStart] == COMPLETED) {
                    continue;
                }

                auto startParticle = particles_[cycleStart];
                auto startGroup = groups_[cycleStart];

                SizeType current = cycleStart;
                SizeType next = originalIndices_[current] = permutation[current];

                while (next != cycleStart) {
                    originalIndices_[current] = permutation[current];
                    permutation[current] = COMPLETED;

                    particles_[current] = particles_[next];
                    groups_[current] = groups_[next];

                    current = next;
                    next = permutation[next];
                }

                originalIndices_[current] = permutation[current];
                permutation[current] = COMPLETED;

                particles_[current] = startParticle;
                groups_[current] = startGroup;
            }

            for (SizeType i = 0; i < n; ++i) {
                internalIndices_[originalIndices_[i]] = i;
            }
        }

		/**
		 * \brief
		 *   Initialize the Diagram.
		 *
		 * \param[in]  fill  A lambda function that fills the Diagram with Particle objects
		 *
		 * \remarks
		 *   Should only be called after all Particles (other than those to be added by fill)
		 *   have been added to the Diagram.
		 */
        template <typename FillDiagramWithParticles>
        void initialize(FillDiagramWithParticles fill)
        {
            fill();
            initialize();
        }

		/**
		 * \brief
		 *   Get the CellConstructor for a Particle.
		 *
		 * \param[in]  index  The index of the Particle
		 *
		 * \return
		 *   The CellConstructor for the particle.
		 */
        CellConstructor getCell(SizeType index) const
        {
            assert(initialized_ &&
                "Remember to call diagram.initialize() before doing Voronoi computations");
            VERIFY(index < particles_.size());
            auto x = CellConstructor(*this, index, particles_[index]);
            return x;
        }

		/**
		 * \brief
		 *   Get the CellConstructor for a Particle.
		 *
		 * \param[in]  index        The index of the Particle
		 * \param[in]  targetGroup  The TargetGroup for cutting the Cell
		 *
		 * \return
		 *   The CellConstructor for the particle.
		 */
        CellConstructor getCell(SizeType index, const TargetGroup& targetGroup) const
        {
            assert(initialized_ &&
                "Remember to call diagram.initialize() before doing Voronoi computations");
            VERIFY(index < particles_.size());
            return CellConstructor(*this, index, particles_[index], targetGroup);
        }

		/**
		 * \brief
		 *   Get the CellConstructor for a Particle using a specfic search radius.
		 *
		 * \param[in]  index         The index of the Particle
		 * \param[in]  searchRadius  The search radius to use for finding neighbors of the Particle
		 *
		 * \return
		 *   The CellConstructor for the particle.
		 */
        CellConstructor getCell(SizeType index, double searchRadius) const
        {
            assert(initialized_ &&
                "Remember to call diagram.initialize() before doing Voronoi computations");
            VERIFY(index < particles_.size());
            VERIFY(searchRadius > 0);
            return CellConstructor(*this, index, particles_[index], searchRadius);
        }

		/**
		 * \brief
		 *   Get the CellConstructor for a Particle using a specfic search radius.
		 *
		 * \param[in]  index         The index of the Particle
		 * \param[in]  targetGroup   The TargetGroup for cutting the Cell
		 * \param[in]  searchRadius  The search radius to use for finding neighbors of the Particle
		 *
		 * \return
		 *   The CellConstructor for the particle.
		 */
        CellConstructor getCell(SizeType index, const TargetGroup& targetGroup, double searchRadius) const
        {
            assert(initialized_ &&
                "Remember to call diagram.initialize() before doing Voronoi computations");
            VERIFY(index < particles_.size());
            VERIFY(searchRadius > 0);
            return CellConstructor(*this, index, particles_[index], targetGroup, searchRadius);
        }

		/**
		 * \brief
		 *   Get the CellConstructor for a hypothetical particle.
		 *
		 * \param[in]  x  The x-coordinate of the particle
		 * \param[in]  y  The y-coordinate of the particle
		 * \param[in]  z  The z-coordinate of the particle
		 *
		 * \return
		 *   The CellConstructor for the particle.
		 *
		 * \remark
		 *   Used to find a cell for a particle that is not actually in the Diagram.
		 */
        CellConstructor getCell(double x, double y, double z) const
        {
            assert(initialized_ &&
                "Remember to call diagram.initialize() before doing Voronoi computations");
            return CellConstructor(*this, NO_INDEX, {x, y, z});
        }

		/**
		 * \brief
		 *   Get the CellConstructor for a Particle.
		 *
		 * \param[in]  x            The x-coordinate of the particle
		 * \param[in]  y            The y-coordinate of the particle
		 * \param[in]  z            The z-coordinate of the particle
		 * \param[in]  targetGroup  The TargetGroup for cutting the Cell
		 *
		 * \return
		 *   The CellConstructor for the particle.
		 *
		 * \remark
		 *   Used to find a cell for a particle that is not actually in the Diagram.
		 */
        CellConstructor getCell(double x, double y, double z, const TargetGroup& targetGroup) const
        {
            assert(initialized_ &&
                "Remember to call diagram.initialize() before doing Voronoi computations");
            return CellConstructor(*this, NO_INDEX, {x, y, z}, targetGroup);
        }

		/**
		 * \brief
		 *   Get the CellConstructor for a Particle using a specfic search radius.
		 *
		 * \param[in]  x             The x-coordinate of the particle
		 * \param[in]  y             The y-coordinate of the particle
		 * \param[in]  z             The z-coordinate of the particle
		 * \param[in]  searchRadius  The search radius to use for finding neighbors of the Particle
		 *
		 * \return
		 *   The CellConstructor for the particle.
		 *
		 * \remark
		 *   Used to find a cell for a particle that is not actually in the Diagram.
		 */
        CellConstructor getCell(double x, double y, double z, double searchRadius) const
        {
            assert(initialized_ &&
                "Remember to call diagram.initialize() before doing Voronoi computations");
            VERIFY(searchRadius > 0);
            return CellConstructor(*this, NO_INDEX, {x, y, z}, searchRadius);
        }

		/**
		 * \brief
		 *   Get the CellConstructor for a Particle using a specfic search radius.
		 *
		 * \param[in]  x             The x-coordinate of the particle
		 * \param[in]  y             The y-coordinate of the particle
		 * \param[in]  z             The z-coordinate of the particle
		 * \param[in]  targetGroup   The TargetGroup for cutting the Cell
		 * \param[in]  searchRadius  The search radius to use for finding neighbors of the Particle
		 *
		 * \return
		 *   The CellConstructor for the particle.
		 *
		 * \remark
		 *   Used to find a cell for a particle that is not actually in the Diagram.
		 */
        CellConstructor getCell(double x, double y, double z, const TargetGroup& targetGroup, double searchRadius) const
        {
            assert(initialized_ &&
                "Remember to call diagram.initialize() before doing Voronoi computations");
            VERIFY(searchRadius > 0);
            return CellConstructor(*this, NO_INDEX, {x, y, z}, targetGroup, searchRadius);
        }

		/**
		 * \brief
		 *   Create a TargetGroup containing the specified groups.
		 *
		 * \param[in]  groups  The groups to include in the TargetGroup.
		 *
		 * \return
		 *   The desired TargetGroup.
		 */
		template <typename... Groups>
		TargetGroup targetGroups(Groups... groups)
		{
			return TargetGroup {groups...};
		}

		// dani hacky constructor thing
		TargetGroup targetGroups(std::vector<int> groups)
		{
			TargetGroup targets = TargetGroup();
			for (unsigned int i = 0; i < groups.size(); ++i) {
				targets.insert(groups[i]);
			}
			return targets;
		}

		/**
		 * \brief
		 *   Create a SourceGroup containing particles from the specified groups. The SourceGroup
		 *   can be treated like a vector of indices of Particles in the Diagram.
		 *
		 * \param[in]  groups  The groups to be included.
		 *
		 * \return
		 *   The desired SourceGroup.
		 *
		 * \remark
		 *   The SourceGroup does not actually contain any group information once created, and
		 *   is simply included for convenience.
		 */
		template <typename... Groups>
		SourceGroup sourceGroups(Groups... groups)
		{
			std::unordered_set<int> sources = std::unordered_set<int> {groups...};
			SourceGroup src;

			int end = particles_.size();
			for (int i = 0; i < end; ++i) {
				if (sources.count(groups_[i])) {
					src.push_back(i);
				}
			}

			return src;
		}

		/**
		 * \brief
		 *   Compute the Voronoi cell for a Particle.
		 *
		 * \param[in]  particleIndex  The index of the Particle
		 * \param[in]  position       The position of the Particle
		 * \param[in]  poly           A reference to the Polyhedron to be cut to compute the cell
		 * \param[in]  search         A reference to an ExpandingSearch for the Particle
		 * \param[in]  targetGroup    If not empty, only cut the particle with the specified groups.
		 * \param[in]  searchRadius   The search radius to look for neighbors of the particle. Optional.
		 *
		 * \remarks
		 *   Uses the efficient expanding search strategy to find neighbors if searchRadius is not specified.
		 */
        void computeVoronoiCell(SizeType particleIndex, Vector3 position, Polyhedron& poly,
                                SDS::ExpandingSearch& search, const TargetGroup* targetGroup= nullptr,
								double searchRadius = NO_RADIUS) const
        {
            VERIFY(poly.isClear());
            poly = containerShape_;
            poly.translate(-position);

            // Use expanding search if no radius is given
            if (searchRadius == NO_RADIUS) {
    			searchRadius = std::numeric_limits<double>::max();
                for (search.startSearch(spatialStructure_, position);
                        !search.done();
    				    search.expandSearch(searchRadius = poly.maximumNeighborDistance()))
                {
					if (targetGroup == nullptr) {
						for (SizeType index : search) {
							Vector3 shiftedPosition = particles_[index] - position;

							if (index != particleIndex
								&& mag2(shiftedPosition) <= searchRadius)
							{
                    			poly.cutWithPlane(
									originalIndices_[index],
									Plane::halfwayFromOriginTo(shiftedPosition)
                                    );
							}
						}
					}

					else {
						for (SizeType index : search) {
							Vector3 shiftedPosition = particles_[index] - position;
							if (index != particleIndex
								&& mag2(shiftedPosition) <= searchRadius
								&& targetGroup->count(groups_[index]) != 0)
							{
								poly.cutWithPlane(
									originalIndices_[index],
									Plane::halfwayFromOriginTo(shiftedPosition)
									);
							}
						}
					}
                }
            }

            // Otherwise (given radius), do a basic search
            else {
				std::vector<SizeType> searchPoints;
				spatialStructure_.search(position, searchRadius, searchPoints);
				if (targetGroup == nullptr) {
					for (SizeType index : searchPoints) {
						if (index != particleIndex) {
							poly.cutWithPlane(
								originalIndices_[index],
								Plane::halfwayFromOriginTo(particles_[index] - position)
								);
						}
					}
				}

				else {
					for (SizeType index : searchPoints) {
						if (index != particleIndex && targetGroup->count(groups_[index]) != 0) {
							poly.cutWithPlane(
								originalIndices_[index],
								Plane::halfwayFromOriginTo(particles_[index] - position)
								);
						}
					}
				}
            }
        }
    };

	/// A wrapper function for calling Diagram::ComputeVoronoiCell.
    void Cell::computeVoronoiCell()
    {
        diagram_->computeVoronoiCell(index_, position_, poly_, search_, targetGroup_, searchRadius_);
    }

	/**
	 * \brief
	 *   A wrapper function for finding the Particle at an index of a Diagram.
	 *
	 * \return
	 *   The desired Particle.
	 */
    const Vector3& Cell::getPosition() const
    {
        return diagram_->particles_[index_];
    }

    SizeType Cell::getOriginalIndex() const
    {
        return (index_ == NO_INDEX)
                ? NO_INDEX
                : diagram_->originalIndices_[index_];
    }

    SizeType Cell::getInternalIndex() const
    {
        // index_ might be NO_INDEX, but in that
        // event we do in fact want to return NO_INDEX
        return index_;
    }

	template<typename Collection>
	void Cell::computeFaces(Collection& result)
	{
		ensurePolyComputed();

		auto output = std::back_inserter(result);
		for (auto fit = poly_.faces_.begin(); fit != poly_.faces_.end(); ++fit) {
			*output++ = VoronoiFace(fit.index(), *this);
		}
	}

	void Cell::computeNeighborCloud(double radius, std::vector<SizeType>& result)
	{
		diagram_->spatialStructure_.search(position_, radius, result);

		if (targetGroup_ != nullptr) {
			std::remove_if(result.begin(), result.end(), [&](SizeType i) {
					return targetGroup_->count(i) == 0;
				});
		}
	}

}

redpajama_set_name: RedPajamaGithub


text:
function Plane(status) {
  this.status = 'airbourne';
}

Plane.prototype.readStatus = function () {
  return this.status;
};

Plane.prototype.changeStatus = function () {
  if (this.status === 'airbourne') {
    this.status = 'landed';
  } else (this.status === 'landed') {
    this.status = 'airbourne';
  }
};

// Plane.prototype.changeStatus = function () {
//   if (this.status === 'landed') {
//     return this.status;
//   }
//   else {
//     this.status = 'landed';
//   }
//   };


text:
require File.dirname(__FILE__) + '/../spec_helper'

describe "A class descending from AbstractParser" do

  before(:each) do
    @class = Class.new(AbstractParser)
    if @class.respond_to? :autocompile
      @class.autocompile(false)
    end
    @parser = @class.new
  end

  it "should raise an exception if it hasn't been compiled" do
    lambda { @parser.parse(:mock) }.should raise_error(RuntimeError)
  end

  it "should have hash-like capabilities" do
    @parser.should respond_to(:[])
    @parser.should respond_to(:[]=)

    @parser[:var]= :foo
    @parser[:var].should == :foo
  end

  it "should be able to specify a production with a name (and in case the mode), some tokens and blocks" do
    @class.should respond_to(:production)

    lambda do
      @class.production(:name) do |prod|
        prod.token(:first_token) { "first" }
        prod.token(:second_token) { "second" }
      end
    end.should_not raise_error

    l = lambda do
      @class.production(:name,:mode) do |prod|
        prod.mode.should == :mode
        prod.token(:first_token) { "first" }
        prod.token(:second_token) { "second" }
      end
    end
    l.should_not raise_error
  end

  it "should memorize its productions in a hash of ProductionSpecification" do
    @class.should respond_to(:productions)
    @class.productions.should == {}

    @class.production(:name) do |prod|
      prod.token(:first_token) { "first" }
      prod.token(:second_token) { "second" }
    end

    @class.productions[:name].should be_kind_of(ProductionSpecification)
    @class.productions[:name].tokens[:first_token].should_not be_nil
    @class.productions[:name].tokens[:second_token].should_not be_nil
  end

  it "should memorize the scope" do
    @class.should respond_to(:scope)
    @class.scope.should be_nil

    @class.scope(:a_new_scope)
    @class.scope.should == :a_new_scope
  end

  it "should have an 'autocompile' attribute" do
    @class.should respond_to(:autocompile)
    @class.autocompile.should == false
    lambda { @class.autocompile(true) }.should_not raise_error
    @class.autocompile.should == true
    lambda { @class.autocompile(false) }.should_not raise_error
    @class.autocompile.should == false
  end

  it "should have a code attribute" do
    @class.should respond_to(:code)
    @class.code.should be_nil
    @class.scope(:a_scope)
    @class.production(:a_scope) {}
    @class.compile
    @class.code.should_not == ""
  end

  it "should initialize and fill 'code' when 'autocompile' is true" do
    @class.autocompile false
    @class.code.should be_nil
    @class.autocompile true
    @class.code.should == ""
    @class.scope(:a_scope)
    @class.code.should_not == ""
    @class.production(:a_scope) {}
    @class.code.should_not == ""
  end

  it "should raise if a production has an unknown mode" do
    l = lambda {
      @class.scope :first
      @class.production(:first) { |p| p.mode = :error_mode }
      @class.compile
    }
    l.should raise_error(RuntimeError)
  end
end

describe "A class descending from AbstractParser with some productions specified" do

  before(:each) do
    @class = Class.new(AbstractParser)
    if @class.respond_to? :autocompile
      @class.autocompile(false)
    end
    @parser = @class.new

    @mock_single = mock "Single"
    @mock_single.should_receive(:respond_to?).with(:to_ary).and_return(false)
    @mock_single.should_receive(:respond_to?).with(:call).and_return(true)
    @class.production(:single) do |prod|
      prod.token(:single_token,@mock_single)
    end

    @mock_recursive = mock "Recursive"
    @mock_recursive.should_receive(:respond_to?).with(:to_ary).and_return(false)
    @mock_recursive.should_receive(:respond_to?).with(:call).and_return(true)
    @class.production(:recursive) do |prod|
      prod.mode=:recursive
      prod.token(:recursive_token,@mock_recursive)
    end

    @class.should respond_to(:compile)
    @class.should respond_to(:compiled)
  end

  it "should be compiled with a scope specified" do
    @class.scope(:single)

    @class.compiled.should == false
    lambda { @class.compile }.should_not raise_error
    @class.compiled.should == true
  end

  it "shouldn't be compiled without a correct scope" do

    @class.compiled.should == false
    lambda { @class.compile }.should raise_error(RuntimeError)
    @class.compiled.should == false

    @class.scope(:fake_scope)
    @class.compiled.should == false
    lambda { @class.compile }.should raise_error(RuntimeError)
    @class.compiled.should == false

  end

  it "shouldn't be compiled if a production has an invalid mode" do
    @class.production(:error) { |prod| prod.mode = :this_is_an_invalid_mode }
    lambda { @class.compile }.should raise_error(RuntimeError)
  end

  it "should mantain an attribute 'code' which mantains the built code" do
    @class.scope(:single)

    @class.should respond_to(:code)
    @class.code.should be_nil
    lambda { @class.compile }.should_not raise_error
    @class.code.should_not be_nil
    @class.code.should be_kind_of(String)
  end

end

describe "An instance of a class descending from AbstractParser with some productions specified after being compiled" do

  before(:each) do
    @class = Class.new(AbstractParser)
    if @class.respond_to? :autocompile
      @class.autocompile(false)
    end
    @parser = @class.new

    @class.scope :single

    @mock_single = mock "Single"
    @mock_single.should_receive(:respond_to?).with(:to_ary).and_return(false)
    @mock_single.should_receive(:respond_to?).with(:call).and_return(true)
    @class.production(:single) do |prod|
      prod.token(:single_token,@mock_single)
    end

    @mock_recursive = mock "Recursive"
    @mock_recursive.should_receive(:respond_to?).with(:to_ary).and_return(false)
    @mock_recursive.should_receive(:respond_to?).with(:call).and_return(true)
    @class.production(:recursive) do |prod|
      prod.mode = :recursive
      prod.token(:recursive_token,@mock_recursive)
    end

    lambda { @class.compile }.should_not raise_error
  end

  it "should expose parse_NAME methods" do
    @parser.should respond_to("parse_single")
    @parser.should respond_to("parse_recursive")
  end

  it "should have a productions method which alias the class one" do
    @parser.should respond_to(:productions)
    @parser.productions.should == @class.productions
  end

  it "should have a parse method which accept a scanner" do
    @parser.should respond_to(:parse)

    scanner = mock "Scanner"
    scanner.should_receive(:next).any_number_of_times
    scanner.should_receive(:current).twice.and_return(:single_token,nil)

    @mock_single.should_receive(:call).with(nil,scanner,@parser).and_return(:good_result)

    lambda { @parser.parse(scanner).should == :good_result }.should_not raise_error

    scanner = mock "Scanner2"
    scanner.should_receive(:next).any_number_of_times
    scanner.should_receive(:current).exactly(5).and_return(:single_token)

    @mock_single.should_receive(:call).with(nil,scanner,@parser).and_return(:good_result)

    lambda { @parser.parse(scanner) }.should raise_error(ParserError)
  end

end

describe "A class descending from AbstractParser should autocompile" do

  before(:each) do
    @class = Class.new(AbstractParser)
    @parser = @class.new
    #@class.autocompile true
  end

  it "the scope" do
    @class.scope :scope
    @parser.should respond_to(:parse)
    @parser.should_receive(:parse_scope).once
    scanner = mock "Scanner"
    scanner.should_receive(:next).once
    scanner.should_receive(:current).once
    @parser.parse(scanner)
  end

  it "a 'single' production" do
    @mock_single = mock "Single"
    @mock_single.should_receive(:respond_to?).with(:to_ary).and_return(false)
    @mock_single.should_receive(:respond_to?).with(:call).and_return(true)
    @class.production(:single) do |prod|
      prod.token(:single_token,@mock_single)
    end
    @parser.should respond_to(:parse_single)
  end

  it "a 'recursive' production" do
    @mock_recursive = mock "Recursive"
    @mock_recursive.should_receive(:respond_to?).with(:to_ary).and_return(false)
    @mock_recursive.should_receive(:respond_to?).with(:call).and_return(true)
    @class.production(:recursive) do |prod|
      prod.mode = :recursive
      prod.token(:recursive_token,@mock_recursive)
    end
    @parser.should respond_to(:parse_recursive)
  end
end