<commit_before>(function (FMS, Backbone, _, $) {
    _.extend( FMS, {
        AroundView: FMS.LocatorView.extend({
            template: 'around',
            id: 'around-page',

            events: {
                'pagehide': 'destroy',
                'pagebeforeshow': 'beforeDisplay',
                'pageshow': 'afterDisplay',
                'vclick #locate_cancel': 'goSearch',
                'vclick #login-options': 'goLogin',
                'vclick #view-my-reports': 'goReports',
                'vclick #search': 'goSearch',
                'vclick .ui-input-clear': 'clearSearchErrors',
                'blur #pc': 'clearSearchErrors',
                'vclick #relocate': 'centerMapOnPosition',
                'vclick #cancel': 'onClickCancel',
                'vclick #confirm': 'onClickReport',
                'vclick #confirm-map': 'onClickReport',
                'vclick #mark-here': 'onClickMark',
                'vclick #reposition': 'onClickReposition',
                'vclick a.address': 'goAddress',
                'submit #postcodeForm': 'search'
            },

            render: function(){
                if ( !this.template ) {
                    console.log('no template to render');
                    return;
                }
                template = _.template( tpl.get( this.template ) );
                if ( this.model ) {
                    this.$el.html(template({ model: this.model.toJSON(), user: FMS.currentUser.toJSON() }));
                } else {
                    this.$el.html(template());
                }
                this.afterRender();
                return this;
            },

            beforeDisplay: function() {
                this.origPcPlaceholder = $('#pc').attr('placeholder');
                $('a[data-role="button"]').hide();
                $('#view-my-reports').hide();
                $('#login-options').hide();
                $('#postcodeForm').hide();
                $('#cancel').hide();
                $('#map_box').removeClass('background-map');
                this.fixPageHeight();
                $('#map_box').on('touchend', function() { if ( ! $('#popup').length ) { $('#OpenLayers_Control_Crosshairs_crosshairs').show(); } } );
            },

            afterDisplay: function() {
                if ( FMS.isOffline ) {
                    $('#locating').hide();
                    this.navigate( 'offline' );
                } else if ( this.model && this.model.get('lat') ) {
                    var modelInfo = { coordinates: { latitude: this.model.get('lat'), longitude: this.model.get('lon') } };
                    this.setMapPosition(modelInfo);
                    this.displayButtons(true);
                    this.setReportPosition({ lat: this.model.get('lat'), lon: this.model.get('lon') }, true);
                    this.listenTo(FMS.locator, 'gps_current_position', this.positionUpdate);
                    FMS.locator.trackPosition();
                } else if ( FMS.currentPosition ) {
                    var info = { coordinates: FMS.currentPosition };
                    FMS.currentPosition = null;
                    if ( !fixmystreet.map ) {
                        this.setMapPosition(info);
                    }
                    this.displayButtons(false);
                    this.listenTo(FMS.locator, 'gps_current_position', this.positionUpdate);
                    FMS.locator.trackPosition();
                } else {
                    this.locate();
                    this.displayButtons(false);
                }
            },

            setMapPosition: function( info ) {
                var coords = info.coordinates;
                fixmystreet.latitude = coords.latitude;
                fixmystreet.longitude = coords.longitude;

                if ( !fixmystreet.map ) {
                    show_map();
                } else {
                    FMS.currentPosition = coords;
                    var centre = this.projectCoords( coords );
                    fixmystreet.map.panTo(centre);
                }
            },

            gotLocation: function( info ) {
                $('#relocate').show();
                this.finishedLocating();

                this.listenTo(FMS.locator, 'gps_current_position', this.positionUpdate);

                this.located = true;
                this.locateCount = 21;

                this.setMapPosition( info );

                FMS.locator.trackPosition();
                // FIXME: not sure why I need to do this
                fixmystreet.select_feature.deactivate();
                fixmystreet.select_feature.activate();
                fixmystreet.nav.activate();
            },

            positionUpdate: function( info ) {
                $('#relocate').show();
                FMS.currentPosition = info.coordinates;
                var centre = this.projectCoords( info.coordinates );

                var point = new OpenLayers.Geometry.Point( centre.lon, centre.lat );

                fixmystreet.location.removeAllFeatures();
                    var x = new OpenLayers.Feature.Vector(
                        point,
                        {},
                        {
                            graphicZIndex: 3000,
                            graphicName: 'circle',
                            'externalGraphic': 'images/gps-marker.svg',
                            pointRadius: 16
                        }
                    );
                fixmystreet.location.addFeatures([ x ]);
            },

            centerMapOnPosition: function(e) {
                e.preventDefault();
                if ( !fixmystreet.map ) {
                    return;
                }

                // if there isn't a currentPosition then something
                // is up so we probably should not recenter
                if ( FMS.currentPosition ) {
                    fixmystreet.map.panTo(this.projectCoords( FMS.currentPosition ));
                }
            },

            failedLocation: function( details ) {
                this.finishedLocating();
                this.locateCount = 21;
                var msg = '';
                if ( details.msg ) {
                    msg = details.msg;
                } else {
                    msg = FMS.strings.location_problem;
                }
                if ( !fixmystreet.map ) {
                    $('#relocate').hide();
                    $('#mark-here').hide();
                    $('#pc').focus();
                }
                $('#front-howto').html('
' + msg + '</msg>');
                $('#front-howto').show();
            },

            displayButtons: function(isLocationSet) {
                if ( fixmystreet.map ) {
                    fixmystreet.nav.activate();
                    fixmystreet.actionafterdrag.activate();
                }
                if (isLocationSet) {
                    $('#cancel').addClass('ui-btn-left').show();
                    $('#confirm').addClass('ui-btn-right ui-btn-icon-right').show();
                    $('#confirm-map').show();
                    $('#view-my-reports').hide();
                    $('#login-options').hide();
                    $('#mark-here').hide();
                    $('#postcodeForm').hide();
                    if ( fixmystreet.map ) {
                        fixmystreet.markers.setVisibility(false);
                        fixmystreet.select_feature.deactivate();
                        fixmystreet.bbox_strategy.deactivate();
                    }
                } else {
                    $('#cancel').hide().removeClass('ui-btn-left');
                    $('#confirm').hide().removeClass('ui-btn-right ui-btn-icon-right');
                    $('#confirm-map').hide();
                    $('#view-my-reports').show();
                    $('#login-options').show();
                    $('#mark-here').show();
                    $('#postcodeForm').show();
                    $('#reposition').hide();
                    if ( fixmystreet.map ) {
                        fixmystreet.bbox_strategy.activate();
                        fixmystreet.report_location.setVisibility(false);
                        fixmystreet.markers.setVisibility(true);
                        fixmystreet.select_feature.deactivate();
                        fixmystreet.select_feature.activate();
                    }
                }
            },

            setReportPosition: function(lonlat, convertPosition) {
                var markers = fms_markers_list( [ [ lonlat.lat, lonlat.lon, 'green', 'location', '', 'location' ] ], convertPosition );
                fixmystreet.report_location.removeAllFeatures();
                fixmystreet.report_location.addFeatures( markers );
                fixmystreet.report_location.setVisibility(true);
            },

            onClickMark: function(e) {
                e.preventDefault();
                this.displayButtons(true);
                $('#popup').hide();
                $('#OpenLayers_Control_Crosshairs_crosshairs').show();
                $('#reposition').hide();

                var lonlat = this.getCrossHairPosition();
                this.setReportPosition(lonlat, true);
            },

            onClickCancel: function(e) {
                e.preventDefault();
                fixmystreet.markers.removeAllFeatures();
                fixmystreet_activate_drag();
                // force pins to be refetched and displayed
                fixmystreet.bbox_strategy.update({force: true});
                this.displayButtons(false);
                if ( this.model.isPartial() ) {
                    FMS.clearCurrentDraft();
                } else {
                    // it's not partial but we've created a draft anyway so
                    // delete it
                    if ( this.model.id ) {
                        var del = FMS.removeDraft( this.model.id, true );
                        var that = this;
                        del.done( function() { that.decrementDraftCount(); } );
                    }
                    this.model.set('lat', null);
                    this.model.set('lon', null);
                }
            },

            decrementDraftCount: function() {
                var counter = $('#view-my-reports .draft_count');
                var count = counter.text();
                count--;
                counter.text(count);
            },

            onClickReposition: function(e) {
                e.preventDefault();
                var lonlat = this.getCrossHairPosition();
                lonlat.transform(
                    new OpenLayers.Projection("EPSG:4326"),
                    fixmystreet.map.getProjectionObject()
                );
                fixmystreet.report_location.features[0].move(lonlat);
                $('#reposition').hide();
            },

           onClickReport: function(e) {
                e.preventDefault();
                var position = this.getMarkerPosition();

                if ( FMS.isOffline ) {
                    this.stopListening(FMS.locator);
                    FMS.locator.stopTracking();
                    // these may be out of the area but lets just save them
                    // for now and they can be checked when we are online.
                    this.model.set('lat', position.lat );
                    this.model.set('lon', position.lon );
                    FMS.saveCurrentDraft();
                    this.navigate( 'offline' );
                } else {
                    this.listenTo(FMS.locator, 'gps_located', this.goPhoto);
                    this.listenTo(FMS.locator, 'gps_failed', this.locationCheckFailed );
                    FMS.locator.check_location( { latitude: position.lat, longitude: position.lon } );
                }
            },

            search: function(e) {
                $('#pc').blur();
                // this is to stop form submission
                e.preventDefault();
                $('#front-howto').hide();
                this.clearSearchErrors();
                this.clearValidationErrors();
                var pc = this.$('#pc').val();
                this.listenTo(FMS.locator, 'search_located', this.searchSuccess );
                this.listenTo(FMS.locator, 'search_failed', this.searchFail);

                FMS.locator.lookup(pc);
            },

            searchSuccess: function( info ) {
                this.stopListening(FMS.locator);
                var coords = info.coordinates;
                if ( fixmystreet.map ) {
                    fixmystreet.map.panTo(this.projectCoords( coords ));
                } else {
                    this.setMapPosition(info);
                    this.displayButtons(false);
                }
            },

            goAddress: function(e) {
                $('#relocate').show();
                $('#front-howto').html('').hide();
                var t = $(e.target);
                var lat = t.attr('data-lat');
                var long = t.attr('data-long');

                var coords  = { latitude: lat, longitude: long };
                if ( fixmystreet.map ) {
                    fixmystreet.map.panTo(this.projectCoords( coords ));
                } else {
                    this.setMapPosition({ coordinates: coords });
                }
            },

            searchError: function(msg) {
                if ( msg.length < 30 ) {
                    $('#pc').attr('placeholder', msg).addClass('error');;
                } else {
                    $('#front-howto').html(msg);
                    $('#relocate').hide();
                    $('#front-howto').show();
                }
            },

            clearSearchErrors: function() {
                $('#pc').attr('placeholder', this.origPcPlaceholder).removeClass('error');;
                $('#front-howto').hide();
                $('#relocate').show();
            },

            searchFail: function( details ) {
                // this makes sure any onscreen keyboard is dismissed
                $('#submit').focus();
                this.stopListening(FMS.locator);
                if ( details.msg ) {
                    this.searchError( details.msg );
                } else if ( details.locations ) {
                    var multiple = '';
                    for ( var i = 0; i < details.locations.length; i++ ) {
                        var loc = details.locations[i];
                        var li = '
' + loc.address + '
';
                        multiple = multiple + li;
                    }
                    $('#front-howto').html('
Multiple matches found

' + multiple + '
');
                    $('.ui-page').trigger('create');
                    $('#relocate').hide();
                    $('#front-howto').show();
                } else {
                    this.searchError( FMS.strings.location_problem );
                }
            },

            pauseMap: function() {
                this.stopListening(FMS.locator);
                FMS.locator.stopTracking();
                if ( FMS.iPhoneModel > 3 ) {
                    $('#map_box').addClass('background-map');
                }
                $('#map_box').off('touchend');
                if ( fixmystreet.map ) {
                    fixmystreet.nav.deactivate();
                    fixmystreet.actionafterdrag.deactivate();
                }
            },

            goPhoto: function(info) {
                this.pauseMap();
                this.model.set('lat', info.coordinates.latitude );
                this.model.set('lon', info.coordinates.longitude );
                this.model.set('categories', info.details.category );
                if ( info.details.titles_list ) {
                    this.model.set('titles_list', info.details.titles_list);
                }
                FMS.saveCurrentDraft();

                this.navigate( 'photo' );
            },

            locationCheckFailed: function() {
                this.displayAlert(FMS.strings.location_check_failed);
            },

            goSearch: function(e) {
                e.preventDefault();
                if ( !fixmystreet.map ) {
                    this.$('#mark-here').hide();
                    this.$('#relocate').hide();
                    $('#front-howto').html('
' + FMS.strings.locate_dismissed + '
');
                    $('#front-howto').show();
                }
                this.finishedLocating();
            },

            goLogin: function(e) {
                e.preventDefault();
                this.pauseMap();
                this.navigate( 'login' );
            },

            goReports: function(e) {
                e.preventDefault();
                this.pauseMap();
                this.navigate( 'reports' );
            },

            getCrossHairPosition: function() {
                var cross = fixmystreet.map.getControlsByClass(
                "OpenLayers.Control.Crosshairs");

                var position = cross[0].getMapPosition();
                position.transform(
                    fixmystreet.map.getProjectionObject(),
                    new OpenLayers.Projection("EPSG:4326")
                );

                return position;
            },

            getMarkerPosition: function() {
                var marker = fixmystreet.report_location.features[0].geometry;

                var position = new OpenLayers.LonLat( marker.x, marker.y );
                position.transform(
                    fixmystreet.map.getProjectionObject(),
                    new OpenLayers.Projection("EPSG:4326")
                );

                return position;
            },

            projectCoords: function( coords ) {
                var centre = new OpenLayers.LonLat( coords.longitude, coords.latitude );
                centre.transform(
                    new OpenLayers.Projection("EPSG:4326"),
                    fixmystreet.map.getProjectionObject()
                );

                return centre;
            }
        })
    });
})(FMS, Backbone, _, $);
<commit_msg>Display reposition report button after recentering map<commit_after>(function (FMS, Backbone, _, $) {
    _.extend( FMS, {
        AroundView: FMS.LocatorView.extend({
            template: 'around',
            id: 'around-page',

            events: {
                'pagehide': 'destroy',
                'pagebeforeshow': 'beforeDisplay',
                'pageshow': 'afterDisplay',
                'vclick #locate_cancel': 'goSearch',
                'vclick #login-options': 'goLogin',
                'vclick #view-my-reports': 'goReports',
                'vclick #search': 'goSearch',
                'vclick .ui-input-clear': 'clearSearchErrors',
                'blur #pc': 'clearSearchErrors',
                'vclick #relocate': 'centerMapOnPosition',
                'vclick #cancel': 'onClickCancel',
                'vclick #confirm': 'onClickReport',
                'vclick #confirm-map': 'onClickReport',
                'vclick #mark-here': 'onClickMark',
                'vclick #reposition': 'onClickReposition',
                'vclick a.address': 'goAddress',
                'submit #postcodeForm': 'search'
            },

            render: function(){
                if ( !this.template ) {
                    console.log('no template to render');
                    return;
                }
                template = _.template( tpl.get( this.template ) );
                if ( this.model ) {
                    this.$el.html(template({ model: this.model.toJSON(), user: FMS.currentUser.toJSON() }));
                } else {
                    this.$el.html(template());
                }
                this.afterRender();
                return this;
            },

            beforeDisplay: function() {
                this.origPcPlaceholder = $('#pc').attr('placeholder');
                $('a[data-role="button"]').hide();
                $('#view-my-reports').hide();
                $('#login-options').hide();
                $('#postcodeForm').hide();
                $('#cancel').hide();
                $('#map_box').removeClass('background-map');
                this.fixPageHeight();
                $('#map_box').on('touchend', function() { if ( ! $('#popup').length ) { $('#OpenLayers_Control_Crosshairs_crosshairs').show(); } } );
            },

            afterDisplay: function() {
                if ( FMS.isOffline ) {
                    $('#locating').hide();
                    this.navigate( 'offline' );
                } else if ( this.model && this.model.get('lat') ) {
                    var modelInfo = { coordinates: { latitude: this.model.get('lat'), longitude: this.model.get('lon') } };
                    this.setMapPosition(modelInfo);
                    this.displayButtons(true);
                    this.setReportPosition({ lat: this.model.get('lat'), lon: this.model.get('lon') }, true);
                    this.listenTo(FMS.locator, 'gps_current_position', this.positionUpdate);
                    FMS.locator.trackPosition();
                } else if ( FMS.currentPosition ) {
                    var info = { coordinates: FMS.currentPosition };
                    FMS.currentPosition = null;
                    if ( !fixmystreet.map ) {
                        this.setMapPosition(info);
                    }
                    this.displayButtons(false);
                    this.listenTo(FMS.locator, 'gps_current_position', this.positionUpdate);
                    FMS.locator.trackPosition();
                } else {
                    this.locate();
                    this.displayButtons(false);
                }
            },

            setMapPosition: function( info ) {
                var coords = info.coordinates;
                fixmystreet.latitude = coords.latitude;
                fixmystreet.longitude = coords.longitude;

                if ( !fixmystreet.map ) {
                    show_map();
                } else {
                    FMS.currentPosition = coords;
                    var centre = this.projectCoords( coords );
                    fixmystreet.map.panTo(centre);
                }
            },

            gotLocation: function( info ) {
                $('#relocate').show();
                this.finishedLocating();

                this.listenTo(FMS.locator, 'gps_current_position', this.positionUpdate);

                this.located = true;
                this.locateCount = 21;

                this.setMapPosition( info );

                FMS.locator.trackPosition();
                // FIXME: not sure why I need to do this
                fixmystreet.select_feature.deactivate();
                fixmystreet.select_feature.activate();
                fixmystreet.nav.activate();
            },

            positionUpdate: function( info ) {
                $('#relocate').show();
                FMS.currentPosition = info.coordinates;
                var centre = this.projectCoords( info.coordinates );

                var point = new OpenLayers.Geometry.Point( centre.lon, centre.lat );

                fixmystreet.location.removeAllFeatures();
                    var x = new OpenLayers.Feature.Vector(
                        point,
                        {},
                        {
                            graphicZIndex: 3000,
                            graphicName: 'circle',
                            'externalGraphic': 'images/gps-marker.svg',
                            pointRadius: 16
                        }
                    );
                fixmystreet.location.addFeatures([ x ]);
            },

            centerMapOnPosition: function(e) {
                e.preventDefault();
                if ( !fixmystreet.map ) {
                    return;
                }

                // if there isn't a currentPosition then something
                // is up so we probably should not recenter
                if ( FMS.currentPosition ) {
                    fixmystreet.map.panTo(this.projectCoords( FMS.currentPosition ));

                    // If we've confirmed the report position then we should be able
                    // to reposition it if we've moved the map.
                    if ( $('#confirm').css('display') == 'block' ) {
                        var currentPos = this.projectCoords(FMS.currentPosition);
                        var markerPos = this.getMarkerPosition(true);

                        // Displaying the button if the report is in the same place as the
                        // GPS location could be confusing so check they are different.
                        // The slight margin of error is there to account for both rounding
                        // wiggle in the projectCoords and also so that small changes due to
                        // GPS noise are ignored
                        if ( Math.abs(markerPos.lat - currentPos.lat) > 1 ||
                             Math.abs(markerPos.lon - currentPos.lon) > 1 ) {
                            $('#reposition').show();
                        }
                    }
                }
            },

            failedLocation: function( details ) {
                this.finishedLocating();
                this.locateCount = 21;
                var msg = '';
                if ( details.msg ) {
                    msg = details.msg;
                } else {
                    msg = FMS.strings.location_problem;
                }
                if ( !fixmystreet.map ) {
                    $('#relocate').hide();
                    $('#mark-here').hide();
                    $('#pc').focus();
                }
                $('#front-howto').html('
' + msg + '</msg>');
                $('#front-howto').show();
            },

            displayButtons: function(isLocationSet) {
                if ( fixmystreet.map ) {
                    fixmystreet.nav.activate();
                    fixmystreet.actionafterdrag.activate();
                }
                if (isLocationSet) {
                    $('#cancel').addClass('ui-btn-left').show();
                    $('#confirm').addClass('ui-btn-right ui-btn-icon-right').show();
                    $('#confirm-map').show();
                    $('#view-my-reports').hide();
                    $('#login-options').hide();
                    $('#mark-here').hide();
                    $('#postcodeForm').hide();
                    if ( fixmystreet.map ) {
                        fixmystreet.markers.setVisibility(false);
                        fixmystreet.select_feature.deactivate();
                        fixmystreet.bbox_strategy.deactivate();
                    }
                } else {
                    $('#cancel').hide().removeClass('ui-btn-left');
                    $('#confirm').hide().removeClass('ui-btn-right ui-btn-icon-right');
                    $('#confirm-map').hide();
                    $('#view-my-reports').show();
                    $('#login-options').show();
                    $('#mark-here').show();
                    $('#postcodeForm').show();
                    $('#reposition').hide();
                    if ( fixmystreet.map ) {
                        fixmystreet.bbox_strategy.activate();
                        fixmystreet.report_location.setVisibility(false);
                        fixmystreet.markers.setVisibility(true);
                        fixmystreet.select_feature.deactivate();
                        fixmystreet.select_feature.activate();
                    }
                }
            },

            setReportPosition: function(lonlat, convertPosition) {
                var markers = fms_markers_list( [ [ lonlat.lat, lonlat.lon, 'green', 'location', '', 'location' ] ], convertPosition );
                fixmystreet.report_location.removeAllFeatures();
                fixmystreet.report_location.addFeatures( markers );
                fixmystreet.report_location.setVisibility(true);
            },

            onClickMark: function(e) {
                e.preventDefault();
                this.displayButtons(true);
                $('#popup').hide();
                $('#OpenLayers_Control_Crosshairs_crosshairs').show();
                $('#reposition').hide();

                var lonlat = this.getCrossHairPosition();
                this.setReportPosition(lonlat, true);
            },

            onClickCancel: function(e) {
                e.preventDefault();
                fixmystreet.markers.removeAllFeatures();
                fixmystreet_activate_drag();
                // force pins to be refetched and displayed
                fixmystreet.bbox_strategy.update({force: true});
                this.displayButtons(false);
                if ( this.model.isPartial() ) {
                    FMS.clearCurrentDraft();
                } else {
                    // it's not partial but we've created a draft anyway so
                    // delete it
                    if ( this.model.id ) {
                        var del = FMS.removeDraft( this.model.id, true );
                        var that = this;
                        del.done( function() { that.decrementDraftCount(); } );
                    }
                    this.model.set('lat', null);
                    this.model.set('lon', null);
                }
            },

            decrementDraftCount: function() {
                var counter = $('#view-my-reports .draft_count');
                var count = counter.text();
                count--;
                counter.text(count);
            },

            onClickReposition: function(e) {
                e.preventDefault();
                var lonlat = this.getCrossHairPosition();
                lonlat.transform(
                    new OpenLayers.Projection("EPSG:4326"),
                    fixmystreet.map.getProjectionObject()
                );
                fixmystreet.report_location.features[0].move(lonlat);
                $('#reposition').hide();
            },

           onClickReport: function(e) {
                e.preventDefault();
                var position = this.getMarkerPosition();

                if ( FMS.isOffline ) {
                    this.stopListening(FMS.locator);
                    FMS.locator.stopTracking();
                    // these may be out of the area but lets just save them
                    // for now and they can be checked when we are online.
                    this.model.set('lat', position.lat );
                    this.model.set('lon', position.lon );
                    FMS.saveCurrentDraft();
                    this.navigate( 'offline' );
                } else {
                    this.listenTo(FMS.locator, 'gps_located', this.goPhoto);
                    this.listenTo(FMS.locator, 'gps_failed', this.locationCheckFailed );
                    FMS.locator.check_location( { latitude: position.lat, longitude: position.lon } );
                }
            },

            search: function(e) {
                $('#pc').blur();
                // this is to stop form submission
                e.preventDefault();
                $('#front-howto').hide();
                this.clearSearchErrors();
                this.clearValidationErrors();
                var pc = this.$('#pc').val();
                this.listenTo(FMS.locator, 'search_located', this.searchSuccess );
                this.listenTo(FMS.locator, 'search_failed', this.searchFail);

                FMS.locator.lookup(pc);
            },

            searchSuccess: function( info ) {
                this.stopListening(FMS.locator);
                var coords = info.coordinates;
                if ( fixmystreet.map ) {
                    fixmystreet.map.panTo(this.projectCoords( coords ));
                } else {
                    this.setMapPosition(info);
                    this.displayButtons(false);
                }
            },

            goAddress: function(e) {
                $('#relocate').show();
                $('#front-howto').html('').hide();
                var t = $(e.target);
                var lat = t.attr('data-lat');
                var long = t.attr('data-long');

                var coords  = { latitude: lat, longitude: long };
                if ( fixmystreet.map ) {
                    fixmystreet.map.panTo(this.projectCoords( coords ));
                } else {
                    this.setMapPosition({ coordinates: coords });
                }
            },

            searchError: function(msg) {
                if ( msg.length < 30 ) {
                    $('#pc').attr('placeholder', msg).addClass('error');;
                } else {
                    $('#front-howto').html(msg);
                    $('#relocate').hide();
                    $('#front-howto').show();
                }
            },

            clearSearchErrors: function() {
                $('#pc').attr('placeholder', this.origPcPlaceholder).removeClass('error');;
                $('#front-howto').hide();
                $('#relocate').show();
            },

            searchFail: function( details ) {
                // this makes sure any onscreen keyboard is dismissed
                $('#submit').focus();
                this.stopListening(FMS.locator);
                if ( details.msg ) {
                    this.searchError( details.msg );
                } else if ( details.locations ) {
                    var multiple = '';
                    for ( var i = 0; i < details.locations.length; i++ ) {
                        var loc = details.locations[i];
                        var li = '
' + loc.address + '
';
                        multiple = multiple + li;
                    }
                    $('#front-howto').html('
Multiple matches found

' + multiple + '
');
                    $('.ui-page').trigger('create');
                    $('#relocate').hide();
                    $('#front-howto').show();
                } else {
                    this.searchError( FMS.strings.location_problem );
                }
            },

            pauseMap: function() {
                this.stopListening(FMS.locator);
                FMS.locator.stopTracking();
                if ( FMS.iPhoneModel > 3 ) {
                    $('#map_box').addClass('background-map');
                }
                $('#map_box').off('touchend');
                if ( fixmystreet.map ) {
                    fixmystreet.nav.deactivate();
                    fixmystreet.actionafterdrag.deactivate();
                }
            },

            goPhoto: function(info) {
                this.pauseMap();
                this.model.set('lat', info.coordinates.latitude );
                this.model.set('lon', info.coordinates.longitude );
                this.model.set('categories', info.details.category );
                if ( info.details.titles_list ) {
                    this.model.set('titles_list', info.details.titles_list);
                }
                FMS.saveCurrentDraft();

                this.navigate( 'photo' );
            },

            locationCheckFailed: function() {
                this.displayAlert(FMS.strings.location_check_failed);
            },

            goSearch: function(e) {
                e.preventDefault();
                if ( !fixmystreet.map ) {
                    this.$('#mark-here').hide();
                    this.$('#relocate').hide();
                    $('#front-howto').html('
' + FMS.strings.locate_dismissed + '
');
                    $('#front-howto').show();
                }
                this.finishedLocating();
            },

            goLogin: function(e) {
                e.preventDefault();
                this.pauseMap();
                this.navigate( 'login' );
            },

            goReports: function(e) {
                e.preventDefault();
                this.pauseMap();
                this.navigate( 'reports' );
            },

            getCrossHairPosition: function() {
                var cross = fixmystreet.map.getControlsByClass(
                "OpenLayers.Control.Crosshairs");

                var position = cross[0].getMapPosition();
                position.transform(
                    fixmystreet.map.getProjectionObject(),
                    new OpenLayers.Projection("EPSG:4326")
                );

                return position;
            },

            getMarkerPosition: function(skipTransform) {
                var marker = fixmystreet.report_location.features[0].geometry;

                var position = new OpenLayers.LonLat( marker.x, marker.y );
                if ( skipTransform ) {
                    return position;
                }
                position.transform(
                    fixmystreet.map.getProjectionObject(),
                    new OpenLayers.Projection("EPSG:4326")
                );

                return position;
            },

            projectCoords: function( coords ) {
                var centre = new OpenLayers.LonLat( coords.longitude, coords.latitude );
                centre.transform(
                    new OpenLayers.Projection("EPSG:4326"),
                    fixmystreet.map.getProjectionObject()
                );

                return centre;
            }
        })
    });
})(FMS, Backbone, _, $);
<|endoftext|>


<commit_before>import FS from "fs";
import Path from "path";
import _ from "lodash-fp";
import { split, match } from "../helpers";

export const title = "Day 22: Wizard Simulator 20XX";

export const parse = _.compose(
    _.zipObject(["health", "damage"]),
    _.map(_.parseInt(10)),
    _.map(_.first),
    _.map(match(/\d+$/)),
    split("\n")
);

export function run() {
    const inputPath = Path.join(__dirname, "input.txt");
    const input = FS.readFileSync(inputPath, "utf-8").trim();

    console.log("What is the least amount of mana you can spend and still win the fight?");
}
<commit_msg>Initial stab at the implementation of simulate.<commit_after>import FS from "fs";
import Path from "path";
import _ from "lodash-fp";
import { split, match, assign } from "../helpers";

export const title = "Day 22: Wizard Simulator 20XX";

export const parse = _.compose(
    _.zipObject(["health", "damage"]),
    _.map(_.parseInt(10)),
    _.map(_.first),
    _.map(match(/\d+$/)),
    split("\n")
);

export function simulate(spells, player, opponent) {
    // Assign an index to each spell.
    spells = _.map((id) => {
        return assign({id}, spells[id], {});
    }, _.range(0, spells.length));
    // Assign a spent property which will increase on each spell purchase.
    player = assign({spent: 0}, player, {});

    const getCombinations = (player, opponent, effects, turn = 0, results = []) => {
        // We kill the opponent.
        if (opponent.health <= 0)
            return results.push({win: true, mana: player.spent});

        // We die, or run out of mana.
        if (player.health <= 0 || player.mana < 0)
            return results.push({win: false, mana: player.spent});

        // Clone each parameter to prevent unwanted mutations.
        player = _.clone(player);
        opponent = _.clone(opponent);
        effects = _.clone(effects);

        // Apply effects, deducting timer values.
        // TODO: It might be best to pull this out into a separate function.
        _.forEach((value, key) => {
            if (value <= 0)
                return;

            const spell = spells[key];

            // Attack the opponent.
            if (spell.damage)
                opponent.health -= spell.damage;

            // If the armor is about to wear off, remove it.
            if (spell.armor && value == 1)
                player.armor -= spell.armor;

            // Heal yourself.
            if (spell.health)
                player.health += spell.health;

            // Increase mana.
            if (spell.mana)
                player.mana += spell.mana;

            // Deduct the timer value.
            effects[key] = value - 1;
        }, effects);

        if (turn % 0)
            // Opponent's turn.
            player.health -= opponent.damage;
        else
            // Our turn, select a spell.
            _.compose(
                _.forEach((spell) => {
                    // TODO: We basically cast the spell. Maybe a separate function?
                    const newPlayer = assign({
                        mana: player.mana - spell.cost,
                        spent: player.spent + spell.cost,
                        health: player.health + (!spell.turns && spell.health || 0)
                    }, player, {});
                    const newOpponent = assign({
                        health: opponent.health - (!spell.turns && spell.damage || 0)
                    }, opponent, {});

                    getCombinations(newPlayer, newOpponent, effects, turn + 1, results);
                }),
                // Can't select a spell that's already in effect.
                _.filter(({id}) => !effects[id])
            )(spells);

        return results;
    };

    return getCombinations(player, opponent, _.reduce((effects, index) => {
        return assign({[index]: 0}, effects, {});
    }, {}, _.range(0, spells.length)));
}

export function run() {
    const inputPath = Path.join(__dirname, "input.txt");
    const input = FS.readFileSync(inputPath, "utf-8").trim();

    console.log("What is the least amount of mana you can spend and still win the fight?");
}
<|endoftext|>


<commit_before>/*
 * gui-builder - A simple WYSIWYG HTML5 app creator
 * Copyright (c) 2011, Intel Corporation.
 *
 * This program is licensed under the terms and conditions of the
 * Apache License, version 2.0.  The full text of the Apache License is at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 */
"use strict";

// Layout view widget

(function($, undefined) {

    $.widget('gb.layoutView', {

        options: {
            model: null,
            iframe: null,
            contentDocument: null,
            customHeaders: {
                'meta': [
                    '<meta http-equiv="cache-control" content="no-cache">'
                ],
                'script': [
                    '',
                    ''
                ],
                'link': [
                    '<link href="src/css/composer.css" rel="stylesheet">'
                ]
            }
        },

        _create: function() {
            var o = this.options,
                e = this.element;

            o.designReset = this._designResetHandler;
            o.selectionChanged = this._selectionChangedHandler;
            o.activePageChanged = this._activePageChangedHandler;
            o.modelUpdated = this._modelUpdatedHandler;

            // FIXME: This should work, but $.extend of options seems to be
            //        creating a copy of the ADM, which will not containt the
            //        same nodes and events as the master
            //o.model = o.model || ADM || undefined;
            if (o.model) {
                this._bindADMEvents(o.model);
            }

            this.options.iframe = this.element.find('iframe');
            if (!this.options.iframe.length) {
                this.options.iframe = $('');
            }

            this.options.iframe.addClass(this.widgetName)
                .addClass('flex1')
                .appendTo(this.element);

            this.options.primaryTools = this._createPrimaryTools();
            this.options.secondaryTools = this._createSecondaryTools();

            this.options.contentDocument =
                $(this.options.iframe[0].contentDocument);

            $(window).resize(this, function(event) {
                var el = event.data.element,
                    doc = event.data.options.contentDocument,
                    iframe = event.data.options.iframe;
                // Force resize of the stage when containing window resizes
                el.height(el.parent().height());
                el.find('div').height(el.parent().height());
                // Force resize of the iframe when containing window resizes
                iframe.height(doc.height());
                iframe.css('min-height',
                            el.height() - 2
                            - parseFloat(iframe.css('margin-top'))
                            - parseFloat(iframe.css('margin-bottom'))
                            - parseFloat(iframe.css('padding-bottom'))
                            - parseFloat(iframe.css('padding-bottom')));
            });

            this.refresh(null, this);

            return this;
        },

        _setOption: function(key, value) {
            switch (key) {
                // Should this REALLY be done here, or plugin registration in
                // the "host"... using the functions mapped in widget options?
                case 'model':
                    this._unbindADMEvents();
                    this._bindADMEvents(value);
                    this._createDocument();
                    this.options.iframe.load(this, this._iframeLoaded);
                    break;
                default:
                    break;
            }
        },

        destroy: function() {
            // TODO: unbind any ADM event handlers
            $(this.element).find('.'+this.widgetName).remove();
            this.options.primaryTools.remove();
            this.options.secondaryTools.remove();
        },

        refresh: function(event, widget) {
            var name;
            widget = widget || event && event.data || this;
            name = (event)?(event.name)?event.name:event.type:'';

            if (!widget.loaded) return;

            if (!event) {
                widget._serializeADMDesignToDOM();
            } else if (event.type === 'load') {
                widget._serializeADMDesignToDOM();
            } else if (event.name === 'designReset') {
                widget._serializeADMDesignToDOM();
            } else if (event.name === 'modelUpdated') {
                widget._serializeADMDesignToDOM();
/* FIXME: Calling serializeADMSubtreeToDom is not actually forcing the
          the DOM to update, but it should work...

                switch (event.type) {
                    case 'nodeAdded':
                    case 'nodeRemoved':
                        serializeADMSubtreeToDOM(event.parent, null,
                                                 widget._renderer);
                        break;
                    case 'nodeMoved':
                        serializeADMSubtreeToDOM(event.oldParent, null,
                                                 widget._renderer);
                        serializeADMSubtreeToDOM(event.newParent, null,
                                                 widget._renderer);
                        break;
                    case 'propertyChanged':
                        serializeADMSubtreeToDOM(event.node, null,
                                                 widget._renderer);
                        break;
                    default:
                        console.warn(widget.widgetName,
                                     ':: Unexpected modelUpdate type:',
                                     event.type);
                        return;
                        break;
                }
*/
            } else {
                console.warn(widget.widgetName,':: Unexpected event:',name);
                return;
            }

            if (widget.options.contentDocument.length) {
                widget.options.contentDocument[0].defaultView
                    .postMessage('reload', '*');
            } else {
                console.error(widget.widgetName, ':: Missing contentDocument');
            }

            // Refresh our cache of iframe sortables
            widget._refreshSortables();
        },

        // Private functions
        _getSortables: function(update) {
            if (update) {
                this._sortables = this.options.contentDocument
                                      .find('.nrc-sortable-container');
            } else {
                this._sortables = this._sortables||this.options.contentDocument
                                      .find('.nrc-sortable-container');
            }
            return this._sortables;
        },

        _refreshSortables: function() {
            // Force a re-search for sortables in the iframe doc
            var s = this._getSortables(true);

            // Associate the palette items with these sortables
            $(':gb-paletteView .ui-draggable').draggable('option', {
                connectToSortable: s,
            });
        },

        _iframeLoaded: function(event) {
            event.data.loaded = true;
            event.data.refresh(null, event.data);
        },

        _createPrimaryTools: function() {
            var doc, html, classes, selector,
                commands = ['undo', 'redo', 'cut', 'copy', 'paste'];

            classes = "buttonStyle primary-tools ui-state-default";
            html = $('
').addClass('hbox').hide();
            $.each(commands, function (index, command) {
                $('<button/>').addClass(classes)
                    .attr('id', 'btn' + command)
                    .appendTo(html);
            });

            doc = $((this.element)[0].ownerDocument);
            selector = "button.primary-tools"
            doc.delegate(selector, "click", jQuery.proxy(function (event) {
                var model = this.options.model;
                if (model) {
                    switch (event.currentTarget.id) {
                    case "btnundo":  model.undo();  break;
                    case "btnredo":  model.redo();  break;
                    case "btncut":   model.cut();   break;
                    case "btncopy":  model.copy();  break;
                    case "btnpaste": model.paste(); break;
                    default:
                        console.warn("Unhandled click on primary tool");
                        break;
                    }
                }
                else {
                    console.warn("No model while attempting undo");
                }
            }, this));

            return html;
        },

        _createSecondaryTools: function() {
            return $(null);
        },

        _bindADMEvents: function(a) {
            var o = this.options,
                d = this.designRoot;

            if (a) {
                o.model = a;

                if (o.designReset) {
                    a.bind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.bind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.bind("activePageChanged", o.activePageChanged, this);
                }

                // Since model changed, need to call our designReset hander
                // to sync up the ADMDesign modelUpdated event handler
                if (o.designReset) {
                    o.designReset({design: a.getDesignRoot()}, this);
                }
            }
        },

        _unbindADMEvents: function() {
            var o = this.options,
                a = this.options.model,
                d = this.designRoot;

            // First unbind our ADMDesign modelUpdated handler, if any...
            if (d && o.modelUpdated) {
                d.designRoot.unbind("modelUpdated", o.modelUpdated, this);
            }

            // Now unbind all ADM model event handlers, if any...
            if (a) {
                if (o.designReset) {
                    a.unbind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.unbind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.unbind("activePageChanged", o.activePageChanged, this);
                }
            }
        },

        _designResetHandler: function(event, widget) {
            var d = event && event.design, o;

            widget = widget || this;
            o = widget.options;
            d = d || o.model.getDesignRoot();

            // Do nothing if the new ADMDesign equals our currently cached one
            if (d === widget.designRoot) {
                return;
            }

            // First, unbind existing modelUpdated hander, if any...
            if (widget.designRoot && o.modelUpdated) {
                widget.designRoot.unbind("modelUpdated", o.modelUpdated,widget);
            }

            // Next, bind to modelUpdated events from new ADMDesign, if any...
            if (d && o.modelUpdated) {
                d.bind("modelUpdated", o.modelUpdated, widget);
            }

            // Then, cache the new ADMDesign reference with this instance
            widget.designRoot = d;

            // Finally, redraw our view since the ADMDesign root has changed
            widget.refresh(event, widget);
        },

        _selectionChangedHandler: function(event, widget) {
            var uid;
            widget = widget || this;

            // Always un-style currently selected nodes
            widget.options.contentDocument.find('.ui-selected')
                .removeClass('ui-selected');

            // Only apply selection style changes on valid nodes
            if (!event || (!event.uid && !event.node)) {
                return;
            }

            // Normally, ADM node id is provided in event.uid
            uid = event && event.uid;
            // Fallback is to try event.node.getUid()
            uid = uid || (event.node)?event.node.getUid():null;

            if (uid) {
                widget.options.contentDocument
                    .find('.adm-node[data-uid=\''+uid+'\']')
                    .not('[data-role=\'page\']')
                    .addClass('ui-selected').first().each(function() {
                        // Scroll selected node into view
                        this.scrollIntoViewIfNeeded();
                    });
            }
        },

        _activePageChangedHandler: function(event, widget) {
            var id, win,
                newPage = event && event.page, curPage;

            widget = widget || this;

            // Only change if new page is valid
            if (!newPage) {
                return;
            }
            id = newPage.getUid();
            curPage = widget.options.model.getActivePage();

            // Only change if new page not the current page
            if (curPage && curPage.getUid() === id) {
                return;
            }

            win = widget.options.contentDocument[0].defaultView;

            if (win && win.$ && win.$.mobile) {
                win.$.mobile.changePage("#"+id, {transition: "none"});
            }
        },

        _modelUpdatedHandler: function(event, widget) {
            var win;

            widget = widget || this;

            widget.refresh(event, widget);

            win = widget.options.contentDocument[0].defaultView;
            if (win && win.$ && win.$.mobile) {
                win.$.mobile.activePage.page('destroy');
                win.$.mobile.activePage.page();
            } else {
                console.error(widget.widgetName, ':: Missing contentDocument');
            }
        },

        _createDocument: function() {
            var contents, doc;

            if (!this.designRoot) return;

            doc = this.options.contentDocument[0];
            doc.open();
            contents = this._serializeFramework(this.designRoot);
            doc.writeln(contents);
            doc.close();
        },

        _serializeFramework: function() {
            var start, end, ret, headers;

            headers = this._getCustomHeaders();

            start = '<!DOCTYPE html>\n <title>Page Title</title>\n';
            end = "\n\n\n";

            if (headers && headers.length > 0) {
                ret = start + headers.join('\n') + end;
            } else {
                ret = start + end;
            }

            return ret;
        },

        // Great assumptions are being made here that the incoming default
        // headers are already "sorted" and in the order in which they should
        // be inserted into the  node of the document being created...
        _getCustomHeaders: function() {
            var dh = $.gb.getDefaultHeaders(),   // default headers
                ch = this.options.customHeaders, // our custom headers
                m, s;

            $.each(dh, function(idx) {
                if (/<meta/.test(this)) {
                    m = idx+1; // Insert our Meta after LAST one
                    return;
                }
                if (/',
                    ''
                ],
                'link': [
                    '<link href="src/css/composer.css" rel="stylesheet">'
                ]
            }
        },

        _create: function() {
            var o = this.options,
                e = this.element;

            o.designReset = this._designResetHandler;
            o.selectionChanged = this._selectionChangedHandler;
            o.activePageChanged = this._activePageChangedHandler;
            o.modelUpdated = this._modelUpdatedHandler;

            // FIXME: This should work, but $.extend of options seems to be
            //        creating a copy of the ADM, which will not containt the
            //        same nodes and events as the master
            //o.model = o.model || ADM || undefined;
            if (o.model) {
                this._bindADMEvents(o.model);
            }

            this.options.iframe = this.element.find('iframe');
            if (!this.options.iframe.length) {
                this.options.iframe = $('');
            }

            this.options.iframe.addClass(this.widgetName)
                .addClass('flex1')
                .appendTo(this.element);

            this.options.primaryTools = this._createPrimaryTools();
            this.options.secondaryTools = this._createSecondaryTools();

            this.options.contentDocument =
                $(this.options.iframe[0].contentDocument);

            $(window).resize(this, function(event) {
                var el = event.data.element,
                    doc = event.data.options.contentDocument,
                    iframe = event.data.options.iframe;
                // Force resize of the stage when containing window resizes
                el.height(el.parent().height());
                el.find('div').height(el.parent().height());
                // Force resize of the iframe when containing window resizes
                iframe.height(doc.height());
                iframe.css('min-height',
                            el.height() - 2
                            - parseFloat(iframe.css('margin-top'))
                            - parseFloat(iframe.css('margin-bottom'))
                            - parseFloat(iframe.css('padding-bottom'))
                            - parseFloat(iframe.css('padding-bottom')));
            });

            this.refresh(null, this);

            return this;
        },

        _setOption: function(key, value) {
            switch (key) {
                // Should this REALLY be done here, or plugin registration in
                // the "host"... using the functions mapped in widget options?
                case 'model':
                    this._unbindADMEvents();
                    this._bindADMEvents(value);
                    this._createDocument();
                    this.options.iframe.load(this, this._iframeLoaded);
                    break;
                default:
                    break;
            }
        },

        destroy: function() {
            // TODO: unbind any ADM event handlers
            $(this.element).find('.'+this.widgetName).remove();
            this.options.primaryTools.remove();
            this.options.secondaryTools.remove();
        },

        refresh: function(event, widget) {
            var name;
            widget = widget || event && event.data || this;
            name = (event)?(event.name)?event.name:event.type:'';

            if (!widget.loaded) return;

            if (!event) {
                widget._serializeADMDesignToDOM();
            } else if (event.type === 'load') {
                widget._serializeADMDesignToDOM();
            } else if (event.name === 'designReset') {
                widget._serializeADMDesignToDOM();
            } else if (event.name === 'modelUpdated') {
                widget._serializeADMDesignToDOM();
/* FIXME: Calling serializeADMSubtreeToDom is not actually forcing the
          the DOM to update, but it should work...

                switch (event.type) {
                    case 'nodeAdded':
                    case 'nodeRemoved':
                        serializeADMSubtreeToDOM(event.parent, null,
                                                 widget._renderer);
                        break;
                    case 'nodeMoved':
                        serializeADMSubtreeToDOM(event.oldParent, null,
                                                 widget._renderer);
                        serializeADMSubtreeToDOM(event.newParent, null,
                                                 widget._renderer);
                        break;
                    case 'propertyChanged':
                        serializeADMSubtreeToDOM(event.node, null,
                                                 widget._renderer);
                        break;
                    default:
                        console.warn(widget.widgetName,
                                     ':: Unexpected modelUpdate type:',
                                     event.type);
                        return;
                        break;
                }
*/
            } else {
                console.warn(widget.widgetName,':: Unexpected event:',name);
                return;
            }

            if (widget.options.contentDocument.length) {
                widget.options.contentDocument[0].defaultView
                    .postMessage('reload', '*');
            } else {
                console.error(widget.widgetName, ':: Missing contentDocument');
            }

            // Refresh our cache of iframe sortables
            widget._refreshSortables();
        },

        // Private functions
        _getSortables: function(update) {
            if (update) {
                this._sortables = this.options.contentDocument
                                      .find('.nrc-sortable-container');
            } else {
                this._sortables = this._sortables||this.options.contentDocument
                                      .find('.nrc-sortable-container');
            }
            return this._sortables;
        },

        _refreshSortables: function() {
            // Force a re-search for sortables in the iframe doc
            var s = this._getSortables(true);

            // Associate the palette items with these sortables
            $(':gb-paletteView .ui-draggable').draggable('option', {
                connectToSortable: s,
            });
        },

        _iframeLoaded: function(event) {
            event.data.loaded = true;
            event.data.refresh(null, event.data);
        },

        _createPrimaryTools: function() {
            var doc, html, classes, selector,
                commands = ['undo', 'redo', 'cut', 'copy', 'paste'];

            classes = "buttonStyle primary-tools ui-state-default";
            html = $('
').addClass('hbox').hide();
            $.each(commands, function (index, command) {
                $('<button/>').addClass(classes)
                    .attr('id', 'btn' + command)
                    .appendTo(html);
            });

            doc = $((this.element)[0].ownerDocument);
            selector = "button.primary-tools"
            doc.delegate(selector, "click", jQuery.proxy(function (event) {
                var model = this.options.model;
                if (model) {
                    switch (event.currentTarget.id) {
                    case "btnundo":  model.undo();  break;
                    case "btnredo":  model.redo();  break;
                    case "btncut":   model.cut();   break;
                    case "btncopy":  model.copy();  break;
                    case "btnpaste": model.paste(); break;
                    default:
                        console.warn("Unhandled click on primary tool");
                        break;
                    }
                }
                else {
                    console.warn("No model while attempting undo");
                }
            }, this));

            return html;
        },

        _createSecondaryTools: function() {
            return $(null);
        },

        _bindADMEvents: function(a) {
            var o = this.options,
                d = this.designRoot;

            if (a) {
                o.model = a;

                if (o.designReset) {
                    a.bind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.bind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.bind("activePageChanged", o.activePageChanged, this);
                }

                // Since model changed, need to call our designReset hander
                // to sync up the ADMDesign modelUpdated event handler
                if (o.designReset) {
                    o.designReset({design: a.getDesignRoot()}, this);
                }
            }
        },

        _unbindADMEvents: function() {
            var o = this.options,
                a = this.options.model,
                d = this.designRoot;

            // First unbind our ADMDesign modelUpdated handler, if any...
            if (d && o.modelUpdated) {
                d.designRoot.unbind("modelUpdated", o.modelUpdated, this);
            }

            // Now unbind all ADM model event handlers, if any...
            if (a) {
                if (o.designReset) {
                    a.unbind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.unbind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.unbind("activePageChanged", o.activePageChanged, this);
                }
            }
        },

        _designResetHandler: function(event, widget) {
            var d = event && event.design, o;

            widget = widget || this;
            o = widget.options;
            d = d || o.model.getDesignRoot();

            // Do nothing if the new ADMDesign equals our currently cached one
            if (d === widget.designRoot) {
                return;
            }

            // First, unbind existing modelUpdated hander, if any...
            if (widget.designRoot && o.modelUpdated) {
                widget.designRoot.unbind("modelUpdated", o.modelUpdated,widget);
            }

            // Next, bind to modelUpdated events from new ADMDesign, if any...
            if (d && o.modelUpdated) {
                d.bind("modelUpdated", o.modelUpdated, widget);
            }

            // Then, cache the new ADMDesign reference with this instance
            widget.designRoot = d;

            // Finally, redraw our view since the ADMDesign root has changed
            widget.refresh(event, widget);
        },

        _selectionChangedHandler: function(event, widget) {
            var uid;
            widget = widget || this;

            // Always un-style currently selected nodes
            widget.options.contentDocument.find('.ui-selected')
                .removeClass('ui-selected');

            // Only apply selection style changes on valid nodes
            if (!event || (!event.uid && !event.node)) {
                return;
            }

            // Normally, ADM node id is provided in event.uid
            uid = event && event.uid;
            // Fallback is to try event.node.getUid()
            uid = uid || (event.node)?event.node.getUid():null;

            if (uid) {
                widget.options.contentDocument
                    .find('.adm-node[data-uid=\''+uid+'\']')
                    .not('[data-role=\'page\']')
                    .addClass('ui-selected').first().each(function() {
                        // Scroll selected node into view
                        this.scrollIntoViewIfNeeded();
                    });
            }
        },

        _activePageChangedHandler: function(event, widget) {
            var win,
                newPage = event && event.page,
                id = newPage && newPage.getProperty('id');

            widget = widget || this;

            // Only change if new page is valid
            if (!newPage) {
                return;
            }

            win = widget.options.contentDocument[0].defaultView;

            if (win && win.$ && win.$.mobile) {
                win.$.mobile.changePage("#"+id, {transition: "none"});
            }
        },

        _modelUpdatedHandler: function(event, widget) {
            var win;

            widget = widget || this;

            widget.refresh(event, widget);

            win = widget.options.contentDocument[0].defaultView;
            if (win && win.$ && win.$.mobile) {
                win.$.mobile.activePage.page('destroy');
                win.$.mobile.activePage.page();
            } else {
                console.error(widget.widgetName, ':: Missing contentDocument');
            }
        },

        _createDocument: function() {
            var contents, doc;

            if (!this.designRoot) return;

            doc = this.options.contentDocument[0];
            doc.open();
            contents = this._serializeFramework(this.designRoot);
            doc.writeln(contents);
            doc.close();
        },

        _serializeFramework: function() {
            var start, end, ret, headers;

            headers = this._getCustomHeaders();

            start = '<!DOCTYPE html>\n <title>Page Title</title>\n';
            end = "\n\n\n";

            if (headers && headers.length > 0) {
                ret = start + headers.join('\n') + end;
            } else {
                ret = start + end;
            }

            return ret;
        },

        // Great assumptions are being made here that the incoming default
        // headers are already "sorted" and in the order in which they should
        // be inserted into the  node of the document being created...
        _getCustomHeaders: function() {
            var dh = $.gb.getDefaultHeaders(),   // default headers
                ch = this.options.customHeaders, // our custom headers
                m, s;

            $.each(dh, function(idx) {
                if (/<meta/.test(this)) {
                    m = idx+1; // Insert our Meta after LAST one
                    return;
                }
                if (/


<commit_before>import FS from "fs";
import Path from "path";
import _ from "lodash-fp";
import { split, match } from "../helpers";

export const title = "Day 25: Let It Snow";

export const parse = _.compose(
    _.map(_.parseInt(10)),
    _.map(_.first),
    _.map(match(/\d+$/)),
    split("\n")
);

export function run() {
    const inputPath = Path.join(__dirname, "input.txt");
    const input = FS.readFileSync(inputPath, "utf-8").trim();

    console.log("What code do you give the machine?");
}
<commit_msg>Completed the implementation of getCodeAt.<commit_after>import FS from "fs";
import Path from "path";
import _ from "lodash-fp";
import { split, match } from "../helpers";

export const title = "Day 25: Let It Snow";

export const parse = _.compose(
    _.map(_.parseInt(10)),
    _.map(_.first),
    _.map(match(/\d+$/)),
    split("\n")
);

export function getCodeAt(targetX, targetY) {
    const findCode = (value, height, x, y) => {
        if (x == targetX && y == targetY)
            return value;

        const nextValue = (value * 252533) % 33554393;

        if (y > 1)
            return findCode(nextValue, height, x + 1, y - 1);

        return findCode(nextValue, height + 1, 1, height + 1);
    };

    return findCode(20151125, 1, 1, 1);
}

export function run() {
    const inputPath = Path.join(__dirname, "input.txt");
    const input = FS.readFileSync(inputPath, "utf-8").trim();

    console.log("What code do you give the machine?");
}
<|endoftext|>


<commit_before>/* File: jstree.checkbox.js
Adds checkboxes to the tree.
*/
(function ($) {
	$.jstree.plugin("checkbox", {
		__construct : function () {
			this.get_container()
				.bind("__construct.jstree", $.proxy(function () {
						// TODO: on move/copy - clean new location and parents
					}, this))
				.bind("move_node.jstree, copy_node.jstree", function (e, data) {
						if(data.rslt.old_instance && data.rslt.old_parent && $.isFunction(data.rslt.old_instance.checkbox_repair)) {
							data.rslt.old_instance.checkbox_repair(data.rslt.old_parent);
						}
						if(data.rslt.new_instance && $.isFunction(data.rslt.new_instance.checkbox_repair)) {
							data.rslt.new_instance.checkbox_repair(data.rslt.parent);
						}
					})
				.bind("delete_node.jstree", function (e, data) {
						this.checkbox_repair(data.rslt.parent);
					})
				.delegate("a", "click.jstree", $.proxy(function (e) {
						e.preventDefault();
						e.currentTarget.blur();
						var obj = this.get_node(e.currentTarget);
						this.toggle_check(obj);
					}, this));
		},
		defaults : {
			three_state : true
		},
		_fn : {
			/*
				Group: CHECKBOX functions
			*/
			check_node : function (obj) {
				obj = this.get_node(obj);
				obj.find(' > a > .jstree-checkbox').removeClass('jstree-unchecked jstree-undetermined').addClass('jstree-checked').children(':checkbox').prop('checked', true).prop('undermined', false);
				this.checkbox_repair(obj);
			},
			uncheck_node : function (obj) {
				obj = this.get_node(obj);
				obj.find(' > a > .jstree-checkbox').removeClass('jstree-checked jstree-undetermined').addClass('jstree-unchecked').children(':checkbox').prop('checked', false).prop('undermined', false);
				this.checkbox_repair(obj);
			},
			toggle_check : function (obj) {
				obj = obj.find(' > a > .jstree-checkbox').removeClass('jstree-undetermined').toggleClass('jstree-checked');
				if(!obj.hasClass('jstree-checked')) {
					obj.addClass('jstree-unchecked').children(':checkbox').prop('checked', false).prop('undermined', false);
				}
				else {
					obj.children(':checkbox').prop('checked', true).prop('undermined', false);
				}
				this.checkbox_repair(this.get_node(obj));
			},
			uncheck_all : function (context) {
				var ret = context ? $(context).find(".jstree-checked").closest('li') : this.get_container().find(".jstree-checked").closest('li');
				ret.children(".jstree-checkbox").removeClass("jstree-checked jstree-undetermined").addClass('jstree-unchecked').children(':checkbox').prop('checked', false).prop('undermined', false);
				this.__callback({ "obj" : ret });
			},

			checkbox_repair : function (obj) {
				if(!this.get_settings(true).checkbox.three_state) { return false; }

				if(!obj || obj === -1) {
					obj = this.get_container_ul().children('li');
				}
				if(obj.length > 1) {
					obj.each($.proxy(function (i, d) {
						this.checkbox_repair($(d));
					}, this));
					return;
				}

				var c = obj.find(' > a > .jstree-checkbox'),
					fix_up = true,
					p, st, sc, su, si;

				if(!c.hasClass('jstree-checked') && !c.hasClass('jstree-unchecked')) {
					p = this.get_parent(obj);
					if(p && p !== -1 && p.length && p.find('> a > .jstree-checked').length) { c.addClass('jstree-checked'); }
					else { c.addClass('jstree-unchecked'); }
					fix_up = false;
				}

				if(c.hasClass('jstree-checked')) {
					obj.find('.jstree-checkbox').removeClass('jstree-undetermined jstree-unchecked').addClass('jstree-checked').children(':checkbox').prop('checked', true).prop('undermined', false);
				}
				if(c.hasClass('jstree-unchecked')) {
					obj.find('.jstree-checkbox').removeClass('jstree-undetermined jstree-checked').addClass('jstree-unchecked').children(':checkbox').prop('checked', false).prop('undermined', false);
				}

				while(fix_up) {
					obj = this.get_parent(obj);
					if(!obj || obj === -1 || !obj.length) { return; }

					st = obj.find(' > ul > li');
					sc = st.find(' > a > .jstree-checked').length;
					su = st.find(' > a > .jstree-unchecked').length;
					si = st.find(' > a > .jstree-undetermined').length;
					st = st.length;

					if(sc + su + si < st) { return; }

					if(su === st) {
						c = obj.find(' > a > .jstree-checkbox');
						if(c.hasClass('jstree-unchecked')) { return; }
						c.removeClass('jstree-undetermined jstree-checked').addClass('jstree-unchecked').children(':checkbox').prop('checked', false).prop('undermined', false);
						continue;
					}
					if(sc === st) {
						c = obj.find(' > a > .jstree-checkbox');
						if(c.hasClass('jstree-checked')) { return; }
						c.removeClass('jstree-undetermined jstree-unchecked').addClass('jstree-checked').children(':checkbox').prop('checked', true).prop('undermined', false);
						continue;
					}
					obj.parentsUntil(".jstree", "li").andSelf().find(' > a > .jstree-checkbox').removeClass('jstree-checked jstree-unchecked').addClass('jstree-undetermined').children(':checkbox').prop('checked', false).prop('undetermined', true);
					return;
				}
			},

			clean_node : function(obj) {
				obj = this.__call_old();
				var t = this;
				obj = obj.each(function () {
					var o = $(this),
						d = o.data("jstree");
					o.find(" > a > .jstree-checkbox").remove();
					o.children("a").prepend("<input class='jstree-check' type='checkbox' " + (d && d.checkbox && d.checkbox.checked ? ' checked="checked" ' : '') + " name='" + (d && d.checkbox && typeof d.checkbox.name !== 'undefined' ? d.checkbox.name : 'jstree[]') + "' value='" + (d && d.checkbox && typeof d.checkbox.value !== 'undefined' ? d.checkbox.value : o.attr('id')) + "' />");
				});
				t.checkbox_repair(obj);
				return obj;
			},
			get_state : function () {
				var state = this.__call_old();
				state.checked = [];
				this.get_container().find('.jstree-checked').closest('li').each(function () { if(this.id) { state.checked.push(this.id); } });
				return state;
			},
			set_state : function (state, callback) {
				if(this.__call_old()) {
					if(state.checkbox) {
						var _this = this;
						this.uncheck_all();
						$.each(state.checkbox, function (i, v) {
							_this.check_node(document.getElementById(v));
						});
						this.checkbox_repair();
						delete state.checkbox;
						this.set_state(state, callback);
						return false;
					}
					return true;
				}
				return false;
			},
			get_json : function (obj, is_callback) {
				var r = this.__call_old(), i;
				if(is_callback) {
					i = obj.find('> a > ins > :checkbox');
					r.data.jstree.checkbox = {};
					r.data.jstree.checkbox.checked = i.parent().hasClass('jstree-checked');
					if(i.attr('name') !== 'jstree[]') { r.data.checkbox.name = i.attr('name'); }
					if(i.val() !== obj.attr('id')) { r.data.checkbox.value = i.val(); }
				}
				return r;
			}
		}
	});
	$(function () {
		// add checkbox specific CSS
		var css_string = '' +
				'.jstree a > .jstree-checkbox { height:16px; width:16px; margin-right:1px; } ' +
				'.jstree-rtl a > .jstree-checkbox { margin-right:0; margin-left:1px; } ' +
				'.jstree .jstree-check { margin:0; padding:0; border:0; display:inline; vertical-align:text-bottom; } ';
		// Correct IE 6 (does not support the > CSS selector)
		if($.jstree.IS_IE6) {
			css_string += '' +
				'.jstree li a .jstree-checkbox { height:16px; width:16px; background:transparent; margin-right:1px; } ' +
				'.jstree-rtl li a .jstree-checkbox { margin-right:0; margin-left:1px; } ';
		}
		// the default stylesheet
		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
	});
	// include the checkbox plugin by default
	$.jstree.defaults.plugins.push("checkbox");
})(jQuery);
//*/<commit_msg>fixes checkbox state restore (closes #216)<commit_after>/* File: jstree.checkbox.js
Adds checkboxes to the tree.
*/
(function ($) {
	$.jstree.plugin("checkbox", {
		__construct : function () {
			this.get_container()
				.bind("__construct.jstree", $.proxy(function () {
						// TODO: on move/copy - clean new location and parents
					}, this))
				.bind("move_node.jstree, copy_node.jstree", function (e, data) {
						if(data.rslt.old_instance && data.rslt.old_parent && $.isFunction(data.rslt.old_instance.checkbox_repair)) {
							data.rslt.old_instance.checkbox_repair(data.rslt.old_parent);
						}
						if(data.rslt.new_instance && $.isFunction(data.rslt.new_instance.checkbox_repair)) {
							data.rslt.new_instance.checkbox_repair(data.rslt.parent);
						}
					})
				.bind("delete_node.jstree", function (e, data) {
						this.checkbox_repair(data.rslt.parent);
					})
				.delegate("a", "click.jstree", $.proxy(function (e) {
						e.preventDefault();
						e.currentTarget.blur();
						var obj = this.get_node(e.currentTarget);
						this.toggle_check(obj);
					}, this));
		},
		defaults : {
			three_state : true
		},
		_fn : {
			/*
				Group: CHECKBOX functions
			*/
			check_node : function (obj) {
				obj = this.get_node(obj);
				obj.find(' > a > .jstree-checkbox').removeClass('jstree-unchecked jstree-undetermined').addClass('jstree-checked').children(':checkbox').prop('checked', true).prop('undermined', false);
				this.checkbox_repair(obj);
			},
			uncheck_node : function (obj) {
				obj = this.get_node(obj);
				obj.find(' > a > .jstree-checkbox').removeClass('jstree-checked jstree-undetermined').addClass('jstree-unchecked').children(':checkbox').prop('checked', false).prop('undermined', false);
				this.checkbox_repair(obj);
			},
			toggle_check : function (obj) {
				obj = obj.find(' > a > .jstree-checkbox').removeClass('jstree-undetermined').toggleClass('jstree-checked');
				if(!obj.hasClass('jstree-checked')) {
					obj.addClass('jstree-unchecked').children(':checkbox').prop('checked', false).prop('undermined', false);
				}
				else {
					obj.children(':checkbox').prop('checked', true).prop('undermined', false);
				}
				this.checkbox_repair(this.get_node(obj));
			},
			uncheck_all : function (context) {
				var ret = context ? $(context).find(".jstree-checked").closest('li') : this.get_container().find(".jstree-checked").closest('li');
				ret.children(".jstree-checkbox").removeClass("jstree-checked jstree-undetermined").addClass('jstree-unchecked').children(':checkbox').prop('checked', false).prop('undermined', false);
				this.__callback({ "obj" : ret });
			},

			checkbox_repair : function (obj) {
				if(!this.get_settings(true).checkbox.three_state) { return false; }

				if(!obj || obj === -1) {
					obj = this.get_container_ul().children('li');
				}
				if(obj.length > 1) {
					obj.each($.proxy(function (i, d) {
						this.checkbox_repair($(d));
					}, this));
					return;
				}

				var c = obj.find(' > a > .jstree-checkbox'),
					fix_up = true,
					p, st, sc, su, si;

				if(!c.hasClass('jstree-checked') && !c.hasClass('jstree-unchecked')) {
					p = this.get_parent(obj);
					if(p && p !== -1 && p.length && p.find('> a > .jstree-checked').length) { c.addClass('jstree-checked'); }
					else { c.addClass('jstree-unchecked'); }
					fix_up = false;
				}

				if(c.hasClass('jstree-checked')) {
					obj.find('.jstree-checkbox').removeClass('jstree-undetermined jstree-unchecked').addClass('jstree-checked').children(':checkbox').prop('checked', true).prop('undermined', false);
				}
				if(c.hasClass('jstree-unchecked')) {
					obj.find('.jstree-checkbox').removeClass('jstree-undetermined jstree-checked').addClass('jstree-unchecked').children(':checkbox').prop('checked', false).prop('undermined', false);
				}

				while(fix_up) {
					obj = this.get_parent(obj);
					if(!obj || obj === -1 || !obj.length) { return; }

					st = obj.find(' > ul > li');
					sc = st.find(' > a > .jstree-checked').length;
					su = st.find(' > a > .jstree-unchecked').length;
					si = st.find(' > a > .jstree-undetermined').length;
					st = st.length;

					if(sc + su + si < st) { return; }

					if(su === st) {
						c = obj.find(' > a > .jstree-checkbox');
						if(c.hasClass('jstree-unchecked')) { return; }
						c.removeClass('jstree-undetermined jstree-checked').addClass('jstree-unchecked').children(':checkbox').prop('checked', false).prop('undermined', false);
						continue;
					}
					if(sc === st) {
						c = obj.find(' > a > .jstree-checkbox');
						if(c.hasClass('jstree-checked')) { return; }
						c.removeClass('jstree-undetermined jstree-unchecked').addClass('jstree-checked').children(':checkbox').prop('checked', true).prop('undermined', false);
						continue;
					}
					obj.parentsUntil(".jstree", "li").andSelf().find(' > a > .jstree-checkbox').removeClass('jstree-checked jstree-unchecked').addClass('jstree-undetermined').children(':checkbox').prop('checked', false).prop('undetermined', true);
					return;
				}
			},

			clean_node : function(obj) {
				obj = this.__call_old();
				var t = this;
				obj = obj.each(function () {
					var o = $(this),
						d = o.data("jstree");
					o.find(" > a > .jstree-checkbox").remove();
					o.children("a").prepend("<input class='jstree-check' type='checkbox' " + (d && d.checkbox && d.checkbox.checked ? ' checked="checked" ' : '') + " name='" + (d && d.checkbox && typeof d.checkbox.name !== 'undefined' ? d.checkbox.name : 'jstree[]') + "' value='" + (d && d.checkbox && typeof d.checkbox.value !== 'undefined' ? d.checkbox.value : o.attr('id')) + "' />");
				});
				t.checkbox_repair(obj);
				return obj;
			},
			get_state : function () {
				var state = this.__call_old();
				state.checkbox = [];
				this.get_container().find('.jstree-checked').closest('li').each(function () { if(this.id) { state.checkbox.push(this.id); } });
				return state;
			},
			set_state : function (state, callback) {
				if(this.__call_old()) {
					if(state.checkbox) {
						var _this = this;
						this.uncheck_all();
						$.each(state.checkbox, function (i, v) {
							_this.check_node(document.getElementById(v));
						});
						this.checkbox_repair();
						delete state.checkbox;
						this.set_state(state, callback);
						return false;
					}
					return true;
				}
				return false;
			},
			get_json : function (obj, is_callback) {
				var r = this.__call_old(), i;
				if(is_callback) {
					i = obj.find('> a > ins > :checkbox');
					r.data.jstree.checkbox = {};
					r.data.jstree.checkbox.checked = i.parent().hasClass('jstree-checked');
					if(i.attr('name') !== 'jstree[]') { r.data.checkbox.name = i.attr('name'); }
					if(i.val() !== obj.attr('id')) { r.data.checkbox.value = i.val(); }
				}
				return r;
			}
		}
	});
	$(function () {
		// add checkbox specific CSS
		var css_string = '' +
				'.jstree a > .jstree-checkbox { height:16px; width:16px; margin-right:1px; } ' +
				'.jstree-rtl a > .jstree-checkbox { margin-right:0; margin-left:1px; } ' +
				'.jstree .jstree-check { margin:0; padding:0; border:0; display:inline; vertical-align:text-bottom; } ';
		// Correct IE 6 (does not support the > CSS selector)
		if($.jstree.IS_IE6) {
			css_string += '' +
				'.jstree li a .jstree-checkbox { height:16px; width:16px; background:transparent; margin-right:1px; } ' +
				'.jstree-rtl li a .jstree-checkbox { margin-right:0; margin-left:1px; } ';
		}
		// the default stylesheet
		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
	});
	// include the checkbox plugin by default
	$.jstree.defaults.plugins.push("checkbox");
})(jQuery);
//*/<|endoftext|>