/*
 * Copyright (C) 2021 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.internal.telephony;

import android.os.HwBinder;
import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.UiccSlotMapping;

import com.android.telephony.Rlog;

import java.util.List;
import java.util.Set;

/**
 * RadioConfig proxy class that abstracts the underlying RadioConfig service implementation to
 * downstream users.
 */
public class RadioConfigProxy {
    private static final String TAG = "RadioConfigProxy";
    private HalVersion mRadioHalVersion;
    private final RadioConfigHidlServiceDeathRecipient mRadioConfigHidlServiceDeathRecipient;
    private final RadioConfigAidlServiceDeathRecipient mRadioConfigAidlServiceDeathRecipient;

    private volatile android.hardware.radio.config.V1_1.IRadioConfig mHidlRadioConfigProxy = null;
    private volatile android.hardware.radio.config.IRadioConfig mAidlRadioConfigProxy = null;

    private HalVersion mRadioConfigHalVersion = RIL.RADIO_HAL_VERSION_UNKNOWN;
    private boolean mIsAidl;

    public RadioConfigProxy(RadioConfig radioConfig, HalVersion radioHalVersion) {
        mRadioHalVersion = radioHalVersion;
        mRadioConfigAidlServiceDeathRecipient =
                new RadioConfigAidlServiceDeathRecipient(radioConfig);
        mRadioConfigHidlServiceDeathRecipient =
                new RadioConfigHidlServiceDeathRecipient(radioConfig);
    }

    /**
     * Set IRadioConfig as the HIDL implementation for RadioConfigProxy
     *
     * @param radioConfigHalVersion RadioConfig HAL version
     * @param radioConfig IRadioConfig implementation
     */
    public void setHidl(
            HalVersion radioConfigHalVersion,
            android.hardware.radio.config.V1_1.IRadioConfig radioConfig) {
        mRadioConfigHalVersion = radioConfigHalVersion;
        mHidlRadioConfigProxy = radioConfig;
        mIsAidl = false;
        mRadioConfigHidlServiceDeathRecipient.setService(radioConfig);
    }

    /**
     * Get HIDL IRadioConfig V1_1
     * @return IRadioConfigV1_1
     */
    public android.hardware.radio.config.V1_1.IRadioConfig getHidl11() {
        return mHidlRadioConfigProxy;
    }

    /**
     * Get HIDL IRadioConfig V1_3
     * @return IRadioConfigV1_3
     */
    public android.hardware.radio.config.V1_3.IRadioConfig getHidl13() {
        return (android.hardware.radio.config.V1_3.IRadioConfig) mHidlRadioConfigProxy;
    }

    /**
     * Set IRadioConfig as the AIDL implementation for RadioConfigProxy
     *
     * @param radioConfig IRadioConfig implementation
     */
    public void setAidl(android.hardware.radio.config.IRadioConfig radioConfig) {
        try {
            mRadioConfigHalVersion = RIL.getServiceHalVersion(radioConfig.getInterfaceVersion());
            Rlog.d(TAG, "setAidl: setting HAL version to version = " + mRadioConfigHalVersion);
        } catch (RemoteException e) {
            Rlog.e(TAG, "setAidl: " + e);
        }
        mAidlRadioConfigProxy = radioConfig;
        mIsAidl = true;
        mRadioConfigAidlServiceDeathRecipient.setService(radioConfig.asBinder());
    }

    /**
     * Get the AIDL implementation of RadioConfigProxy
     *
     * @return IRadio implementation
     */
    public android.hardware.radio.config.IRadioConfig getAidl() {
        return mAidlRadioConfigProxy;
    }

    /** Reset RadioConfigProxy */
    public void clear() {
        mRadioConfigHalVersion = RIL.RADIO_HAL_VERSION_UNKNOWN;
        mHidlRadioConfigProxy = null;
        mAidlRadioConfigProxy = null;
        mRadioConfigHidlServiceDeathRecipient.clear();
        mRadioConfigAidlServiceDeathRecipient.clear();
    }

    /**
     * Wrapper for service's linkToDeath()
     */
    public void linkToDeath(long cookie) throws RemoteException {
        if (isAidl()) {
            mRadioConfigAidlServiceDeathRecipient.linkToDeath((int) cookie);
        } else {
            mRadioConfigHidlServiceDeathRecipient.linkToDeath(cookie);
        }
    }

    /**
     * Check whether an implementation exists for this service
     *
     * @return false if there is neither a HIDL nor AIDL implementation
     */
    public boolean isEmpty() {
        return mAidlRadioConfigProxy == null && mHidlRadioConfigProxy == null;
    }

    /**
     * Whether RadioConfigProxy is an AIDL or HIDL implementation
     *
     * @return true if AIDL, false if HIDL
     */
    public boolean isAidl() {
        return mIsAidl;
    }

    /**
     * Return RadioConfig HAL version used by this instance
     * @return RadioConfig HAL Version
     */
    public HalVersion getVersion() {
        return mRadioConfigHalVersion;
    }

    /**
     * Set the response functions for RadioConfig instance
     * @param radioConfig main RadioConfig instance
     * @throws RemoteException
     */
    public void setResponseFunctions(RadioConfig radioConfig) throws RemoteException {
        if (isEmpty()) return;

        if (isAidl()) {
            mAidlRadioConfigProxy.setResponseFunctions(
                    new RadioConfigResponseAidl(radioConfig, mRadioHalVersion),
                    new RadioConfigIndicationAidl(radioConfig));
        } else {
            mHidlRadioConfigProxy.setResponseFunctions(
                    new RadioConfigResponseHidl(radioConfig, mRadioHalVersion),
                    new RadioConfigIndicationHidl(radioConfig));
        }
    }

    /**
     * Get capabilities based off of the radio hal version and feature set configurations
     * @return Set string capabilities
     */
    public Set<String> getFullCapabilitySet() {
        return RILUtils.getCaps(mRadioHalVersion, false);
    }

    /**
     * Wrapper function for IRadioConfig.getSimSlotsStatus().
     */
    public void getSimSlotStatus(int serial) throws RemoteException {
        if (isAidl()) {
            getAidl().getSimSlotsStatus(serial);
        } else {
            getHidl11().getSimSlotsStatus(serial);
        }
    }

    /**
     * Wrapper function for IRadioConfig.setPreferredDataModem(int modemId).
     */
    public void setPreferredDataModem(int serial, int modemId) throws RemoteException {
        if (isAidl()) {
            getAidl().setPreferredDataModem(serial, (byte) modemId);
        } else {
            getHidl11().setPreferredDataModem(serial, (byte) modemId);
        }
    }

    /**
     * Wrapper function for IRadioConfig.getPhoneCapability().
     */
    public void getPhoneCapability(int serial) throws RemoteException {
        if (isAidl()) {
            getAidl().getPhoneCapability(serial);
        } else {
            getHidl11().getPhoneCapability(serial);
        }
    }

    /**
     * Wrapper function for IRadioConfig.setSimSlotsMapping(int32_t serial,
     * vec<SlotPortMapping> portMap).
     */
    public void setSimSlotsMapping(int serial, List<UiccSlotMapping> slotMapping)
            throws RemoteException {
        if (isAidl()) {
            getAidl().setSimSlotsMapping(serial, RILUtils.convertSimSlotsMapping(slotMapping));
        } else {
            getHidl11().setSimSlotsMapping(serial,
                    RILUtils.convertSlotMappingToList(slotMapping));
        }
    }

    /**
     * Wrapper function for IRadioConfig.getSimultaneousCallingSupport()
     */
    public void updateSimultaneousCallingSupport(int serial) throws RemoteException {
        if (isAidl()) {
            getAidl().getSimultaneousCallingSupport(serial);
        }
        // Only supported on AIDL.
    }

    /**
     * Wrapper function for using IRadioConfig.setNumOfLiveModems(int32_t serial,
     * byte numOfLiveModems) to switch between single-sim and multi-sim.
     */
    public void setNumOfLiveModems(int serial, int numOfLiveModems) throws RemoteException {
        if (isAidl()) {
            getAidl().setNumOfLiveModems(serial, (byte) numOfLiveModems);
        } else {
            android.hardware.radio.config.V1_1.ModemsConfig modemsConfig =
                    new android.hardware.radio.config.V1_1.ModemsConfig();
            modemsConfig.numOfLiveModems = (byte) numOfLiveModems;
            getHidl11().setModemsConfig(serial, modemsConfig);
        }
    }

    /**
     * Gets the hal capabilities from the device.
     */
    public void getHalDeviceCapabilities(int serial) throws RemoteException {
        if (isAidl()) {
            getAidl().getHalDeviceCapabilities(serial);
        } else {
            getHidl13().getHalDeviceCapabilities(serial);
        }
    }

    /**
     * Death Recipient for HIDL binder (if any) of RadioConfig.
     */
    private static class RadioConfigHidlServiceDeathRecipient implements HwBinder.DeathRecipient {
        private static final String TAG = "RadioConfigHidlSDR";

        private final RadioConfig mRadioConfig;
        private android.hardware.radio.config.V1_1.IRadioConfig mService;

        RadioConfigHidlServiceDeathRecipient(RadioConfig radioConfig) {
            mRadioConfig = radioConfig;
        }

        public void setService(android.hardware.radio.config.V1_1.IRadioConfig service) {
            mService = service;
        }

        public void linkToDeath(long cookie) throws RemoteException {
            mService.linkToDeath(this, cookie);
        }

        public void clear() {
            mService = null;
        }

        @Override
        public void serviceDied(long cookie) {
            // Deal with service going away
            Rlog.e(TAG, "serviceDied");
            mRadioConfig.sendMessage(
                    mRadioConfig.obtainMessage(RadioConfig.EVENT_HIDL_SERVICE_DEAD, cookie));
        }
    }

    /**
     * DeathRecipient for AIDL binder service (if any) of RadioConfig
     */
    private static class RadioConfigAidlServiceDeathRecipient implements IBinder.DeathRecipient {
        private static final String TAG = "RadioConfigAidlSDR";

        private final RadioConfig mRadioConfig;

        private IBinder mService;

        RadioConfigAidlServiceDeathRecipient(RadioConfig radioConfig) {
            mRadioConfig = radioConfig;
        }

        public void setService(IBinder service) {
            mService = service;
        }

        public void linkToDeath(int cookie) throws RemoteException {
            mService.linkToDeath(this, cookie);
        }

        public void clear() {
            mService = null;
        }

        /**
         * Unlink from RadioConfig if any.
         */
        public synchronized void unlinkToDeath() {
            if (mService != null) {
                mService.unlinkToDeath(this, 0);
                mService = null;
            }
        }

        @Override
        public void binderDied() {
            Rlog.e(TAG, "service died.");
            unlinkToDeath();
            mRadioConfig.sendMessage(
                    mRadioConfig.obtainMessage(RadioConfig.EVENT_AIDL_SERVICE_DEAD));
        }
    }

    @Override
    public String toString() {
        return "RadioConfigProxy["
                + "mRadioHalVersion=" + mRadioHalVersion
                + ", mRadioConfigHalVersion=" + mRadioConfigHalVersion + ']';
    }
}
