/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * 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.
 */

package com.android.location.gnss;

import static android.location.provider.ProviderProperties.ACCURACY_FINE;
import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH;

import android.annotation.Nullable;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationRequest;
import android.location.provider.LocationProviderBase;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.os.Bundle;
import android.util.SparseArray;


import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ConcurrentUtils;

import java.util.List;

/** Basic pass-through GNSS location provider implementation. */
public class GnssOverlayLocationProvider extends LocationProviderBase {

    private static final String TAG = "GnssOverlay";

    private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder()
                .setHasAltitudeSupport(true)
                .setHasSpeedSupport(true)
                .setHasBearingSupport(true)
                .setPowerUsage(POWER_USAGE_HIGH)
                .setAccuracy(ACCURACY_FINE)
                .build();

    @GuardedBy("mPendingFlushes")
    private final SparseArray<OnFlushCompleteCallback> mPendingFlushes = new SparseArray<>();

    private final LocationManager mLocationManager;

    private final GnssLocationListener mGnssLocationListener = new GnssLocationListener();

    @GuardedBy("mPendingFlushes")
    private int mFlushCode = 0;

    /** Location listener for receiving locations from LocationManager. */
    private class GnssLocationListener implements LocationListener {
        @Override
        public void onLocationChanged(Location location) {
            reportLocation(location);
        }

        @Override
        public void onLocationChanged(List<Location> locations) {
            reportLocations(locations);
        }

        @Override
        public void onFlushComplete(int requestCode) {
            OnFlushCompleteCallback flushCompleteCallback;
            synchronized (mPendingFlushes) {
                flushCompleteCallback = mPendingFlushes.get(requestCode);
                mPendingFlushes.remove(requestCode);
            }
            if (flushCompleteCallback != null) {
                flushCompleteCallback.onFlushComplete();
            }
        }
    }

    public GnssOverlayLocationProvider(Context context) {
        super(context, TAG, PROPERTIES);
        mLocationManager = context.getSystemService(LocationManager.class);
    }

    void start() {
    }

    void stop() {
        mLocationManager.removeUpdates(mGnssLocationListener);
    }

    @Override
    public void onSendExtraCommand(String command, @Nullable Bundle extras) {
        mLocationManager.sendExtraCommand(LocationManager.GPS_HARDWARE_PROVIDER, command, extras);
    }

    @Override
    public void onFlush(OnFlushCompleteCallback callback) {
        int flushCodeCopy;
        synchronized (mPendingFlushes) {
            flushCodeCopy = mFlushCode++;
            mPendingFlushes.put(flushCodeCopy, callback);
        }
        mLocationManager.requestFlush(
                LocationManager.GPS_HARDWARE_PROVIDER, mGnssLocationListener, flushCodeCopy);
    }

    @Override
    public void onSetRequest(ProviderRequest request) {
        if (request.isActive()) {
            mLocationManager.requestLocationUpdates(
                    LocationManager.GPS_HARDWARE_PROVIDER,
                    new LocationRequest.Builder(request.getIntervalMillis())
                            .setMaxUpdateDelayMillis(request.getMaxUpdateDelayMillis())
                            .setLowPower(request.isLowPower())
                            .setLocationSettingsIgnored(request.isLocationSettingsIgnored())
                            .setWorkSource(request.getWorkSource())
                            .setQuality(request.getQuality())
                            .build(),
                    ConcurrentUtils.DIRECT_EXECUTOR,
                    mGnssLocationListener);
        } else {
            mLocationManager.removeUpdates(mGnssLocationListener);
        }
    }
}