// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/geolocation/location_arbitrator.h"

#include <map>

#include "chrome/browser/profiles/profile.h"
#include "content/browser/geolocation/arbitrator_dependency_factory.h"

namespace {

const char* kDefaultNetworkProviderUrl = "https://www.google.com/loc/json";
GeolocationArbitratorDependencyFactory* g_dependency_factory_for_test = NULL;

}  // namespace

// To avoid oscillations, set this to twice the expected update interval of a
// a GPS-type location provider (in case it misses a beat) plus a little.
const int64 GeolocationArbitrator::kFixStaleTimeoutMilliseconds =
    11 * base::Time::kMillisecondsPerSecond;

GeolocationArbitrator::GeolocationArbitrator(
    GeolocationArbitratorDependencyFactory* dependency_factory,
    GeolocationObserver* observer)
    : dependency_factory_(dependency_factory),
      access_token_store_(dependency_factory->NewAccessTokenStore()),
      context_getter_(dependency_factory->GetContextGetter()),
      get_time_now_(dependency_factory->GetTimeFunction()),
      observer_(observer),
      position_provider_(NULL) {
  DCHECK(GURL(kDefaultNetworkProviderUrl).is_valid());
  access_token_store_->LoadAccessTokens(
      &request_consumer_,
      NewCallback(this,
                  &GeolocationArbitrator::OnAccessTokenStoresLoaded));
}

GeolocationArbitrator::~GeolocationArbitrator() {
}

GeolocationArbitrator* GeolocationArbitrator::Create(
    GeolocationObserver* observer) {
  GeolocationArbitratorDependencyFactory* dependency_factory =
      g_dependency_factory_for_test ?
      g_dependency_factory_for_test :
      new DefaultGeolocationArbitratorDependencyFactory;
  GeolocationArbitrator* arbitrator =
      new GeolocationArbitrator(dependency_factory, observer);
  g_dependency_factory_for_test = NULL;
  return arbitrator;
}

void GeolocationArbitrator::OnPermissionGranted(
    const GURL& requesting_frame) {
  most_recent_authorized_frame_ = requesting_frame;
  for (ScopedVector<LocationProviderBase>::iterator i = providers_.begin();
      i != providers_.end(); ++i) {
    (*i)->OnPermissionGranted(requesting_frame);
  }
}

void GeolocationArbitrator::StartProviders(
    const GeolocationObserverOptions& options) {
  // Stash options incase OnAccessTokenStoresLoaded has not yet been called
  // (in which case |providers_| will be empty).
  current_provider_options_ = options;
  StartProviders();
}

void GeolocationArbitrator::StartProviders() {
  for (ScopedVector<LocationProviderBase>::iterator i = providers_.begin();
       i != providers_.end(); ++i) {
    (*i)->StartProvider(current_provider_options_.use_high_accuracy);
  }
}

void GeolocationArbitrator::StopProviders() {
  for (ScopedVector<LocationProviderBase>::iterator i = providers_.begin();
       i != providers_.end(); ++i) {
    (*i)->StopProvider();
  }
}

void GeolocationArbitrator::OnAccessTokenStoresLoaded(
    AccessTokenStore::AccessTokenSet access_token_set) {
  DCHECK(providers_.empty())
      << "OnAccessTokenStoresLoaded : has existing location "
      << "provider. Race condition caused repeat load of tokens?";
  // If there are no access tokens, boot strap it with the default server URL.
  if (access_token_set.empty())
    access_token_set[GURL(kDefaultNetworkProviderUrl)];
  for (AccessTokenStore::AccessTokenSet::iterator i =
           access_token_set.begin();
      i != access_token_set.end(); ++i) {
    RegisterProvider(
        dependency_factory_->NewNetworkLocationProvider(
            access_token_store_.get(), context_getter_.get(),
            i->first, i->second));
  }
  RegisterProvider(dependency_factory_->NewSystemLocationProvider());
  StartProviders();
}

void GeolocationArbitrator::RegisterProvider(
    LocationProviderBase* provider) {
  if (!provider)
    return;
  provider->RegisterListener(this);
  if (most_recent_authorized_frame_.is_valid())
    provider->OnPermissionGranted(most_recent_authorized_frame_);
  providers_->push_back(provider);
}

void GeolocationArbitrator::LocationUpdateAvailable(
    LocationProviderBase* provider) {
  DCHECK(provider);
  Geoposition new_position;
  provider->GetPosition(&new_position);
  DCHECK(new_position.IsInitialized());
  if (!IsNewPositionBetter(position_, new_position,
                           provider == position_provider_))
    return;
  position_provider_ = provider;
  position_ = new_position;
  observer_->OnLocationUpdate(position_);
}

bool GeolocationArbitrator::IsNewPositionBetter(
    const Geoposition& old_position, const Geoposition& new_position,
    bool from_same_provider) const {
  // Updates location_info if it's better than what we currently have,
  // or if it's a newer update from the same provider.
  if (!old_position.IsValidFix()) {
    // Older location wasn't locked.
    return true;
  }
  if (new_position.IsValidFix()) {
    // New location is locked, let's check if it's any better.
    if (old_position.accuracy >= new_position.accuracy) {
      // Accuracy is better.
      return true;
    } else if (from_same_provider) {
      // Same provider, fresher location.
      return true;
    } else if ((get_time_now_() - old_position.timestamp).InMilliseconds() >
               kFixStaleTimeoutMilliseconds) {
      // Existing fix is stale.
      return true;
    }
  }
  return false;
}

bool GeolocationArbitrator::HasPermissionBeenGranted() const {
  return most_recent_authorized_frame_.is_valid();
}

void GeolocationArbitrator::SetDependencyFactoryForTest(
    GeolocationArbitratorDependencyFactory* dependency_factory) {
  g_dependency_factory_for_test = dependency_factory;
}