/*
 * Copyright (C) 2016 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.hardware.cabin;

import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.car.Car;
import android.car.CarManagerBase;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyManager;
import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
import android.car.hardware.property.ICarProperty;
import android.os.IBinder;
import android.util.ArraySet;

import com.android.internal.annotations.GuardedBy;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * API for controlling Cabin system in cars.
 * Most Car Cabin properties have both a MOVE and POSITION parameter associated with them.
 *
 * The MOVE parameter will start moving the device in the indicated direction.  Magnitude
 * indicates relative speed.  For instance, setting the WINDOW_MOVE parameter to +1 rolls
 * the window down.  Setting it to +2 (if available) will roll it down faster.
 *
 * POSITION parameter will move the device to the desired position.  For instance, if the
 * WINDOW_POS has a range of 0-100, setting this parameter to 50 will open the window
 * halfway.  Depending upon the initial position, the window may move up or down to the
 * 50% value.
 *
 * One or both of the MOVE/POSITION parameters may be implemented depending upon the
 * capability of the hardware.
 * @hide
 * @deprecated Use {@link CarPropertyManager} instead.
 */
@Deprecated
@SystemApi
public final class CarCabinManager extends CarManagerBase {
    private final Object mLock = new Object();
    private final CarPropertyManager mCarPropertyMgr;
    @GuardedBy("mLock")
    private final ArraySet<CarCabinEventCallback> mCallbacks = new ArraySet<>();
    @GuardedBy("mLock")
    private CarPropertyEventListenerToBase mListenerToBase = null;

    /** Door properties are zoned by VehicleAreaDoor */
    /**
     * door position, int type
     * Max value indicates fully open, min value (0) indicates fully closed.
     *
     * Some vehicles (minivans) can open the door electronically.  Hence, the ability
     * to write this property.
     */
    public static final int ID_DOOR_POS = 0x16400b00;
    /** door move, int type
     * Positive values open the door, negative values close it.
     */
    public static final int ID_DOOR_MOVE = 0x16400b01;
    /** door lock, bool type
     * 'true' indicates door is locked.
     */
    public static final int ID_DOOR_LOCK = 0x16200b02;

    /** Mirror properties are zoned by VehicleAreaMirror */
    /**
     * mirror z position, int type
     * Positive value indicates tilt upwards, negative value tilt downwards.
     */
    public static final int ID_MIRROR_Z_POS = 0x14400b40;
    /** mirror z move, int type
     * Positive value tilts the mirror upwards, negative value tilts downwards.
     */
    public static final int ID_MIRROR_Z_MOVE = 0x14400b41;
    /**
     * mirror y position, int type
     * Positive value indicates tilt right, negative value tilt left
     */
    public static final int ID_MIRROR_Y_POS = 0x14400b42;
    /** mirror y move, int type
     * Positive value tilts the mirror right, negative value tilts left.
     */
    public static final int ID_MIRROR_Y_MOVE = 0x14400b43;
    /**
     * mirror lock, bool type
     * True indicates mirror positions are locked and not changeable.
     */
    public static final int ID_MIRROR_LOCK = 0x11200b44;
    /**
     * mirror fold, bool type
     * True indicates mirrors are folded.
     */
    public static final int ID_MIRROR_FOLD = 0x11200b45;

    /** Seat properties are zoned by VehicleAreaSeat */
    /**
     * seat memory select, int type
     * This parameter selects the memory preset to use to select the seat position.
     * The minValue is always 1, and the maxValue determines the number of seat
     * positions available.
     *
     * For instance, if the driver's seat has 3 memory presets, the maxValue will be 3.
     * When the user wants to select a preset, the desired preset number (1, 2, or 3)
     * is set.
     */
    public static final int ID_SEAT_MEMORY_SELECT = 0x15400b80;
    /**
     * seat memory set, int type
     * This setting allows the user to save the current seat position settings into
     * the selected preset slot.  The maxValue for each seat position shall match
     * the maxValue for VEHICLE_PROPERTY_SEAT_MEMORY_SELECT.
     */
    public static final int ID_SEAT_MEMORY_SET = 0x15400b81;
    /**
     * seat belt buckled, bool type
     * True indicates belt is buckled.
     */
    public static final int ID_SEAT_BELT_BUCKLED = 0x15200b82;
    /**
     * seat belt height position, int type
     * Adjusts the shoulder belt anchor point.
     * Max value indicates highest position.
     * Min value indicates lowest position.
     */
    public static final int ID_SEAT_BELT_HEIGHT_POS = 0x15400b83;
    /** seat belt height move, int type
     * Adjusts the shoulder belt anchor point.
     * Positive value moves towards highest point.
     * Negative value moves towards lowest point.
     */
    public static final int ID_SEAT_BELT_HEIGHT_MOVE = 0x15400b84;
    /**
     * seat fore/aft position, int type
     * Sets the seat position forward (closer to steering wheel) and backwards.
     * Max value indicates closest to wheel, min value indicates most rearward position.
     */
    public static final int ID_SEAT_FORE_AFT_POS = 0x15400b85;
    /**
     * seat fore/aft move, int type
     * Positive value moves seat forward (closer to steering wheel).
     * Negative value moves seat rearward.
     */
    public static final int ID_SEAT_FORE_AFT_MOVE = 0x15400b86;
    /**
     * seat backrest angle #1 position, int type
     * Backrest angle 1 is the actuator closest to the bottom of the seat.
     * Max value indicates angling forward towards the steering wheel.
     * Min value indicates full recline.
     */
    public static final int ID_SEAT_BACKREST_ANGLE_1_POS = 0x15400b87;
    /** seat backrest angle #1 move, int type
     * Backrest angle 1 is the actuator closest to the bottom of the seat.
     * Positive value angles seat towards the steering wheel.
     * Negatie value angles away from steering wheel.
     */
    public static final int ID_SEAT_BACKREST_ANGLE_1_MOVE = 0x15400b88;
    /**
     * seat backrest angle #2 position, int type
     * Backrest angle 2 is the next actuator up from the bottom of the seat.
     * Max value indicates angling forward towards the steering wheel.
     * Min value indicates full recline.
     */
    public static final int ID_SEAT_BACKREST_ANGLE_2_POS = 0x15400b89;
    /** seat backrest angle #2 move, int type
     * Backrest angle 2 is the next actuator up from the bottom of the seat.
     * Positive value tilts forward towards the steering wheel.
     * Negative value tilts backwards.
     */
    public static final int ID_SEAT_BACKREST_ANGLE_2_MOVE = 0x15400b8a;
    /**
     * seat height position, int type
     * Sets the seat height.
     * Max value indicates highest position.
     * Min value indicates lowest position.
     */
    public static final int ID_SEAT_HEIGHT_POS = 0x15400b8b;
    /** seat height move, int type
     * Sets the seat height.
     * Positive value raises the seat.
     * Negative value lowers the seat.
     * */
    public static final int ID_SEAT_HEIGHT_MOVE = 0x15400b8c;
    /**
     * seat depth position, int type
     * Sets the seat depth, distance from back rest to front edge of seat.
     * Max value indicates longest depth position.
     * Min value indicates shortest position.
     */
    public static final int ID_SEAT_DEPTH_POS = 0x15400b8d;
    /** seat depth move, int type
     * Adjusts the seat depth, distance from back rest to front edge of seat.
     * Positive value increases the distance from back rest to front edge of seat.
     * Negative value decreases this distance.
     */
    public static final int ID_SEAT_DEPTH_MOVE = 0x15400b8e;
    /**
     * seat tilt position, int type
     * Sets the seat tilt.
     * Max value indicates front edge of seat higher than back edge.
     * Min value indicates front edge of seat lower than back edge.
     */
    public static final int ID_SEAT_TILT_POS = 0x15400b8f;
    /** seat tilt move, int type
     * Adjusts the seat tilt.
     * Positive value lifts front edge of seat higher than back edge.
     * Negative value lowers front edge of seat in relation to back edge.
     */
    public static final int ID_SEAT_TILT_MOVE = 0x15400b90;
    /**
     * seat lumbar fore/aft position, int type
     * Pushes the lumbar support forward and backwards.
     * Max value indicates most forward position.
     * Min value indicates most rearward position.
     */
    public static final int ID_SEAT_LUMBAR_FORE_AFT_POS = 0x15400b91;
    /** seat lumbar fore/aft move, int type
     * Adjusts the lumbar support forwards and backwards.
     * Positive value moves lumbar support forward.
     * Negative value moves lumbar support rearward.
     */
    public static final int ID_SEAT_LUMBAR_FORE_AFT_MOVE = 0x15400b92;
    /**
     * seat lumbar side support position, int type
     * Sets the amount of lateral lumbar support.
     * Max value indicates widest lumbar setting (i.e. least support)
     * Min value indicates thinnest lumbar setting.
     */
    public static final int ID_SEAT_LUMBAR_SIDE_SUPPORT_POS = 0x15400b93;
    /** seat lumbar side support move, int type
     * Adjusts the amount of lateral lumbar support.
     * Positive value widens the lumbar area.
     * Negative value makes the lumbar area thinner.
     */
    public static final int ID_SEAT_LUMBAR_SIDE_SUPPORT_MOVE = 0x15400b94;
    /**
     * seat headrest height position, int type
     * Sets the headrest height.
     * Max value indicates tallest setting.
     * Min value indicates shortest setting.
     */
    public static final int ID_SEAT_HEADREST_HEIGHT_POS = 0x15400b95;
    /** seat headrest height move, int type
     * Postive value moves the headrest higher.
     * Negative value moves the headrest lower.
     */
    public static final int ID_SEAT_HEADREST_HEIGHT_MOVE = 0x15400b96;
    /**
     * seat headrest angle position, int type
     * Sets the angle of the headrest.
     * Max value indicates most upright angle.
     * Min value indicates shallowest headrest angle.
     */
    public static final int ID_SEAT_HEADREST_ANGLE_POS = 0x15400b97;
    /** seat headrest angle move, int type
     * Adjusts the angle of the headrest.
     * Positive value angles headrest towards most upright angle.
     * Negative value angles headrest towards shallowest headrest angle.
     */
    public static final int ID_SEAT_HEADREST_ANGLE_MOVE = 0x15400b98;
    /**
     * seat headrest fore/aft position, int type
     * Sets the headrest forwards and backwards.
     * Max value indicates position closest to front of car.
     * Min value indicates position closest to rear of car.
     */
    public static final int ID_SEAT_HEADREST_FORE_AFT_POS = 0x15400b99;
    /** seat headrest fore/aft move, int type
     * Adjsuts the headrest forwards and backwards.
     * Positive value moves the headrest closer to front of car.
     * Negative value moves the headrest closer to rear of car.
     */
    public static final int ID_SEAT_HEADREST_FORE_AFT_MOVE = 0x15400b9a;

    /** Window properties are zoned by VehicleAreaWindow */
    /**
     * window position, int type
     * Max = window down / open.
     * Min = window up / closed.
     */
    public static final int ID_WINDOW_POS = 0x13400bc0;
    /** window move, int type
     * Positive value moves window down / opens window.
     * Negative value moves window up / closes window.
     */
    public static final int ID_WINDOW_MOVE = 0x13400bc1;
    /**
     * window lock, bool type
     * True indicates windows are locked and can't be moved.
     */
    public static final int ID_WINDOW_LOCK = 0x13400bc4;

    /** @hide */
    @IntDef({
            ID_DOOR_POS,
            ID_DOOR_MOVE,
            ID_DOOR_LOCK,
            ID_MIRROR_Z_POS,
            ID_MIRROR_Z_MOVE,
            ID_MIRROR_Y_POS,
            ID_MIRROR_Y_MOVE,
            ID_MIRROR_LOCK,
            ID_MIRROR_FOLD,
            ID_SEAT_MEMORY_SELECT,
            ID_SEAT_MEMORY_SET,
            ID_SEAT_BELT_BUCKLED,
            ID_SEAT_BELT_HEIGHT_POS,
            ID_SEAT_BELT_HEIGHT_MOVE,
            ID_SEAT_FORE_AFT_POS,
            ID_SEAT_FORE_AFT_MOVE,
            ID_SEAT_BACKREST_ANGLE_1_POS,
            ID_SEAT_BACKREST_ANGLE_1_MOVE,
            ID_SEAT_BACKREST_ANGLE_2_POS,
            ID_SEAT_BACKREST_ANGLE_2_MOVE,
            ID_SEAT_HEIGHT_POS,
            ID_SEAT_HEIGHT_MOVE,
            ID_SEAT_DEPTH_POS,
            ID_SEAT_DEPTH_MOVE,
            ID_SEAT_TILT_POS,
            ID_SEAT_TILT_MOVE,
            ID_SEAT_LUMBAR_FORE_AFT_POS,
            ID_SEAT_LUMBAR_FORE_AFT_MOVE,
            ID_SEAT_LUMBAR_SIDE_SUPPORT_POS,
            ID_SEAT_LUMBAR_SIDE_SUPPORT_MOVE,
            ID_SEAT_HEADREST_HEIGHT_POS,
            ID_SEAT_HEADREST_HEIGHT_MOVE,
            ID_SEAT_HEADREST_ANGLE_POS,
            ID_SEAT_HEADREST_ANGLE_MOVE,
            ID_SEAT_HEADREST_FORE_AFT_POS,
            ID_SEAT_HEADREST_FORE_AFT_MOVE,
            ID_WINDOW_POS,
            ID_WINDOW_MOVE,
            ID_WINDOW_LOCK
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface PropertyId {}
    private final ArraySet<Integer> mCabinPropertyIds = new ArraySet<>(Arrays.asList(new Integer[]{
            ID_DOOR_POS,
            ID_DOOR_MOVE,
            ID_DOOR_LOCK,
            ID_MIRROR_Z_POS,
            ID_MIRROR_Z_MOVE,
            ID_MIRROR_Y_POS,
            ID_MIRROR_Y_MOVE,
            ID_MIRROR_LOCK,
            ID_MIRROR_FOLD,
            ID_SEAT_MEMORY_SELECT,
            ID_SEAT_MEMORY_SET,
            ID_SEAT_BELT_BUCKLED,
            ID_SEAT_BELT_HEIGHT_POS,
            ID_SEAT_BELT_HEIGHT_MOVE,
            ID_SEAT_FORE_AFT_POS,
            ID_SEAT_FORE_AFT_MOVE,
            ID_SEAT_BACKREST_ANGLE_1_POS,
            ID_SEAT_BACKREST_ANGLE_1_MOVE,
            ID_SEAT_BACKREST_ANGLE_2_POS,
            ID_SEAT_BACKREST_ANGLE_2_MOVE,
            ID_SEAT_HEIGHT_POS,
            ID_SEAT_HEIGHT_MOVE,
            ID_SEAT_DEPTH_POS,
            ID_SEAT_DEPTH_MOVE,
            ID_SEAT_TILT_POS,
            ID_SEAT_TILT_MOVE,
            ID_SEAT_LUMBAR_FORE_AFT_POS,
            ID_SEAT_LUMBAR_FORE_AFT_MOVE,
            ID_SEAT_LUMBAR_SIDE_SUPPORT_POS,
            ID_SEAT_LUMBAR_SIDE_SUPPORT_MOVE,
            ID_SEAT_HEADREST_HEIGHT_POS,
            ID_SEAT_HEADREST_HEIGHT_MOVE,
            ID_SEAT_HEADREST_ANGLE_POS,
            ID_SEAT_HEADREST_ANGLE_MOVE,
            ID_SEAT_HEADREST_FORE_AFT_POS,
            ID_SEAT_HEADREST_FORE_AFT_MOVE,
            ID_WINDOW_POS,
            ID_WINDOW_MOVE,
            ID_WINDOW_LOCK
    }));

    /**
     * Application registers CarCabinEventCallback object to receive updates and changes to
     * subscribed Car Cabin properties.
     */
    public interface CarCabinEventCallback {
        /**
         * Called when a property is updated
         * @param value Property that has been updated.
         */
        void onChangeEvent(CarPropertyValue value);

        /**
         * Called when an error is detected with a property
         * @param propertyId
         * @param zone
         */
        void onErrorEvent(@PropertyId int propertyId, int zone);
    }

    private static class CarPropertyEventListenerToBase implements CarPropertyEventCallback {
        private final WeakReference<CarCabinManager> mManager;

        CarPropertyEventListenerToBase(CarCabinManager manager) {
            mManager = new WeakReference<>(manager);
        }

        @Override
        public void onChangeEvent(CarPropertyValue value) {
            CarCabinManager manager = mManager.get();
            if (manager != null) {
                manager.handleOnChangeEvent(value);
            }
        }

        @Override
        public void onErrorEvent(int propertyId, int zone) {
            CarCabinManager manager = mManager.get();
            if (manager != null) {
                manager.handleOnErrorEvent(propertyId, zone);
            }
        }
    }

    private void handleOnChangeEvent(CarPropertyValue value) {
        Collection<CarCabinEventCallback> callbacks;
        synchronized (mLock) {
            callbacks = new ArraySet<>(mCallbacks);
        }
        for (CarCabinEventCallback l: callbacks) {
            l.onChangeEvent(value);
        }
    }

    private void handleOnErrorEvent(int propertyId, int zone) {
        Collection<CarCabinEventCallback> listeners;
        synchronized (mLock) {
            listeners = new ArraySet<>(mCallbacks);
        }
        if (!listeners.isEmpty()) {
            for (CarCabinEventCallback l: listeners) {
                l.onErrorEvent(propertyId, zone);
            }
        }
    }

    /**
     * Get an instance of CarCabinManager
     *
     * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
     * @param service
     * @param context
     * @param handler
     * @hide
     */
    public CarCabinManager(Car car, IBinder service) {
        super(car);
        ICarProperty mCarPropertyService = ICarProperty.Stub.asInterface(service);
        mCarPropertyMgr = new CarPropertyManager(car, mCarPropertyService);
    }

    /**
     * All properties in CarCabinManager are zoned.
     * @param propertyId
     * @return true if property is a zoned type
     */
    public static boolean isZonedProperty(@PropertyId int propertyId) {
        return true;
    }

    /**
     * Implement wrappers for contained CarPropertyManagerBase object
     * @param callback
     */
    public void registerCallback(CarCabinEventCallback callback) {
        List<CarPropertyConfig> configs = getPropertyList();
        synchronized (mLock) {
            if (mListenerToBase == null) {
                mListenerToBase = new CarPropertyEventListenerToBase(this);
            }
            for (CarPropertyConfig c : configs) {
                // Register each individual propertyId
                mCarPropertyMgr.registerCallback(mListenerToBase, c.getPropertyId(), 0);
            }
            mCallbacks.add(callback);
        }
    }

    /**
     * Stop getting property updates for the given callback. If there are multiple registrations for
     * this listener, all listening will be stopped.
     * @param callback
     */
    public void unregisterCallback(CarCabinEventCallback callback) {
        synchronized (mLock) {
            mCallbacks.remove(callback);
            List<CarPropertyConfig> configs = getPropertyList();
            for (CarPropertyConfig c : configs) {
                    // Register each individual propertyId
                mCarPropertyMgr.unregisterCallback(mListenerToBase, c.getPropertyId());
            }
            if (mCallbacks.isEmpty()) {
                mListenerToBase = null;
            }
        }
    }

    /**
     * Get list of properties represented by CarCabinManager for this car.
     * @return List of CarPropertyConfig objects available via Car Cabin Manager.
     */
    public List<CarPropertyConfig> getPropertyList() {
        return mCarPropertyMgr.getPropertyList(mCabinPropertyIds);
    }

    /**
     * Get value of boolean property
     * @param propertyId
     * @param area
     * @return value of requested boolean property
     */
    public boolean getBooleanProperty(@PropertyId int propertyId, int area) {
        return mCarPropertyMgr.getBooleanProperty(propertyId, area);
    }

    /**
     * Get value of float property
     * @param propertyId
     * @param area
     * @return value of requested float property
     */
    public float getFloatProperty(@PropertyId int propertyId, int area) {
        return mCarPropertyMgr.getFloatProperty(propertyId, area);
    }

    /**
     * Get value of integer property
     * @param propertyId
     * @param area
     * @return value of requested integer property
     */
    public int getIntProperty(@PropertyId int propertyId, int area) {
        return mCarPropertyMgr.getIntProperty(propertyId, area);
    }

    /**
     * Set the value of a boolean property
     * @param propertyId
     * @param area
     * @param val
     */
    public void setBooleanProperty(@PropertyId int propertyId, int area, boolean val) {
        if (mCabinPropertyIds.contains(propertyId)) {
            mCarPropertyMgr.setBooleanProperty(propertyId, area, val);
        }
    }

    /**
     * Set the value of a float property
     * @param propertyId
     * @param area
     * @param val
     */
    public void setFloatProperty(@PropertyId int propertyId, int area, float val) {
        if (mCabinPropertyIds.contains(propertyId)) {
            mCarPropertyMgr.setFloatProperty(propertyId, area, val);
        }
    }

    /**
     * Set the value of an integer property
     * @param propertyId
     * @param area
     * @param val
     */
    public void setIntProperty(@PropertyId int propertyId, int area, int val) {
        if (mCabinPropertyIds.contains(propertyId)) {
            mCarPropertyMgr.setIntProperty(propertyId, area, val);
        }
    }

    /** @hide */
    @Override
    public void onCarDisconnected() {
        synchronized (mLock) {
            mCallbacks.clear();
        }
        mCarPropertyMgr.onCarDisconnected();
    }
}
