function Geofences() {
    var
        _this = this,
        items = [],
        tmpItem = null,

        draw = null,

        loadFails = 0;

    _this.events = function() {
        $('#ajax-geofences')
            .on('multichanged', 'input[data-toggle="checkbox"]', function(e, data){
                dd('multichanged', e, data);

                _this.activeMulti( $( this ).val(), data.values, $( this ).is(':checked') );
            })
            .on('multichange', 'input[type="checkbox"]', function(e, data){});

        $(document).on('change', '#geofences_export select[name="export_type"]', function() {
            dd( 'geofences_export.export_type.change' );

            var $container = $('#geofences_export');

            $.ajax({
                type: 'GET',
                url: app.urls.geofencesExportType,
                data: {
                    type: $(this).val()
                },
                success: function (res) {
                    $('.geofences-export-input', $container).html(res);

                    initComponents('#geofences_export');
                }
            });
        });

        $(document)
            .on('hidden.bs.tab', '[data-toggle="tab"][href="#geofencing_create"]', function (e) {
                _this.cancelEditing();
            })
            .on('hidden.bs.tab', '[data-toggle="tab"][href="#geofencing_edit"]', function (e) {
                _this.cancelEditing();
            });

        $(document).on('geofence.created geofence.updated', function(e, geofence){
            dd( 'geofence', e, geofence );

            if ( ! geofence.isLayerVisible () )
                return;

            geofence.removeLayer();

            app.map.addLayer( geofence.getLayer() );
        });

        app.map.on(L.Draw.Event.CREATED, function (e) {
            dd('geofences.draw:created');

            if ( ! tmpItem )
                return;

            var type = e.layerType,
                layer = e.layer;

            if (type === 'polygon' || type === 'circle') {
                dd('app.drawnItems.addLayer');

                var _polygon = tmpItem.setLayer(layer);

                app.map.addLayer(_polygon);

                tmpItem.enableEdit();
            }
        });

        $('#geofencing_tab').on('keyup', 'input[name="search"]', $.debounce(100, function(){
            sidebarSearch( $(this).val().toLowerCase(), items, 'data-geofence-id', '#ajax-geofences');
        }));
    };

    _this.init = function() {
        _this.events();
    };

    _this.list = function() {
        var dataType = 'html';

        dd('geofences.list');

        var $container = $('#ajax-geofences');

        $.ajax({
            type: 'GET',
            dataType: dataType,
            url: app.urls.geofences,
            beforeSend: function() {
                loader.add( $container );
            },
            success: function(response) {
                dd('geofences.list.success');

                $container.html(response);

                initComponents( $container );

                loadFails = 0;
            },
            complete: function() {
                loader.remove( $container );
            },
            error: function(jqXHR, textStatus, errorThrown) {
                handlerFail(jqXHR, textStatus, errorThrown);

                loadFails++;

                if ( loadFails >= 5 ) {
                    app.notice.error('Failed to recover geofences.');
                }
                else {
                    _this.list();
                }
            }
        });
    };

    _this.get = function( id ) {
        var _item = items[ id ];

        if ( typeof _item === "Geofence" )
            return null;

        return _item;
    };

    _this.add = function(data){
        data = data || {};

        if ( typeof data === 'string' ) {
            data = JSON.parse(data);
        }

        if ( !data ) {
            return;
        }

        if (typeof items[ data.id ] === 'undefined' ) {
            items[ data.id ] = new Geofence(data);
        } else {
            items[ data.id ].update(data);
        }
    };

    _this.addMulti = function(all) {

        $.each(all , function( index, data ) {
            _this.add(data);
        });
    };

    _this.active = function(geofence_id, value) {
        var _item = items[geofence_id];

        if ( !_item )
            return;

        _item.active( value );

        if (value) {
            if ( _item.isLayerVisible() )
                app.map.addLayer( _item.getLayer() );
        } else {
            app.map.removeLayer( _item.getLayer() );
        }

        //_item.refreshlayer();

        _this.changeActive( geofence_id, value );
    };

    _this.activeMulti = function(group_id, changeItems, value) {
        $.each( changeItems, function(index, geofence_id) {
            var _geofence = items[geofence_id];

            if ( !_geofence )
                return;

            _geofence.active( value );

            if (value) {
                if ( _geofence.isLayerVisible() )
                    app.map.addLayer( _geofence.getLayer() );
            } else {
                app.map.removeLayer( _geofence.getLayer() );
            }
        });

        _this.changeActive(changeItems, value);
    };

    _this.select = function( id ) {
        if ( ! _this.get(id) )
            return;

        _this.fitBounds(id);
    };

    _this.fitBounds = function( id, currentZoom ) {
        var _bounds = [];
        var _item = _this.get( id );

        _bounds = _item.getBounds();

        if ( _bounds ) {
            var _option = app.getMapPadding();

            if ( currentZoom && typeof currentZoom === 'boolean' )
                currentZoom = app.map.getZoom();

            if ( currentZoom && app.map.getBoundsZoom(_bounds) > currentZoom )
                _option.maxZoom = currentZoom;

            app.map.fitBounds( _bounds, _option );
        }
    };

    _this.changeActive = function( id, status ) {
        dd( 'geofences.changeActive', id, status );

        $.ajax({
            type: 'POST',
            url: app.urls.geofenceChangeActive,
            data: {
                id: id,
                active: status
            },
            error: handlerFail
        });
    };

    _this.toggleGroup = function( id ) {
        dd( 'geofences.toggleGroup', id );

        $.ajax({
            type: 'GET',
            url: app.urls.geofenceToggleGroup,
            data: {
                id: id
            }
        });
    };

    _this.create = function() {
        tmpItem = new Geofence();

        _this.setDrawer();

        _this.initForm(tmpItem);

        app.openTab('geofencing_create');
    };

    _this.setDrawer = function(type) {
        type = type || 'polygon';

        if ( draw ) {
            draw.disable();
            app.map.removeLayer(draw);
        }

        switch (type) {
            case 'polygon':
                draw = new L.Draw.Polygon( app.map );
                break;
            case 'circle':
                draw = new L.Draw.Circle( app.map );
                break;
            default:
                console.log('default drawer');
                draw = new L.Draw.Polygon( app.map );
        }

        draw.enable();
    };

    _this.store = function() {
        var modal = $('#geofencing_create');
        var form = modal.find('form');
        var url = form.attr('action');
        var method = form.find('input[name="_method"]').val();
        var data = form.serializeArray();

        if (tmpItem.getLayer() instanceof L.Polygon) {
            data.push({
                name: 'polygon',
                value: JSON.stringify( tmpItem.getLatLngs() )
            });
        }
        else if (tmpItem.getLayer() instanceof L.Circle) {
            data.push(
                {
                    name: 'radius',
                    value: JSON.stringify( tmpItem.getLayer().getRadius() )
                },
                {
                    name: 'polygon',
                    value: JSON.stringify( tmpItem.getLayer().toPolygon() )
                },
                {
                    name: 'center',
                    value: JSON.stringify( tmpItem.getBounds().getCenter())
                },
                {
                    name: 'type',
                    value: 'circle'
                }
            );
        }

        method = (typeof method != 'undefined' ? method : 'POST');

        $modal.postData(url, method, modal, data);
    };

    _this.edit = function(id) {

        tmpItem = items[id];

        tmpItem.enableEdit();

        _this.initForm(tmpItem);

        app.map.fitBounds( tmpItem.getBounds() );

        app.openTab( 'geofencing_edit' );
    };

    _this.update = function() {
        var modal = $('#geofencing_edit');
        var form = modal.find('form');
        var url = form.attr('action');
        var method = form.find('input[name="_method"]').val();
        var data = form.serializeArray();


        if (tmpItem.getLayer() instanceof L.Polygon) {
            data.push(
                {
                    name: 'polygon',
                    value: JSON.stringify( tmpItem.getLatLngs() )
                },
                {
                    name: 'id',
                    value: tmpItem.id()
                },
                {
                    name: 'type',
                    value: 'polygon'
                }
            );
        }
        else if (tmpItem.getLayer() instanceof L.Circle) {
            data.push(
                {
                    name: 'id',
                    value: tmpItem.id()
                },
                {
                    name: 'radius',
                    value: JSON.stringify( tmpItem.getLayer().getRadius() )
                },
                {
                    name: 'polygon',
                    value: JSON.stringify( tmpItem.getLayer().toPolygon() )
                },
                {
                    name: 'center',
                    value: JSON.stringify( tmpItem.getBounds().getCenter() )
                },
                {
                    name: 'type',
                    value: 'circle'
                }
            );
        }


        method = (typeof method != 'undefined' ? method : 'POST');

        $modal.postData(url, method, modal, data);
    };

    _this.delete = function(id, confirmed) {
        if ( ! confirmed ) {
            $('#deleteGeofence button[onclick]').attr('onclick', 'app.geofences.delete('+id+', true);');

            return;
        }

        _this.remove( id );

        $modal.postData(
            app.urls.geofenceDelete,
            'DELETE',
            $('#geofencing_edit'),
            {
                id: id,
                _method: 'DELETE'
            }
        );
    };

    _this.remove = function( id ) {
        var _item = items[id];

        if ( !_item )
            return;

        if ( _item.isLayerVisible() )
            app.map.removeLayer( _item.getLayer() );

        delete items[_item];
    };

    _this.import = function() {
        var modal = $('#geofences_import');
        var form = modal.find('form');
        var url = form.attr('action');
        var method = form.find('input[name="_method"]').val();
        var data = new FormData(form['0']);

        method = (typeof method != 'undefined' ? method : 'POST');

        $modal.postData(url, method, modal, data, true);
    };

    _this.tmpUpdate = function(data) {
        if ( ! tmpItem )
            return;

        dd( 'geofences.tmpUpdate' );

        var $container;

        if ( tmpItem.id() ) {
            $container = $('#geofencing_edit');
        } else {
            $container = $('#geofencing_create');
        }

        var _options = {
            name: $container.find('input[name="name"]').val(),
            polygon_color: $container.find('input[name="polygon_color"]').val(),
        };

        _options = $.extend({}, _options, data || {});
        tmpItem.update(_options);

        $( '[name="coordinates"]', $container ).val( JSON.stringify( tmpItem.getBounds() ) );

        dd('geofences.map.click.data', _options);
    };

    _this.initForm = function( item ) {
        var $container;

        if ( item.id() ) {
            $container = $('#geofencing_edit');
        } else {
            $container = $('#geofencing_create');
        }

        if ( ! item.id() ) {
            item.update({
                name: $container.find('input[name="name"]').val(),
                polygon_color: $container.find('input[name="polygon_color"]').val(),
            });
        }

        $( '[name="id"]', $container ).val( item.options().id );
        $( '[name="name"]', $container ).val( item.options().name );
        $( '[name="polygon_color"]', $container ).val( item.options().polygon_color );
        $( '[name="coordinates"]', $container ).val( JSON.stringify( item.getBounds() ) );
        $( '[name="type"]', $container ).val( item.options().type );
        $( '[name="type"]', $container ).selectpicker('refresh');
        //$(  '#type option[value="'+ item.options().type +'"]', $container ).prop('selected', true);
        //$(  '#type', $container).trigger('change').selectpicker('refresh');
    };

    _this.hideLayers = function() {
        $.each(items , function( geofence_id, geofence ) {
            if ( ! geofence )
                return;

            geofence.removeLayer();
        });
    };

    _this.showLayers = function() {
        $.each(items , function( geofence_id, geofence ) {
            if ( ! geofence )
                return;

            if ( ! geofence.isLayerVisible() )
                return;

            var _layer = geofence.getLayer();

            if ( ! _layer)
                return;

            app.map.addLayer( _layer );
        });
    };

    _this.cancelEditing = function() {
        if ( draw ) {
            draw.disable();
            app.map.removeLayer(draw);
            draw = null;
        }

        var $container;

        if ( tmpItem.id() ) {
            $container = $('#geofencing_edit');
        } else {
            $container = $('#geofencing_create');
        }

        console.log($container);

        if ( tmpItem.id() ) {
            app.map.addLayer(items[tmpItem.id()].getLayer());
        }

        tmpItem.removeLayer();
        tmpItem = null;

        _this.showLayers();

        $('input[name="name"]', $container).val('');
        $('input[name="polygon_color"]', $container).val('');
    };

    _this.changeType = function (element) {
        var $type = $(element).val();
        var $id  = tmpItem.id();
        var _options = {};

        if (draw)
            _this.setDrawer($type);

        _options.type = $type;

        tmpItem.removeLayer();

        switch (_options.type) {
            case 'polygon':
                _options = $.extend({}, _options, tmpItem.circleToPolygon());
                break;
            case 'circle':
                _options = $.extend({}, _options, tmpItem.polygonToCircle());
                break;
        }

        tmpItem.enableEdit(_options);

        app.map.addLayer( tmpItem.getLayer() );

        if (tmpItem.getBounds().isValid())
            app.map.fitBounds( tmpItem.getBounds() );
    }
}

function geofencing_create_modal_callback(res) {
    if (res.status == 1) {
        app.notice.success( window.lang.successfully_created_geofence );

        app.openTab('geofencing_tab');
        app.geofences.list();
    }
}

function geofencing_edit_modal_callback(res) {
    if (res.status == 1) {
        app.notice.success(window.lang.successfully_updated_geofence);

        app.openTab('geofencing_tab');
        app.geofences.list();
    }
}

function geofences_import_modal_callback(res) {
    app.notice.success(res.message);

    app.openTab('geofencing_tab');

    app.geofences.list();
}


