/*
 * 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.annotation.NonNull;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.ArraySet;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.util.WorkerThread;
import com.android.telephony.Rlog;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Set;

/**
 * Provides the capabilities that the Radio Interface supports on the current device.
 */
public class RadioInterfaceCapabilityController extends Handler {
    private static final String LOG_TAG =
            RadioInterfaceCapabilityController.class.getSimpleName();

    private static RadioInterfaceCapabilityController sInstance;
    private final RadioConfig mRadioConfig;
    private final CommandsInterface mCommandsInterface;
    private Set<String> mRadioInterfaceCapabilities;
    private final Object mLockRadioInterfaceCapabilities = new Object();
    private static final int EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE = 100;

    /**
     * Init method to instantiate the object
     * Should only be called once.
     */
    public static synchronized RadioInterfaceCapabilityController init(
            final RadioConfig radioConfig,
            final CommandsInterface commandsInterface) {
        if (sInstance == null) {
            sInstance = new RadioInterfaceCapabilityController(
                    radioConfig,
                    commandsInterface,
                    WorkerThread.get().getLooper());
        } else {
            Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
        }
        return sInstance;
    }

    /**
     * Static method to get instance.
     */
    public static RadioInterfaceCapabilityController getInstance() {
        if (sInstance == null) {
            Log.wtf(LOG_TAG, "getInstance null");
        }

        return sInstance;
    }

    @VisibleForTesting
    public RadioInterfaceCapabilityController(final RadioConfig radioConfig,
            final CommandsInterface commandsInterface, final Looper looper) {
        super(looper);
        mRadioConfig = radioConfig;
        mCommandsInterface = commandsInterface;
        register();
    }

    private void requestCapabilities() {
        if (mRadioInterfaceCapabilities != null) return;

        mRadioConfig.getHalDeviceCapabilities(obtainMessage(
                EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE));
    }

    /**
     * Gets the radio interface capabilities for the device
     */
    @NonNull
    public Set<String> getCapabilities() {
        if (mRadioInterfaceCapabilities == null) {
            // Only incur cost of synchronization block if mRadioInterfaceCapabilities isn't null
            synchronized (mLockRadioInterfaceCapabilities) {
                if (mRadioInterfaceCapabilities == null) {
                    requestCapabilities();
                    try {
                        if (Looper.myLooper() != getLooper()) {
                            mLockRadioInterfaceCapabilities.wait(2000);
                        }
                    } catch (final InterruptedException ignored) {
                    }

                    if (mRadioInterfaceCapabilities == null) {
                        loge("getRadioInterfaceCapabilities: Radio Capabilities not "
                                + "loaded in time");
                        return new ArraySet<>();
                    }
                }
            }
        }
        return mRadioInterfaceCapabilities;
    }

    private void setupCapabilities(final @NonNull AsyncResult ar) {
        if (mRadioInterfaceCapabilities == null) {
            synchronized (mLockRadioInterfaceCapabilities) {
                if (mRadioInterfaceCapabilities == null) {
                    if (ar.exception != null) {
                        loge("setupRadioInterfaceCapabilities: " + ar.exception);
                    }
                    if (ar.result == null) {
                        loge("setupRadioInterfaceCapabilities: ar.result is null");
                        return;
                    }
                    log("setupRadioInterfaceCapabilities: "
                            + "mRadioInterfaceCapabilities now setup");
                    mRadioInterfaceCapabilities =
                            Collections.unmodifiableSet((Set<String>) ar.result);
                    if (mRadioInterfaceCapabilities != null) {
                        unregister();
                    }
                }
                mLockRadioInterfaceCapabilities.notify();
            }
        }
    }

    private void register() {
        // There is no radio HAL, capabilities are irrelevant in this case.
        if (mCommandsInterface == null) {
            mRadioInterfaceCapabilities = Collections.unmodifiableSet(new ArraySet<>());
            return;
        }

        mCommandsInterface.registerForAvailable(this, Phone.EVENT_RADIO_AVAILABLE, null);
    }

    private void unregister() {
        mCommandsInterface.unregisterForAvailable(this);
    }

    @Override
    public void handleMessage(final Message msg) {
        switch (msg.what) {
            case Phone.EVENT_RADIO_AVAILABLE:
            case Phone.EVENT_RADIO_ON:
                requestCapabilities();
                break;
            case EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE:
                setupCapabilities((AsyncResult) msg.obj);
                break;
        }
    }

    /**
     * Dump the fields of the instance
     */
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("mRadioConfig=" + mRadioConfig);
    }

    private static void log(final String s) {
        Rlog.d(LOG_TAG, s);
    }

    private static void loge(final String s) {
        Rlog.e(LOG_TAG, s);
    }
}
