// 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 "chrome/browser/geolocation/win7_location_api_win.h"

#include "base/base_paths_win.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/geoposition.h"

namespace {
const double kKnotsToMetresPerSecondConversionFactor = 0.5144;

void ConvertKnotsToMetresPerSecond(double* knots) {
  *knots *= kKnotsToMetresPerSecondConversionFactor;
}

HINSTANCE LoadWin7Library(const string16& lib_name) {
  FilePath sys_dir;
  PathService::Get(base::DIR_SYSTEM, &sys_dir);
  return LoadLibrary(sys_dir.Append(lib_name).value().c_str());
}
}

Win7LocationApi::Win7LocationApi(
    HINSTANCE prop_library,
    PropVariantToDoubleFunction PropVariantToDouble_function,
    ILocation* locator)
    : prop_lib_(prop_library),
      PropVariantToDouble_function_(PropVariantToDouble_function),
      locator_(locator) {
}

Win7LocationApi::~Win7LocationApi() {
  if (prop_lib_ != NULL)
    FreeLibrary(prop_lib_);
}

Win7LocationApi* Win7LocationApi::Create() {
  if (!CommandLine::ForCurrentProcess()
      ->HasSwitch(switches::kExperimentalLocationFeatures))
    return NULL;
  // Load probsys.dll
  string16 lib_needed = L"propsys.dll";
  HINSTANCE prop_lib = LoadWin7Library(lib_needed);
  if (!prop_lib)
    return NULL;
  // Get pointer to function.
  PropVariantToDoubleFunction PropVariantToDouble_function;
  PropVariantToDouble_function =
      reinterpret_cast<PropVariantToDoubleFunction>(
          GetProcAddress(prop_lib, "PropVariantToDouble"));
  if (!PropVariantToDouble_function) {
    FreeLibrary(prop_lib);
    return NULL;
  }
  // Create the ILocation object that receives location reports.
  HRESULT result_type;
  CComPtr<ILocation> locator;
  result_type = CoCreateInstance(
      CLSID_Location, NULL, CLSCTX_INPROC, IID_PPV_ARGS(&locator));
  if (!SUCCEEDED(result_type)) {
    FreeLibrary(prop_lib);
    return NULL;
  }
  IID reports_needed[] = { IID_ILatLongReport };
  result_type = locator->RequestPermissions(NULL, reports_needed, 1, TRUE);
  return new Win7LocationApi(prop_lib,
                             PropVariantToDouble_function,
                             locator);
}

void Win7LocationApi::GetPosition(Geoposition* position) {
  DCHECK(position);
  position->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
  if (!locator_)
    return;
  // Try to get a position fix
  if (!GetPositionIfFixed(position))
    return;
  position->error_code = Geoposition::ERROR_CODE_NONE;
  if (!position->IsValidFix()) {
    // GetPositionIfFixed returned true, yet we've not got a valid fix.
    // This shouldn't happen; something went wrong in the conversion.
    NOTREACHED() << "Invalid position from GetPositionIfFixed: lat,long "
                 << position->latitude << "," << position->longitude
                 << " accuracy " << position->accuracy << " time "
                 << position->timestamp.ToDoubleT();
    position->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
    position->error_message = "Bad fix from Win7 provider";
  }
}

bool Win7LocationApi::GetPositionIfFixed(Geoposition* position) {
  HRESULT result_type;
  CComPtr<ILocationReport> location_report;
  CComPtr<ILatLongReport> lat_long_report;
  result_type = locator_->GetReport(IID_ILatLongReport, &location_report);
  // Checks to see if location access is allowed.
  if (result_type == E_ACCESSDENIED) 
    position->error_code = Geoposition::ERROR_CODE_PERMISSION_DENIED;
  // Checks for any other errors while requesting a location report.
  if(!SUCCEEDED(result_type))
    return false;
  result_type = location_report->QueryInterface(&lat_long_report);
  if(!SUCCEEDED(result_type))
    return false;
  result_type = lat_long_report->GetLatitude(&position->latitude);
  if(!SUCCEEDED(result_type))
    return false;
  result_type = lat_long_report->GetLongitude(&position->longitude);
  if(!SUCCEEDED(result_type))
    return false;
  result_type = lat_long_report->GetErrorRadius(&position->accuracy);
  if (!SUCCEEDED(result_type) || position->accuracy <= 0)
    return false;
  double temp_dbl;
  result_type = lat_long_report->GetAltitude(&temp_dbl);
  if (SUCCEEDED(result_type))
    position->altitude = temp_dbl;
  result_type = lat_long_report->GetAltitudeError(&temp_dbl);
  if (SUCCEEDED(result_type))
    position->altitude_accuracy = temp_dbl; 
  PROPVARIANT heading;
  PropVariantInit(&heading);
  result_type = lat_long_report->GetValue(
      SENSOR_DATA_TYPE_TRUE_HEADING_DEGREES, &heading);
  if (SUCCEEDED(result_type))
    PropVariantToDouble_function_(heading, &position->heading);
  PROPVARIANT speed;
  PropVariantInit(&speed);
  result_type = lat_long_report->GetValue(
      SENSOR_DATA_TYPE_SPEED_KNOTS, &speed);
  if (SUCCEEDED(result_type)) {
    PropVariantToDouble_function_(speed, &position->speed);
    ConvertKnotsToMetresPerSecond(&position->speed);
  }
  position->timestamp = base::Time::Now();
  return true;
}

bool Win7LocationApi::SetHighAccuracy(bool acc) {
  HRESULT result_type;
  result_type = locator_->SetDesiredAccuracy(IID_ILatLongReport,
      acc ? LOCATION_DESIRED_ACCURACY_HIGH :
            LOCATION_DESIRED_ACCURACY_DEFAULT);
  return SUCCEEDED(result_type);
}