/*
 * 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.internal.compat;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * This class contains all the possible override allowed states.
 */
public final class OverrideAllowedState implements Parcelable {
    @IntDef({
            ALLOWED,
            DISABLED_NOT_DEBUGGABLE,
            DISABLED_NON_TARGET_SDK,
            DISABLED_TARGET_SDK_TOO_HIGH,
            DEFERRED_VERIFICATION,
            LOGGING_ONLY_CHANGE,
            PLATFORM_TOO_OLD
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface State {
    }

    /**
     * Change can be overridden.
     */
    public static final int ALLOWED = 0;
    /**
     * Change cannot be overridden, due to the app not being debuggable.
     */
    public static final int DISABLED_NOT_DEBUGGABLE = 1;
    /**
     * Change cannot be overridden, due to the build being non-debuggable and the change being
     * enabled regardless of targetSdk.
     */
    public static final int DISABLED_NON_TARGET_SDK = 2;
    /**
     * Change cannot be overridden, due to the app's targetSdk being above the change's targetSdk.
     */
    public static final int DISABLED_TARGET_SDK_TOO_HIGH = 3;
     /**
     * Change override decision is currently being deferred, due to the app not being installed yet.
     */
    public static final int DEFERRED_VERIFICATION = 4;
    /**
     * Change is marked as logging only, and cannot be toggled.
     */
    public static final int LOGGING_ONLY_CHANGE = 5;
    /**
     * Change is gated by a target sdk version newer than the current platform sdk version.
     */
    public static final int PLATFORM_TOO_OLD = 6;

    @State
    public final int state;
    public final int appTargetSdk;
    public final int changeIdTargetSdk;

    private OverrideAllowedState(Parcel parcel) {
        state = parcel.readInt();
        appTargetSdk = parcel.readInt();
        changeIdTargetSdk = parcel.readInt();
    }

    public OverrideAllowedState(@State int state, int appTargetSdk, int changeIdTargetSdk) {
        this.state = state;
        this.appTargetSdk = appTargetSdk;
        this.changeIdTargetSdk = changeIdTargetSdk;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(state);
        out.writeInt(appTargetSdk);
        out.writeInt(changeIdTargetSdk);
    }

    /**
     * Enforces the policy for overriding compat changes.
     *
     * @param changeId    the change id that was attempted to be overridden.
     * @param packageName the package for which the attempt was made.
     * @throws SecurityException if the policy forbids this operation.
     */
    public void enforce(long changeId, String packageName)
            throws SecurityException {
        switch (state) {
            case ALLOWED:
            case DEFERRED_VERIFICATION:
                return;
            case DISABLED_NOT_DEBUGGABLE:
                throw new SecurityException(
                        "Cannot override a change on a non-debuggable app and user build.");
            case DISABLED_NON_TARGET_SDK:
                throw new SecurityException(
                        "Cannot override a default enabled/disabled change on a user build.");
            case DISABLED_TARGET_SDK_TOO_HIGH:
                throw new SecurityException(String.format(
                        "Cannot override %1$d for %2$s because the app's targetSdk (%3$d) is "
                                + "above the change's targetSdk threshold (%4$d)",
                        changeId, packageName, appTargetSdk, changeIdTargetSdk));
            case LOGGING_ONLY_CHANGE:
                throw new SecurityException(String.format(
                        "Cannot override %1$d because it is marked as a logging-only change.",
                        changeId));
            case PLATFORM_TOO_OLD:
                throw new SecurityException(String.format(
                        "Cannot override %1$d for %2$s because the change's targetSdk threshold "
                                + "(%3$d) is above the platform sdk.",
                        changeId, packageName, changeIdTargetSdk));
        }
    }

    public static final @NonNull
            Parcelable.Creator<OverrideAllowedState> CREATOR =
                new Parcelable.Creator<OverrideAllowedState>() {
                public OverrideAllowedState createFromParcel(Parcel parcel) {
                    OverrideAllowedState info = new OverrideAllowedState(parcel);
                    return info;
                }

                public OverrideAllowedState[] newArray(int size) {
                    return new OverrideAllowedState[size];
                }
            };

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof OverrideAllowedState)) {
            return false;
        }
        OverrideAllowedState otherState = (OverrideAllowedState) obj;
        return state == otherState.state
                && appTargetSdk == otherState.appTargetSdk
                && changeIdTargetSdk == otherState.changeIdTargetSdk;
    }

    private String stateName() {
        switch (state) {
            case ALLOWED:
                return "ALLOWED";
            case DISABLED_NOT_DEBUGGABLE:
                return "DISABLED_NOT_DEBUGGABLE";
            case DISABLED_NON_TARGET_SDK:
                return "DISABLED_NON_TARGET_SDK";
            case DISABLED_TARGET_SDK_TOO_HIGH:
                return "DISABLED_TARGET_SDK_TOO_HIGH";
            case DEFERRED_VERIFICATION:
                return "DEFERRED_VERIFICATION";
            case LOGGING_ONLY_CHANGE:
                return "LOGGING_ONLY_CHANGE";
            case PLATFORM_TOO_OLD:
                return "PLATFORM_TOO_OLD";
        }
        return "UNKNOWN";
    }

    @Override
    public String toString() {
        return "OverrideAllowedState(state=" + stateName() + "; appTargetSdk=" + appTargetSdk
                + "; changeIdTargetSdk=" + changeIdTargetSdk + ")";
    }
}
