/*
 * Copyright (C) 2019 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.car.settings.datausage;

import android.car.drivingstate.CarUxRestrictions;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.INetworkPolicyManager;
import android.os.ServiceManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionPlan;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.text.style.AbsoluteSizeSpan;
import android.util.RecurrenceRule;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.car.settings.R;
import com.android.car.settings.common.FragmentController;
import com.android.car.settings.common.Logger;
import com.android.car.settings.network.NetworkBasePreferenceController;
import com.android.car.settings.network.NetworkUtils;
import com.android.settingslib.net.DataUsageController;
import com.android.settingslib.utils.StringUtil;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Business logic for setting the {@link DataUsageSummaryPreference} with the current data usage and
 * the appropriate summary text.
 */
public class DataUsageSummaryPreferenceController extends
        NetworkBasePreferenceController<DataUsageSummaryPreference> {
    private static final Logger LOG = new Logger(DataUsageSummaryPreferenceController.class);

    private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1);
    private static final long MILLIS_IN_AN_HOUR = TimeUnit.HOURS.toMillis(1);
    private static final long MILLIS_IN_A_MINUTE = TimeUnit.MINUTES.toMillis(1);
    private static final long MILLIS_IN_A_SECOND = TimeUnit.SECONDS.toMillis(1);
    private static final int MAX_PROGRESS_BAR_VALUE = 1000;

    @VisibleForTesting
    static final long WARNING_AGE = TimeUnit.HOURS.toMillis(6L);

    private final SubscriptionManager mSubscriptionManager;
    private final DataUsageController mDataUsageController;

    /** Name of the carrier, or null if not available */
    @Nullable
    private CharSequence mCarrierName;
    /** The number of registered plans, [0,N] */
    private int mDataplanCount;
    /** The time of the last update in milliseconds since the epoch, or -1 if unknown */
    private long mSnapshotTime;
    /** The size of the first registered plan if one exists. -1 if no information is available. */
    private long mDataplanSize = -1;
    /**
     * Limit to track. Size of the first registered plan if one exists. Otherwise size of data limit
     * or warning.
     */
    private long mDataplanTrackingThreshold;
    /** The number of bytes used since the start of the cycle. */
    private long mDataplanUse;
    /** The ending time of the billing cycle in ms since the epoch */
    private long mCycleEnd;
    private Intent mManageSubscriptionIntent;

    public DataUsageSummaryPreferenceController(Context context, String preferenceKey,
            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
        this(context, preferenceKey, fragmentController, uxRestrictions,
                context.getSystemService(SubscriptionManager.class),
                new DataUsageController(context));
    }

    @VisibleForTesting
    DataUsageSummaryPreferenceController(Context context, String preferenceKey,
            FragmentController fragmentController, CarUxRestrictions uxRestrictions,
            SubscriptionManager subscriptionManager, DataUsageController dataUsageController) {
        super(context, preferenceKey, fragmentController, uxRestrictions);
        mSubscriptionManager = subscriptionManager;
        mDataUsageController = dataUsageController;
    }

    @Override
    protected Class<DataUsageSummaryPreference> getPreferenceType() {
        return DataUsageSummaryPreference.class;
    }

    @Override
    protected int getDefaultAvailabilityStatus() {
        return NetworkUtils.hasSim(getTelephonyManager()) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
    }

    @Override
    protected void onCreateInternal() {
        getPreference().setMin(0);
        getPreference().setMax(MAX_PROGRESS_BAR_VALUE);
    }

    @Override
    protected void updateState(DataUsageSummaryPreference preference) {
        DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo(
                getNetworkTemplate());

        if (mSubscriptionManager != null) {
            refreshDataplanInfo(info);
        }

        preference.setTitle(getUsageText());
        preference.setManageSubscriptionIntent(mManageSubscriptionIntent);

        // Carrier Info has special styling based on when it was last updated.
        preference.setCarrierInfoText(getCarrierInfoText());
        long updateAgeMillis = calculateTruncatedUpdateAge(mSnapshotTime);
        if (updateAgeMillis <= WARNING_AGE) {
            preference.setCarrierInfoTextStyle(R.style.DataUsageSummaryCarrierInfoTextAppearance);
        } else {
            preference.setCarrierInfoTextStyle(
                    R.style.DataUsageSummaryCarrierInfoWarningTextAppearance);
        }

        // Set the progress bar values.
        preference.setMinLabel(DataUsageUtils.bytesToIecUnits(getContext(), /* byteValue= */ 0));
        preference.setMaxLabel(
                DataUsageUtils.bytesToIecUnits(getContext(), mDataplanTrackingThreshold));
        preference.setProgress(scaleUsage(mDataplanUse, mDataplanTrackingThreshold));
    }

    private CharSequence getUsageText() {
        Formatter.BytesResult usedResult = Formatter.formatBytes(getContext().getResources(),
                mDataplanUse, Formatter.FLAG_CALCULATE_ROUNDED | Formatter.FLAG_IEC_UNITS);
        SpannableString usageNumberText = new SpannableString(usedResult.value);
        int textSize = getContext().getResources().getDimensionPixelSize(
                R.dimen.usage_number_text_size);

        // Set the usage text (only the number) to the size defined by usage_number_text_size.
        usageNumberText.setSpan(new AbsoluteSizeSpan(textSize), /* start= */ 0,
                usageNumberText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

        CharSequence template = getContext().getText(R.string.data_used_formatted);
        CharSequence usageText = TextUtils.expandTemplate(template, usageNumberText,
                usedResult.units);
        return usageText;
    }

    private CharSequence getCarrierInfoText() {
        if (mDataplanCount > 0 && mSnapshotTime >= 0L) {
            long updateAgeMillis = calculateTruncatedUpdateAge(mSnapshotTime);

            int textResourceId;
            CharSequence updateTime = null;
            if (updateAgeMillis == 0) {
                if (mCarrierName != null) {
                    textResourceId = R.string.carrier_and_update_now_text;
                } else {
                    textResourceId = R.string.no_carrier_update_now_text;
                }
            } else {
                if (mCarrierName != null) {
                    textResourceId = R.string.carrier_and_update_text;
                } else {
                    textResourceId = R.string.no_carrier_update_text;
                }
                updateTime = StringUtil.formatElapsedTime(getContext(),
                        updateAgeMillis, /* withSeconds= */ false, /* collapseTimeUnit= */ false);
            }
            return TextUtils.expandTemplate(getContext().getText(textResourceId), mCarrierName,
                    updateTime);
        }

        return null;
    }

    private void refreshDataplanInfo(DataUsageController.DataUsageInfo info) {
        // Reset data before overwriting.
        mCarrierName = null;
        mDataplanCount = 0;
        mSnapshotTime = -1L;
        mDataplanSize = -1L;
        mDataplanTrackingThreshold = getSummaryLimit(info);
        mDataplanUse = info.usageLevel;
        mCycleEnd = info.cycleEnd;

        SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(getSubId());
        if (subInfo != null) {
            mCarrierName = subInfo.getCarrierName();
            List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans(getSubId());
            SubscriptionPlan primaryPlan = DataUsageUtils.getPrimaryPlan(mSubscriptionManager,
                    getSubId());
            if (primaryPlan != null) {
                mDataplanCount = plans.size();
                mDataplanSize = primaryPlan.getDataLimitBytes();
                if (mDataplanSize == SubscriptionPlan.BYTES_UNLIMITED) {
                    mDataplanSize = -1L;
                }
                mDataplanTrackingThreshold = mDataplanSize;
                mDataplanUse = primaryPlan.getDataUsageBytes();

                RecurrenceRule rule = primaryPlan.getCycleRule();
                if (rule != null && rule.start != null && rule.end != null) {
                    mCycleEnd = rule.end.toEpochSecond() * MILLIS_IN_A_SECOND;
                }
                mSnapshotTime = primaryPlan.getDataUsageTime();
            }
        }
        mManageSubscriptionIntent = createManageSubscriptionIntent(getSubId());
    }

    /**
     * Create an {@link Intent} that can be launched towards the carrier app
     * that is currently defining the billing relationship plan through
     * {@link INetworkPolicyManager#setSubscriptionPlans(int, SubscriptionPlan [], String)}.
     *
     * @return ready to launch Intent targeted towards the carrier app, or
     *         {@code null} if no carrier app is defined, or if the defined
     *         carrier app provides no management activity.
     */
    private Intent createManageSubscriptionIntent(int subId) {
        INetworkPolicyManager networkPolicyManager = INetworkPolicyManager.Stub.asInterface(
                ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
        String owner = "";
        try {
            owner = networkPolicyManager.getSubscriptionPlansOwner(subId);
        } catch (Exception ex) {
            LOG.w("Fail to get subscription plan owner for subId " + subId, ex);
        }

        if (TextUtils.isEmpty(owner)) {
            return null;
        }

        List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans(subId);
        if (plans.isEmpty()) {
            return null;
        }

        Intent intent = new Intent(SubscriptionManager.ACTION_MANAGE_SUBSCRIPTION_PLANS);
        intent.setPackage(owner);
        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);

        if (getContext().getPackageManager().queryIntentActivities(intent,
                PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
            return null;
        }

        return intent;
    }

    /** Scales the current usage to be an integer between 0 and {@link #MAX_PROGRESS_BAR_VALUE}. */
    private int scaleUsage(long usage, long maxUsage) {
        if (maxUsage == 0) {
            return 0;
        }
        return (int) ((usage / (float) maxUsage) * MAX_PROGRESS_BAR_VALUE);
    }

    /**
     * Gets the max displayed limit based on {@link DataUsageController.DataUsageInfo}.
     *
     * @return the most appropriate limit for the data usage summary. Use the total usage when it
     * is higher than the limit and warning level. Use the limit when it is set and less than usage.
     * Otherwise use warning level.
     */
    private static long getSummaryLimit(DataUsageController.DataUsageInfo info) {
        long limit = info.limitLevel;
        if (limit <= 0) {
            limit = info.warningLevel;
        }
        if (info.usageLevel > limit) {
            limit = info.usageLevel;
        }
        return limit;
    }

    /**
     * Returns the time since the last carrier update, as defined by {@link #mSnapshotTime},
     * truncated to the nearest day / hour / minute in milliseconds, or 0 if less than 1 min.
     */
    private long calculateTruncatedUpdateAge(long snapshotTime) {
        long updateAgeMillis = System.currentTimeMillis() - snapshotTime;

        // Round to nearest whole unit
        if (updateAgeMillis >= MILLIS_IN_A_DAY) {
            return (updateAgeMillis / MILLIS_IN_A_DAY) * MILLIS_IN_A_DAY;
        } else if (updateAgeMillis >= MILLIS_IN_AN_HOUR) {
            return (updateAgeMillis / MILLIS_IN_AN_HOUR) * MILLIS_IN_AN_HOUR;
        } else if (updateAgeMillis >= MILLIS_IN_A_MINUTE) {
            return (updateAgeMillis / MILLIS_IN_A_MINUTE) * MILLIS_IN_A_MINUTE;
        } else {
            return 0;
        }
    }
}
