/*
 * Copyright (C) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "location.h"

#include <parcel.h>
#include <string>
#include "string_ex.h"
#include <iostream>
#include <cmath>

namespace OHOS {
namespace Location {
static constexpr double MIN_LATITUDE = -90.0;
static constexpr double MIN_LONGITUDE = -180.0;
static constexpr int MAX_POI_ARRAY_SIZE = 20;
static constexpr double MAX_LATITUDE = 90.0;
static constexpr double MAX_LONGITUDE = 180.0;
const double PI = 3.1415926;
const double DEGREE_PI = 180.0;
const double POW_PARAMETER_TOW = 2;
const double NUM_DOUBLE = 2;
const double EARTH_SEMI_AXIS = 6378137.0;
const double EARTH_FLATTENING = 6356752.3142;
const double EARTH_SEMI_MINOR = (EARTH_SEMI_AXIS - EARTH_FLATTENING) / EARTH_SEMI_AXIS;

Location::Location()
{
    latitude_ = MIN_LATITUDE - 1;
    longitude_ = MIN_LONGITUDE - 1;
    altitude_ = 0.0;
    accuracy_ = 0.0;
    speed_ = 0.0;
    direction_ = 0.0;
    timeStamp_ = 0;
    timeSinceBoot_ = 0;
    additionSize_ = 0;
    isFromMock_ = false;
    isSystemApp_ = 0;
    floorNo_ = 0;
    floorAccuracy_ = 0.0;
    altitudeAccuracy_ = 0.0;
    speedAccuracy_ = 0.0;
    directionAccuracy_ = 0.0;
    uncertaintyOfTimeSinceBoot_ = 0;
    locationSourceType_ = 0;
    uuid_ = "";
    fieldValidity_ = 0;
}

Location::Location(const Location& location)
{
    latitude_ = location.GetLatitude();
    longitude_ = location.GetLongitude();
    altitude_ = location.GetAltitude();
    accuracy_ = location.GetAccuracy();
    speed_ = location.GetSpeed();
    direction_ = location.GetDirection();
    timeStamp_ = location.GetTimeStamp();
    timeSinceBoot_ = location.GetTimeSinceBoot();
    floorNo_ = location.GetFloorNo();
    floorAccuracy_ = location.GetFloorAccuracy();
    additions_ = location.GetAdditions();
    additionsMap_ = location.GetAdditionsMap();
    additionSize_ = location.GetAdditionSize();
    isFromMock_ = location.GetIsFromMock();
    isSystemApp_ = location.GetIsSystemApp();
    altitudeAccuracy_ = location.GetAltitudeAccuracy();
    speedAccuracy_ = location.GetSpeedAccuracy();
    directionAccuracy_ = location.GetDirectionAccuracy();
    uncertaintyOfTimeSinceBoot_ = location.GetUncertaintyOfTimeSinceBoot();
    locationSourceType_ = location.GetLocationSourceType();
    uuid_ = location.GetUuid();
    fieldValidity_ = location.GetFieldValidity();
    poiInfo_ = location.GetPoiInfo();
}

bool Location::Marshalling(Parcel& parcel) const
{
    return true;
}


std::string Location::ToString() const
{
    std::string str =
        ", altitude : " + std::to_string(altitude_) +
        ", accuracy : " + std::to_string(accuracy_) +
        ", speed : " + std::to_string(speed_) +
        ", direction : " + std::to_string(direction_) +
        ", timeStamp : " + std::to_string(timeStamp_) +
        ", timeSinceBoot : " + std::to_string(timeSinceBoot_) +
        ", additionSize : " + std::to_string(additionSize_) +
        ", isFromMock : " + std::to_string(isFromMock_) +
        ", isSystemApp : " + std::to_string(isSystemApp_) +
        ", altitudeAccuracy : " + std::to_string(altitudeAccuracy_) +
        ", speedAccuracy : " + std::to_string(speedAccuracy_) +
        ", directionAccuracy : " + std::to_string(directionAccuracy_) +
        ", uncertaintyOfTimeSinceBoot : " + std::to_string(uncertaintyOfTimeSinceBoot_) +
        ", locationSourceType : " + std::to_string(locationSourceType_) +
        ", uuid : " + uuid_ +
        ", fieldValidity : " + std::to_string(fieldValidity_);
    return str;
}

bool Location::LocationEqual(const std::unique_ptr<Location>& location)
{
    if (location == nullptr) {
        return false;
    }
    if (this->GetLatitude() == location->GetLatitude() &&
        this->GetLongitude() == location->GetLongitude() &&
        this->GetAltitude() == location->GetAltitude() &&
        this->GetAccuracy() == location->GetAccuracy() &&
        this->GetSpeed() == location->GetSpeed() &&
        this->GetDirection() == location->GetDirection() &&
        this->GetTimeStamp() == location->GetTimeStamp() &&
        this->GetTimeSinceBoot() == location->GetTimeSinceBoot() &&
        this->AdditionEqual(location) &&
        this->GetAdditionSize() == location->GetAdditionSize() &&
        this->GetIsFromMock() == location->GetIsFromMock()) {
        return true;
    }
    return false;
}

bool Location::AdditionEqual(const std::unique_ptr<Location>& location)
{
    if (location == nullptr) {
        return false;
    }
    std::vector<std::string> additionA = this->GetAdditions();
    std::vector<std::string> additionB = location->GetAdditions();
    if (additionA.size() != additionB.size()) {
        return false;
    }
    for (size_t i = 0; i < additionA.size(); i++) {
        if (additionA[i].compare(additionB[i]) != 0) {
            return false;
        }
    }
    return true;
}

bool Location::isValidLatitude(double latitude)
{
    return latitude >= MIN_LATITUDE && latitude <= MAX_LATITUDE;
}

bool Location::isValidLongitude(double longitude)
{
    return longitude >= MIN_LONGITUDE && longitude <= MAX_LONGITUDE;
}

double Location::GetDistanceBetweenLocations(const double lat1, const double lon1, const double lat2, const double lon2)
{
    if (lat1 == lat2 && lon1 == lon2) {
        return 0;
    }
    double radLat1 = lat1 * PI / DEGREE_PI;
    double radLat2 = lat2 * PI / DEGREE_PI;
    double radLon1 = lon1 * PI / DEGREE_PI;
    double radLon2 = lon2 * PI / DEGREE_PI;

    double deltaLon = radLon2 - radLon1;
    double reducedLat1 = atan((1 - EARTH_SEMI_MINOR) * tan(radLat1));
    double reducedLat2 = atan((1 - EARTH_SEMI_MINOR) * tan(radLat2));

    double sinReducedLat1 = sin(reducedLat1);
    double cosReducedLat1 = cos(reducedLat1);
    double sinReducedLat2 = sin(reducedLat2);
    double cosReducedLat2 = cos(reducedLat2);

    double lambda = deltaLon;
    double lambdaP = 100;
    double iterLimit = 20;
    double sinAlpha = 0.0;
    double cosSqAlpha = 0.0;
    double sinSigma = 0.0;
    double cos2SigmaM = 0.0;
    double cosSigma = 0.0;
    double sigma = 0.0;
    double sinLambda = 0.0;
    double cosLambda = 0.0;

    for (int iter = 0; iter < iterLimit; iter++) {
        sinLambda = sin(lambda);
        cosLambda = cos(lambda);
        sinSigma = sqrt(pow(cosReducedLat2 * sinLambda, POW_PARAMETER_TOW) +
            pow(cosReducedLat1 * sinReducedLat2 - sinReducedLat1 * cosReducedLat2 * cosLambda, POW_PARAMETER_TOW));
        if (sinSigma == 0) {
            return 0;
        }
        cosSigma = sinReducedLat1 * sinReducedLat2 + cosReducedLat1 * cosReducedLat2 * cosLambda;
        sigma = atan2(sinSigma, cosSigma);
        sinAlpha = cosReducedLat1 * cosReducedLat2 * sinLambda / sinSigma;
        cosSqAlpha = 1 - sinAlpha * sinAlpha;
        cos2SigmaM = (cosSqAlpha != 0) ? (cosSigma - NUM_DOUBLE * sinReducedLat1 * sinReducedLat2 / cosSqAlpha) : 0;
        double correction = EARTH_SEMI_MINOR / 16 * cosSqAlpha * (4 + EARTH_SEMI_MINOR * (4 - 3 * cosSqAlpha));

        lambdaP = lambda;
        lambda = deltaLon + (1 - correction) * EARTH_SEMI_MINOR * sinAlpha  *
            (sigma + correction * sinSigma *
            (cos2SigmaM + correction * cosSigma * (-1 + NUM_DOUBLE * pow(cos2SigmaM, POW_PARAMETER_TOW))));
        if (fabs(lambda - lambdaP) < 1e-12) {
            break;
        }
    }
    double uSquared = cosSqAlpha * (EARTH_SEMI_AXIS * EARTH_SEMI_AXIS - EARTH_FLATTENING * EARTH_FLATTENING) /
        (EARTH_FLATTENING * EARTH_FLATTENING);
    double highOrderCorrection = 1 + uSquared / 16384 * (4096 + uSquared * (-768 + uSquared * (320 - 175 * uSquared)));
    double highOrderTerm = uSquared / 1024 * (256 + uSquared * (-128 + uSquared * (74 - 47 * uSquared)));
    double deltaSigma = highOrderTerm * sinSigma *
        (cos2SigmaM + highOrderTerm / 4 * (cosSigma * (-1 + 2 * pow(cos2SigmaM, POW_PARAMETER_TOW)) -
            highOrderTerm / 6 * cos2SigmaM * (-3 + 4 * pow(sinSigma, POW_PARAMETER_TOW)) *
            (-3 + 4 * pow(cos2SigmaM, POW_PARAMETER_TOW))));

    double distance = EARTH_FLATTENING * highOrderCorrection * (sigma - deltaSigma);
    return distance;
}
} // namespace Location
} // namespace OHOS