angular
  .module('mage')
  .controller('MageController', MageController);

MageController.$inject = [
  '$scope',
  '$compile',
  '$timeout',
  '$animate',
  '$document',
  '$uibModal',
  'UserService',
  'FilterService',
  'EventService',
  'MapService',
  'LocalStorageService',
  'Observation',
  'Location',
  'LocationService',
  'FeatureService'
];

function MageController($scope, $compile, $timeout, $animate, $document, $uibModal, UserService, FilterService, EventService, MapService, LocalStorageService, Observation, Location, LocationService, FeatureService) {
  $scope.hideFeed = false;

  var observationsById = {};
  var newObservation = null;
  var firstObservationChange = true;
  $scope.feedObservations = [];
  $scope.feedChangedObservations = {count: 0};

  var usersById = {};
  var followingUserId = null;
  var firstUserChange = true;
  $scope.feedUsers = [];
  $scope.feedChangedUsers = {};

  $scope.filteredEvent = FilterService.getEvent();
  $scope.filteredInterval = FilterService.getIntervalChoice().label;

  $animate.on('addClass', $document.find('.feed'), resolveMapAfterFeaturesPaneTransition);
  $animate.on('removeClass', $document.find('.feed'), resolveMapAfterFeaturesPaneTransition);

  function resolveMapAfterFeaturesPaneTransition($mapPane, animationPhase) {
    if (animationPhase === 'close') {
      MapService.hideFeed($scope.hideFeed);
    }
  }

  // TODO is there a better way to do this?
  // Need to hang onto popup scopes so that I can delete the scope if the observation
  // get deleted.  I.E. no observation, no popup so remove its scope
  // Possible user of leaflet templates to accomplish this in leaflet
  // rather than angular
  var popupScopes = {};

  var observationLayer = {
    name: 'Observations',
    group: 'MAGE',
    type: 'geojson',
    options: {
      selected: true,
      cluster: true,
      popup: {
        html: function(observation) {
          var el = angular.element('<div observation-popup="observation" observation-popup-info="onInfo(observation)" observation-zoom="onZoom(observation)"></div>');
          var compiled = $compile(el);
          var newScope = $scope.$new(true);
          newScope.observation = observation;
          newScope.onInfo = function(observation) {
            $timeout(function() {
              onObservationSelected(observation, {scrollTo: true});
            });
          };

          newScope.onZoom = function(observation) {
            MapService.zoomToFeatureInLayer(observation, 'Observations');
          };

          compiled(newScope);
          popupScopes[observation.id] = newScope;

          return el[0];
        },
        closeButton: false,
        onClose: function(observation) {
          $timeout(function() {
            onObservationDeselected(observation);
          });
        }
      }
    }
  };
  MapService.createVectorLayer(observationLayer);

  var peopleLayer = {
    name: 'People',
    group: 'MAGE',
    type: 'geojson',
    options: {
      selected: true,
      cluster: false,
      showAccuracy: true,
      temporal: {
        property: 'timestamp',
        colorBuckets: LocationService.colorBuckets
      },
      popup: {
        html: function(location) {
          var user = usersById[location.id];
          var el = angular.element('<div location-popup="user" user-popup-info="onInfo(user)" user-zoom="onZoom(user)"></div>');
          var compiled = $compile(el);
          var newScope = $scope.$new(true);
          newScope.user = user;
          newScope.onInfo = function(user) {
            $timeout(function() {
              onUserSelected(user, {scrollTo: true});
            });
          };

          newScope.onZoom = function(user) {
            MapService.zoomToFeatureInLayer(user, 'People');
          };

          compiled(newScope);
          popupScopes[user.id] = newScope;

          return el[0];
        },
        closeButton: false,
        onClose: function(user) {
          $timeout(function() {
            onUserDeselected(user);
          });
        }
      }
    }
  };
  MapService.createVectorLayer(peopleLayer);

  var observationsChangedListener = {
    onObservationsChanged: onObservationsChanged
  };
  EventService.addObservationsChangedListener(observationsChangedListener);

  var layersChangedListener = {
    onLayersChanged: onLayersChanged
  };
  EventService.addLayersChangedListener(layersChangedListener);

  var filterChangedListener = {
    onFilterChanged: onFilterChanged
  };
  FilterService.addListener(filterChangedListener);

  var usersChangedListener = {
    onUsersChanged: onUsersChanged
  };
  EventService.addUsersChangedListener(usersChangedListener);

  var pollListener = {
    onPoll: onPoll
  };
  EventService.addPollListener(pollListener);

  var locationListener = {
    onLocation: onLocation,
    onBroadcastLocation: onBroadcastLocation
  };
  MapService.addListener(locationListener);

  function onFilterChanged(filter) {
    $scope.feedChangedObservations = {count: 0};
    $scope.feedChangedUsers = {};

    firstUserChange = true;
    firstObservationChange = true;

    if (filter.event) {
      $scope.filteredEvent = FilterService.getEvent();

      // Close the new observation panel if its open
      // If it was open it was open for a different event
      $scope.$broadcast('observation:cancel');

      // Stop broadcasting location if the event switches
      MapService.onLocationStop();
    }

    if (filter.teams) $scope.filteredTeams = _.map(FilterService.getTeams(), function(t) { return t.name; }).join(', ');
    if (filter.timeInterval) {
      var intervalChoice = FilterService.getIntervalChoice();
      if (intervalChoice.filter !== 'all') {
        if (intervalChoice.filter === 'custom') {
          // TODO format custom time interval
          $scope.filteredInterval = 'Custom time interval';
        } else {
          $scope.filteredInterval = intervalChoice.label;
        }
      } else {
        $scope.filteredInterval = null;
      }
    }
  }

  function onLayersChanged(changed, event) {
    var baseLayerFound = false;
    _.each(changed.added, function(layer) {
      // Add token to the url of all private layers
      // TODO add watch for token change and reset the url for these layers
      if (layer.type === 'Imagery' && layer.url.indexOf('private') === 0) {
        layer.url = layer.url + "?access_token=" + LocalStorageService.getToken();
      }

      if (layer.type === 'Imagery') {
        if (layer.base && !baseLayerFound) {
          layer.options = {selected: true};
          baseLayerFound = true;
        }

        MapService.createRasterLayer(layer);
      } else if (layer.type === 'Feature') {
        FeatureService.getFeatureCollection(event, layer).then(function(featureCollection) {
          MapService.createVectorLayer({
            name: layer.name, // TODO need to track by id as well not just names
            group: 'Static',
            type: 'geojson',
            geojson: featureCollection,
            options: {
              popup: {
                html: function(feature) {
                  // TODO user leaflet template for this
                  var content = "";
                  if (feature.properties.name) {
                    content += '<div><strong><u>' + feature.properties.name + '</u></strong></div>';
                  }
                  if (feature.properties.description) {
                    content += '<div>' + feature.properties.description + '</div>';
                  }

                  return content;
                }
              }
            }
          });
        });
      }
    });

    _.each(changed.removed, function(layer) {
      MapService.removeLayer(layer);
    });
  }

  function onObservationsChanged(changed) {
    _.each(changed.added, function(added) {
      observationsById[added.id] = added;
    });
    if (changed.added.length) MapService.addFeaturesToLayer(changed.added, 'Observations');

    _.each(changed.updated, function(updated) {
      var observation = observationsById[updated.id];
      if (observation) {
        observationsById[updated.id] = updated;
        popupScopes[updated.id].user = updated;
        MapService.updateFeatureForLayer(updated, 'Observations');
      }
    });

    _.each(changed.removed, function(removed) {
      delete observationsById[removed.id];

      MapService.removeFeatureFromLayer(removed, 'Observations');

      var scope = popupScopes[removed.id];
      if (scope) {
        scope.$destroy();
        delete popupScopes[removed.id];
      }
    });

    // update the news feed observations
    $scope.feedObservations = _.values(observationsById);

    if (!firstObservationChange) {
      if (changed.added) $scope.feedChangedObservations.count += changed.added.length;
      if (changed.updated) $scope.feedChangedObservations.count += changed.updated.length;
    }

    firstObservationChange = false;
  }

  function onUsersChanged(changed) {
    _.each(changed.added, function(added) {
      usersById[added.id] = added;

      MapService.addFeaturesToLayer([added.location], 'People');

      if (!firstUserChange) $scope.feedChangedUsers[added.id] = true;
    });

    _.each(changed.updated, function(updated) {
      var user = usersById[updated.id];
      if (user) {
        usersById[updated.id] = updated;
        popupScopes[updated.id].user = updated;
        MapService.updateFeatureForLayer(updated.location, 'People');

        // pan/zoom map to user if this is the user we are following
        if (user.id === followingUserId) MapService.zoomToFeatureInLayer(user, 'People');
      }

      if (!firstUserChange) $scope.feedChangedUsers[updated.id] = true;
    });

    _.each(changed.removed, function(removed) {
      delete usersById[removed.id];

      MapService.removeFeatureFromLayer(removed.location, 'People');

      var scope = popupScopes[removed.id];
      if (scope) {
        scope.$destroy();
        delete popupScopes[removed.id];
      }
    });

    // update the news feed observations
    $scope.feedUsers = _.values(usersById);

    firstUserChange = false;
  }

  function onLocation(l) {
    var event = FilterService.getEvent();
    Location.create({
      eventId: event.id,
      geometry: {
        type: 'Point',
        coordinates: [l.longitude, l.latitude]
      },
      properties: {
        timestamp: new Date().valueOf(),
        accuracy: l.accuracy,
        altitude: l.altitude,
        altitudeAccuracy: l.altitudeAccuracy,
        heading: l.heading,
        speed: l.speed
      }
    });
  }

  function onBroadcastLocation(callback) {
    if (!EventService.isUserInEvent(UserService.myself, FilterService.getEvent())) {
      $uibModal.open({
        templateUrl: '/app/error/not.in.event.html',
        controller: 'NotInEventController',
        resolve: {
          title: function() {
            return 'Cannot Broadcast Location';
          }
        }
      });

      callback(false);
    } else {
      callback(true);
    }
  }

  function onPoll() {
    // The event service just polled, lets update times and marker colors
    $scope.$broadcast('observation:poll');
    $scope.$broadcast('user:poll');
    MapService.onPoll();
  }

  function onObservationSelected(observation, options) {
    $scope.$broadcast('observation:select', observation, options);
    if (options.scrollTo) {
      $scope.hideFeed = false;
      MapService.hideFeed(false);
    }
  }

  function onObservationDeselected(observation) {
    $scope.$broadcast('observation:deselect', observation);
  }

  function onUserSelected(user, options) {
    $scope.$broadcast('user:select', user);
    if (options.scrollTo) {
      $scope.hideFeed = false;
      MapService.hideFeed(false);
    }
  }

  function onUserDeselected(user) {
    $scope.$broadcast('user:deselect', user);
  }

  $scope.$on('$destroy', function() {
    FilterService.removeListener(filterChangedListener);
    EventService.removeLayersChangedListener(layersChangedListener);
    EventService.removeObservationsChangedListener(observationsChangedListener);
    EventService.removeUsersChangedListener(usersChangedListener);
    EventService.removePollListener(pollListener);
    MapService.destroy();
  });

  $scope.$on('observation:selected', function(e, observation, options) {
    onObservationSelected(observation, options);
  });

  $scope.$on('observation:zoom', function(e, observation) {
    MapService.zoomToFeatureInLayer(observation, 'Observations');
  });

  $scope.$on('user:zoom', function(e, user) {
    MapService.zoomToFeatureInLayer(user, 'People');
  });

  $scope.$on('user:follow', function(e, user) {
    if (user && user.id !== followingUserId) {
      followingUserId = user.id;
      MapService.zoomToFeatureInLayer(user, 'People');
    } else {
      followingUserId = null;
    }
  });

  $scope.$on('observation:create', function(e, latlng) {
    $scope.hideFeed = false;
    MapService.hideFeed(false);

    var event = FilterService.getEvent();

    if (!EventService.isUserInEvent(UserService.myself, event)) {
      $uibModal.open({
        templateUrl: '/app/error/not.in.event.html',
        controller: 'NotInEventController',
        resolve: {
          title: function() {
            return 'Cannot Create Observation';
          }
        }
      });

      return;
    }

    newObservation = new Observation({
      eventId: event.id,
      type: 'Feature',
      geometry: {
        type: 'Point',
        coordinates: [latlng.lng, latlng.lat]
      },
      properties: {
        timestamp: new Date()
      }
    });

    MapService.createMarker(newObservation, {
      layerId: 'NewObservation',
      selected: true,
      draggable: true,
      onDragEnd: function(latlng) {
        $scope.$broadcast('observation:moved', newObservation, latlng);
        $scope.$apply();
      }
    });

    $scope.$broadcast('observation:new', newObservation);
    $scope.$apply();
  });

  $scope.$on('observation:editDone', function(e, observation) {
    if (newObservation === observation) {
      MapService.removeMarker(observation, 'NewObservation');
    }

    newObservation = null;
  });

  $scope.$on('observation:move', function(e, observation, latlng) {
    $scope.$broadcast('observation:moved', observation, latlng);
    MapService.updateMarker(observation, 'NewObservation');
  });

  $scope.$on('feed:toggle', function() {
    $scope.hideFeed = !$scope.hideFeed;
    $scope.$apply();
  });

}
