/*
 * Copyright (C) 2014 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.systemui.statusbar.phone;

import android.content.Context;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.MathUtils;
import android.util.SparseBooleanArray;

import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.systemui.R;

import java.io.PrintWriter;

public class DozeParameters {
    private static final int MAX_DURATION = 60 * 1000;
    public static final String DOZE_SENSORS_WAKE_UP_FULLY = "doze_sensors_wake_up_fully";

    private final Context mContext;
    private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;

    private static IntInOutMatcher sPickupSubtypePerformsProxMatcher;

    public DozeParameters(Context context) {
        mContext = context;
        mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
    }

    public void dump(PrintWriter pw) {
        pw.println("  DozeParameters:");
        pw.print("    getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
        pw.print("    getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false));
        pw.print("    getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true));
        pw.print("    getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false));
        pw.print("    getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true));
        pw.print("    getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
        pw.print("    getPulseOutDuration(): "); pw.println(getPulseOutDuration());
        pw.print("    getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
        pw.print("    getVibrateOnSigMotion(): "); pw.println(getVibrateOnSigMotion());
        pw.print("    getVibrateOnPickup(): "); pw.println(getVibrateOnPickup());
        pw.print("    getProxCheckBeforePulse(): "); pw.println(getProxCheckBeforePulse());
        pw.print("    getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold());
        pw.print("    getPickupSubtypePerformsProxCheck(): ");pw.println(
                dumpPickupSubtypePerformsProxCheck());
    }

    private String dumpPickupSubtypePerformsProxCheck() {
        // Refresh sPickupSubtypePerformsProxMatcher
        getPickupSubtypePerformsProxCheck(0);

        if (sPickupSubtypePerformsProxMatcher == null) {
            return "fallback: " + mContext.getResources().getBoolean(
                    R.bool.doze_pickup_performs_proximity_check);
        } else {
            return "spec: " + sPickupSubtypePerformsProxMatcher.mSpec;
        }
    }

    public boolean getDisplayStateSupported() {
        return getBoolean("doze.display.supported", R.bool.doze_display_state_supported);
    }

    public boolean getDozeSuspendDisplayStateSupported() {
        return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
    }

    public int getPulseDuration(boolean pickup) {
        return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
    }

    public int getPulseInDuration(boolean pickupOrDoubleTap) {
        return pickupOrDoubleTap
                ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup)
                : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
    }

    public int getPulseVisibleDuration() {
        return getInt("doze.pulse.duration.visible", R.integer.doze_pulse_duration_visible);
    }

    public int getPulseOutDuration() {
        return getInt("doze.pulse.duration.out", R.integer.doze_pulse_duration_out);
    }

    public boolean getPulseOnSigMotion() {
        return getBoolean("doze.pulse.sigmotion", R.bool.doze_pulse_on_significant_motion);
    }

    public boolean getVibrateOnSigMotion() {
        return SystemProperties.getBoolean("doze.vibrate.sigmotion", false);
    }

    public boolean getVibrateOnPickup() {
        return SystemProperties.getBoolean("doze.vibrate.pickup", false);
    }

    public boolean getProxCheckBeforePulse() {
        return getBoolean("doze.pulse.proxcheck", R.bool.doze_proximity_check_before_pulse);
    }

    public int getPickupVibrationThreshold() {
        return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold);
    }

    public boolean getAlwaysOn() {
        return mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
    }

    private boolean getBoolean(String propName, int resId) {
        return SystemProperties.getBoolean(propName, mContext.getResources().getBoolean(resId));
    }

    private int getInt(String propName, int resId) {
        int value = SystemProperties.getInt(propName, mContext.getResources().getInteger(resId));
        return MathUtils.constrain(value, 0, MAX_DURATION);
    }

    private String getString(String propName, int resId) {
        return SystemProperties.get(propName, mContext.getString(resId));
    }

    public boolean getPickupSubtypePerformsProxCheck(int subType) {
        String spec = getString("doze.pickup.proxcheck",
                R.string.doze_pickup_subtype_performs_proximity_check);

        if (TextUtils.isEmpty(spec)) {
            // Fall back to non-subtype based property.
            return mContext.getResources().getBoolean(R.bool.doze_pickup_performs_proximity_check);
        }

        if (sPickupSubtypePerformsProxMatcher == null
                || !TextUtils.equals(spec, sPickupSubtypePerformsProxMatcher.mSpec)) {
            sPickupSubtypePerformsProxMatcher = new IntInOutMatcher(spec);
        }

        return sPickupSubtypePerformsProxMatcher.isIn(subType);
    }

    public int getPulseVisibleDurationExtended() {
        return 2 * getPulseVisibleDuration();
    }

    public boolean doubleTapReportsTouchCoordinates() {
        return mContext.getResources().getBoolean(R.bool.doze_double_tap_reports_touch_coordinates);
    }


    /**
     * Parses a spec of the form `1,2,3,!5,*`. The resulting object will match numbers that are
     * listed, will not match numbers that are listed with a ! prefix, and will match / not match
     * unlisted numbers depending on whether * or !* is present.
     *
     * *  -> match any numbers that are not explicitly listed
     * !* -> don't match any numbers that are not explicitly listed
     * 2  -> match 2
     * !3 -> don't match 3
     *
     * It is illegal to specify:
     * - an empty spec
     * - a spec containing that are empty, or a lone !
     * - a spec for anything other than numbers or *
     * - multiple terms for the same number / multiple *s
     */
    public static class IntInOutMatcher {
        private static final String WILDCARD = "*";
        private static final char OUT_PREFIX = '!';

        private final SparseBooleanArray mIsIn;
        private final boolean mDefaultIsIn;
        final String mSpec;

        public IntInOutMatcher(String spec) {
            if (TextUtils.isEmpty(spec)) {
                throw new IllegalArgumentException("Spec must not be empty");
            }

            boolean defaultIsIn = false;
            boolean foundWildcard = false;

            mSpec = spec;
            mIsIn = new SparseBooleanArray();

            for (String itemPrefixed : spec.split(",", -1)) {
                if (itemPrefixed.length() == 0) {
                    throw new IllegalArgumentException(
                            "Illegal spec, must not have zero-length items: `" + spec + "`");
                }
                boolean isIn = itemPrefixed.charAt(0) != OUT_PREFIX;
                String item = isIn ? itemPrefixed : itemPrefixed.substring(1);

                if (itemPrefixed.length() == 0) {
                    throw new IllegalArgumentException(
                            "Illegal spec, must not have zero-length items: `" + spec + "`");
                }

                if (WILDCARD.equals(item)) {
                    if (foundWildcard) {
                        throw new IllegalArgumentException("Illegal spec, `" + WILDCARD +
                                "` must not appear multiple times in `" + spec + "`");
                    }
                    defaultIsIn = isIn;
                    foundWildcard = true;
                } else {
                    int key = Integer.parseInt(item);
                    if (mIsIn.indexOfKey(key) >= 0) {
                        throw new IllegalArgumentException("Illegal spec, `" + key +
                                "` must not appear multiple times in `" + spec + "`");
                    }
                    mIsIn.put(key, isIn);
                }
            }

            if (!foundWildcard) {
                throw new IllegalArgumentException("Illegal spec, must specify either * or !*");
            }

            mDefaultIsIn = defaultIsIn;
        }

        public boolean isIn(int value) {
            return (mIsIn.get(value, mDefaultIsIn));
        }
    }
}
