/* This file is part of Clementine.
   Copyright 2012, David Sansome <me@davidsansome.com>

   Clementine is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Clementine is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Clementine.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "geolocator.h"

#include <cmath>
#include <limits>

#include <qjson/parser.h>

#include <QStringList>

#include "core/closure.h"
#include "core/logging.h"

const char* Geolocator::kUrl = "http://data.clementine-player.org/geolocate";

using std::numeric_limits;

Geolocator::LatLng::LatLng()
    : lat_e6_(numeric_limits<int>::min()),
      lng_e6_(numeric_limits<int>::min()) {}

Geolocator::LatLng::LatLng(int lat_e6, int lng_e6)
    : lat_e6_(lat_e6), lng_e6_(lng_e6) {}

Geolocator::LatLng::LatLng(const QString& latlng)
    : lat_e6_(numeric_limits<int>::min()), lng_e6_(numeric_limits<int>::min()) {
  QStringList split = latlng.split(",");
  if (split.length() != 2) {
    return;
  }

  const double lat = split[0].toDouble();
  const double lng = split[1].toDouble();
  lat_e6_ = static_cast<int>(lat * 1e6);
  lng_e6_ = static_cast<int>(lng * 1e6);
}

Geolocator::LatLng::LatLng(const QString& lat, const QString& lng) {
  lat_e6_ = static_cast<int>(lat.toDouble() * 1e6);
  lng_e6_ = static_cast<int>(lng.toDouble() * 1e6);
}

int Geolocator::LatLng::Distance(const Geolocator::LatLng& other) const {
  static const int kEarthRadiusMetres = 6372800;

  double lat_a = lat_e6() / 1000000.0 * (M_PI / 180.0);
  double lng_a = lng_e6() / 1000000.0 * (M_PI / 180.0);

  double lat_b = other.lat_e6() / 1000000.0 * (M_PI / 180.0);
  double lng_b = other.lng_e6() / 1000000.0 * (M_PI / 180.0);

  double delta_longitude = lng_b - lng_a;

  double sines = sin(lat_a) * sin(lat_b);
  double cosines = cos(lat_a) * cos(lat_b) * cos(delta_longitude);
  double central_angle = acos(sines + cosines);
  double distance_metres = kEarthRadiusMetres * central_angle;
  return static_cast<int>(distance_metres);
}

bool Geolocator::LatLng::IsValid() const {
  return lat_e6_ != numeric_limits<int>::min() &&
         lng_e6_ != numeric_limits<int>::min();
}

Geolocator::Geolocator(QObject* parent) : QObject(parent) {}

void Geolocator::Geolocate() {
  QNetworkRequest req = QNetworkRequest(QUrl(kUrl));
  QNetworkReply* reply = network_.get(req);
  NewClosure(reply, SIGNAL(finished()), this,
             SLOT(RequestFinished(QNetworkReply*)), reply);
}

void Geolocator::RequestFinished(QNetworkReply* reply) {
  reply->deleteLater();
  if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) != 200) {
    emit Finished(LatLng());
    return;
  }

  QJson::Parser parser;
  bool ok = false;
  QVariant result = parser.parse(reply, &ok);
  if (!ok) {
    emit Finished(LatLng());
    return;
  }

  QVariantMap map = result.toMap();
  QString latlng = map["latlng"].toString();

  LatLng ll(latlng);
  emit Finished(ll);
}

QDebug operator<<(QDebug dbg, const Geolocator::LatLng& ll) {
  dbg.nospace() << "(" << ll.lat_e6() << "," << ll.lng_e6() << ")";
  return dbg.space();
}
