/*
 * Copyright (C) 2006, 2012 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.uicc;

import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;

import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.uicc.IccCardStatus.CardState;
import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode;
import com.android.internal.telephony.uicc.euicc.EuiccCard;
import com.android.internal.telephony.uicc.euicc.EuiccPort;
import com.android.internal.telephony.util.TelephonyUtils;
import com.android.telephony.Rlog;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.LinkedHashMap;

/**
 * {@hide}
 */
public class UiccCard {
    protected static final String LOG_TAG = "UiccCard";
    protected static final boolean DBG = true;

    public static final String EXTRA_ICC_CARD_ADDED =
            "com.android.internal.telephony.uicc.ICC_CARD_ADDED";

    // The lock object is created by UiccSlot that owns this UiccCard - this is to share the lock
    // between UiccSlot, UiccCard, EuiccCard, UiccPort, EuiccPort and UiccProfile for now.
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected final Object mLock;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private CardState mCardState;
    protected String mCardId;
    protected MultipleEnabledProfilesMode mSupportedMepMode;

    protected LinkedHashMap<Integer, UiccPort> mUiccPorts = new LinkedHashMap<>();
    private HashMap<Integer, Integer> mPhoneIdToPortIdx = new HashMap<>();

    public UiccCard(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId, Object lock,
            MultipleEnabledProfilesMode supportedMepMode) {
        if (DBG) log("Creating");
        mCardState = ics.mCardState;
        mLock = lock;
        mSupportedMepMode = supportedMepMode;
        update(c, ci, ics, phoneId);
    }

    /**
     * Dispose the card and its related UiccPort objects.
     */
    public void dispose() {
        synchronized (mLock) {
            if (DBG) log("Disposing card");
            for (UiccPort uiccPort : mUiccPorts.values()) {
                if (uiccPort != null) {
                    uiccPort.dispose();
                }
            }
            mUiccPorts.clear();
            mUiccPorts = null;
            mPhoneIdToPortIdx.clear();
            mPhoneIdToPortIdx = null;
        }
    }

    /**
     * Dispose the port corresponding to the port index.
     */
    public void disposePort(int portIndex) {
        synchronized (mLock) {
            if (DBG) log("Disposing port for index " + portIndex);
            UiccPort port = getUiccPort(portIndex);
            if (port != null) {
                mPhoneIdToPortIdx.remove(port.getPhoneId());
                port.dispose();
            }
            mUiccPorts.remove(portIndex);
        }
    }

    /**
     * Update card. The main trigger for this is a change in the ICC Card status.
     */
    public void update(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId) {
        synchronized (mLock) {
            mCardState = ics.mCardState;
            updateCardId(ics.iccid);
            if (mCardState != CardState.CARDSTATE_ABSENT) {
                int portIdx = ics.mSlotPortMapping.mPortIndex;
                UiccPort port = mUiccPorts.get(portIdx);
                if (port == null) {
                    if (this instanceof EuiccCard) {
                        port = new EuiccPort(c, ci, ics, phoneId, mLock, this,
                                mSupportedMepMode); // eSim
                    } else {
                        port = new UiccPort(c, ci, ics, phoneId, mLock, this); // pSim
                    }
                    mUiccPorts.put(portIdx, port);
                } else {
                    port.update(c, ci, ics, this);
                }
                mPhoneIdToPortIdx.put(phoneId, portIdx);
            } else {
                throw new RuntimeException("Card state is absent when updating!");
            }
        }
    }

    @Override
    protected void finalize() {
        if (DBG) log("UiccCard finalized");
    }

    /**
     * Updates the ID of the SIM card.
     *
     * <p>Whenever the {@link UiccCard#update(Context, CommandsInterface, IccCardStatus, int)}
     * is called, this function needs to be called to update the card ID. Subclasses of
     * {@link UiccCard} could override this function to set the {@link UiccCard#mCardId} to be
     * something else instead of setting iccId.</p>
     */
    protected void updateCardId(String iccId) {
        mCardId = iccId;
    }


    /**
     * Updates MEP(Multiple Enabled Profile) supported mode flag.
     *
     * <p>If IccSlotStatus comes later, the number of ports reported is only known after the
     * UiccCard creation which will impact UICC MEP capability.
     */
    public void updateSupportedMepMode(MultipleEnabledProfilesMode supportedMepMode) {
        mSupportedMepMode = supportedMepMode;
    }

    @UnsupportedAppUsage
    public CardState getCardState() {
        synchronized (mLock) {
            return mCardState;
        }
    }

    /**
     * Returns the ID of this SIM card, it is the ICCID of the active profile on the card for a UICC
     * card or the EID of the card for an eUICC card.
     */
    public String getCardId() {
        if (!TextUtils.isEmpty(mCardId)) {
            return mCardId;
        } else {
            UiccPort uiccPort = mUiccPorts.get(TelephonyManager.DEFAULT_PORT_INDEX);
            if (uiccPort == null) {
                return null;
            }
            UiccProfile uiccProfile = uiccPort.getUiccProfile();
            return uiccProfile == null ? null : uiccProfile.getIccId();
        }
    }

    /**
     * Returns all the UiccPorts associated with the card.
     */
    public UiccPort[] getUiccPortList() {
        synchronized (mLock) {
            return mUiccPorts.values().stream().toArray(UiccPort[]::new);
        }
    }

    /**
     * Returns the UiccPort associated with the given phoneId
     */
    public UiccPort getUiccPortForPhone(int phoneId) {
        synchronized (mLock) {
            return mUiccPorts.get(mPhoneIdToPortIdx.get(phoneId));
        }
    }

    /**
     * Returns the UiccPort associated with the given port index.
     */
    public UiccPort getUiccPort(int portIdx) {
        synchronized (mLock) {
            return mUiccPorts.get(portIdx);
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private void log(String msg) {
        Rlog.d(LOG_TAG, msg);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private void loge(String msg) {
        Rlog.e(LOG_TAG, msg);
    }

    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
        pw.println("UiccCard:");
        pw.increaseIndent();
        pw.println("mCardState=" + mCardState);
        pw.println("mCardId=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mCardId));
        pw.println("mNumberOfPorts=" + mUiccPorts.size());
        pw.println("mSupportedMepMode=" + mSupportedMepMode);
        pw.println("mUiccPorts= size=" + mUiccPorts.size());
        pw.increaseIndent();
        for (UiccPort uiccPort : mUiccPorts.values()) {
            uiccPort.dump(fd, pw, args);
        }
        pw.decreaseIndent();
        pw.decreaseIndent();
    }
}
