/*
 * Copyright (C) 2018 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 android.car.cluster.sensors;

import static android.car.VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;

import android.car.VehiclePropertyIds;
import android.car.VehiclePropertyType;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.CarSensorEvent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

/**
 * The collection of all sensors supported by this application.
 */
public class Sensors {
    private static Sensors sInstance;
    private static List<Sensor<?>> sSensors = new ArrayList<>();
    private Map<Integer, List<Sensor<?>>> mSensorsByPropertyId = new HashMap<>();

    /** Possible values of the {@link #SENSOR_GEAR} sensor */
    public enum Gear {
        NEUTRAL,
        REVERSE,
        DRIVE,
        PARK,
    }

    /** Fuel of the car, measured in millimeters */
    public static final Sensor<Float> SENSOR_FUEL = registerSensor(
            "Fuel", VehiclePropertyIds.FUEL_LEVEL, VEHICLE_AREA_TYPE_GLOBAL,
            VehiclePropertyType.FLOAT,
            value -> (Float) value.getValue());
    /** Fuel capacity of the car, measured in millimeters */
    public static final Sensor<Float> SENSOR_FUEL_CAPACITY = registerSensor(
            "Fuel Capacity", VehiclePropertyIds.INFO_FUEL_CAPACITY, VEHICLE_AREA_TYPE_GLOBAL,
            VehiclePropertyType.FLOAT,
            value -> (Float) value.getValue());
    /** RPMs */
    public static final Sensor<Float> SENSOR_RPM = registerSensor(
            "RPM", VehiclePropertyIds.ENGINE_RPM, VEHICLE_AREA_TYPE_GLOBAL,
            VehiclePropertyType.FLOAT,
            value -> (Float) value.getValue());
    /** Fuel range in meters */
    public static final Sensor<Float> SENSOR_FUEL_RANGE = registerSensor(
            "Fuel Range", VehiclePropertyIds.RANGE_REMAINING, VEHICLE_AREA_TYPE_GLOBAL,
            VehiclePropertyType.FLOAT,
            value -> (Float) value.getValue());
    /** Speed in meters per second */
    public static final Sensor<Float> SENSOR_SPEED = registerSensor(
            "Speed", VehiclePropertyIds.PERF_VEHICLE_SPEED, VEHICLE_AREA_TYPE_GLOBAL,
            VehiclePropertyType.FLOAT,
            value -> (Float) value.getValue());
    /** Current gear of the car */
    public static final Sensor<Gear> SENSOR_GEAR = registerSensor(
            "Gear", VehiclePropertyIds.GEAR_SELECTION, VEHICLE_AREA_TYPE_GLOBAL,
            VehiclePropertyType.INT32,
            value -> {
                Integer gear = (Integer) value.getValue();
                if ((gear & CarSensorEvent.GEAR_REVERSE) != 0) {
                    return Gear.REVERSE;
                } else if ((gear & CarSensorEvent.GEAR_NEUTRAL) != 0) {
                    return Gear.NEUTRAL;
                } else if ((gear & CarSensorEvent.GEAR_DRIVE) != 0) {
                    return Gear.DRIVE;
                } else if ((gear & CarSensorEvent.GEAR_PARK) != 0) {
                    return Gear.PARK;
                } else {
                    return null;
                }
            });

    private static <T> Sensor<T> registerSensor(String propertyName, int propertyId, int areaId,
            int expectedPropertyType, Function<CarPropertyValue<?>, T> adapter) {
        Sensor<T> sensor = new Sensor<>(propertyName, propertyId, areaId, expectedPropertyType,
                adapter);
        sSensors.add(sensor);
        return sensor;
    }

    /**
     * Obtains the singleton instance of this class
     */
    public static Sensors getInstance() {
        if (sInstance == null) {
            sInstance = new Sensors();
        }
        return sInstance;
    }

    private Sensors() {
        initializeSensorsMap();
    }

    private void initializeSensorsMap() {
        for (Sensor<?> sensorId : getSensors()) {
            mSensorsByPropertyId
                    .computeIfAbsent(sensorId.mPropertyId, (id) -> new ArrayList<>())
                    .add(sensorId);
        }
    }

    /**
     * Returns all sensors.
     */
    public List<Sensor<?>> getSensors() {
        return sSensors;
    }

    /**
     * Returns all sensors associated to the given VHAL property id.
     */
    public List<Sensor<?>> getSensorsForPropertyId(int propertyId) {
        return mSensorsByPropertyId.get(propertyId);
    }

    /**
     * Returns all property ids we care about.
     */
    public Set<Integer> getPropertyIds() {
        return mSensorsByPropertyId.keySet();
    }
}
