/*
 * Copyright (C) 2017 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.plugins.qs;

import android.annotation.NonNull;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.metrics.LogMaker;
import android.service.quicksettings.Tile;
import android.view.View;

import androidx.annotation.Nullable;

import com.android.internal.logging.InstanceId;
import com.android.systemui.plugins.annotations.DependsOn;
import com.android.systemui.plugins.annotations.ProvidesInterface;
import com.android.systemui.plugins.qs.QSTile.Callback;
import com.android.systemui.plugins.qs.QSTile.Icon;
import com.android.systemui.plugins.qs.QSTile.State;

import java.util.Objects;
import java.util.function.Supplier;

@ProvidesInterface(version = QSTile.VERSION)
@DependsOn(target = QSIconView.class)
@DependsOn(target = DetailAdapter.class)
@DependsOn(target = Callback.class)
@DependsOn(target = Icon.class)
@DependsOn(target = State.class)
public interface QSTile {
    int VERSION = 1;

    DetailAdapter getDetailAdapter();
    String getTileSpec();

    boolean isAvailable();
    void setTileSpec(String tileSpec);

    @Deprecated default void clearState() {}
    void refreshState();

    void addCallback(Callback callback);
    void removeCallback(Callback callback);
    void removeCallbacks();

    QSIconView createTileView(Context context);

    /**
     * The tile was clicked.
     *
     * @param view The view that was clicked.
     */
    void click(@Nullable View view);

    /**
     * The tile secondary click was triggered.
     *
     * @param view The view that was clicked.
     */
    void secondaryClick(@Nullable View view);

    /**
     * The tile was long clicked.
     *
     * @param view The view that was clicked.
     */
    void longClick(@Nullable View view);

    void userSwitch(int currentUser);
    int getMetricsCategory();

    void setListening(Object client, boolean listening);
    void setDetailListening(boolean show);

    void destroy();

    CharSequence getTileLabel();

    State getState();

    default LogMaker populate(LogMaker logMaker) {
        return logMaker;
    }

    /**
     * Return a string to be used to identify the tile in UiEvents.
     */
    default String getMetricsSpec() {
        return getClass().getSimpleName();
    }

    /**
     * Return an {@link InstanceId} to be used to identify the tile in UiEvents.
     */
    InstanceId getInstanceId();

    default boolean isTileReady() {
        return false;
    }

    @ProvidesInterface(version = Callback.VERSION)
    public interface Callback {
        public static final int VERSION = 1;
        void onStateChanged(State state);
        void onShowDetail(boolean show);
        void onToggleStateChanged(boolean state);
        void onScanStateChanged(boolean state);
        void onAnnouncementRequested(CharSequence announcement);
    }

    @ProvidesInterface(version = Icon.VERSION)
    public static abstract class Icon {
        public static final int VERSION = 1;
        abstract public Drawable getDrawable(Context context);

        public Drawable getInvisibleDrawable(Context context) {
            return getDrawable(context);
        }

        @Override
        public int hashCode() {
            return Icon.class.hashCode();
        }

        public int getPadding() {
            return 0;
        }

        @Override
        @NonNull
        public String toString() {
            return "Icon";
        }
    }

    @ProvidesInterface(version = State.VERSION)
    public static class State {
        public static final int VERSION = 1;
        public static final int DEFAULT_STATE = Tile.STATE_ACTIVE;

        public Icon icon;
        public Supplier<Icon> iconSupplier;
        public int state = DEFAULT_STATE;
        public CharSequence label;
        public CharSequence secondaryLabel;
        public CharSequence contentDescription;
        public CharSequence stateDescription;
        public CharSequence dualLabelContentDescription;
        public boolean disabledByPolicy;
        public boolean dualTarget = false;
        public boolean isTransient = false;
        public String expandedAccessibilityClassName;
        public SlashState slash;
        public boolean handlesLongClick = true;
        public boolean showRippleEffect = true;
        public Drawable sideViewCustomDrawable;
        public String spec;

        public boolean copyTo(State other) {
            if (other == null) throw new IllegalArgumentException();
            if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
            final boolean changed = !Objects.equals(other.spec, spec)
                    || !Objects.equals(other.icon, icon)
                    || !Objects.equals(other.iconSupplier, iconSupplier)
                    || !Objects.equals(other.label, label)
                    || !Objects.equals(other.secondaryLabel, secondaryLabel)
                    || !Objects.equals(other.contentDescription, contentDescription)
                    || !Objects.equals(other.stateDescription, stateDescription)
                    || !Objects.equals(other.dualLabelContentDescription,
                            dualLabelContentDescription)
                    || !Objects.equals(other.expandedAccessibilityClassName,
                            expandedAccessibilityClassName)
                    || !Objects.equals(other.disabledByPolicy, disabledByPolicy)
                    || !Objects.equals(other.state, state)
                    || !Objects.equals(other.isTransient, isTransient)
                    || !Objects.equals(other.dualTarget, dualTarget)
                    || !Objects.equals(other.slash, slash)
                    || !Objects.equals(other.handlesLongClick, handlesLongClick)
                    || !Objects.equals(other.showRippleEffect, showRippleEffect)
                    || !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable);
            other.spec = spec;
            other.icon = icon;
            other.iconSupplier = iconSupplier;
            other.label = label;
            other.secondaryLabel = secondaryLabel;
            other.contentDescription = contentDescription;
            other.stateDescription = stateDescription;
            other.dualLabelContentDescription = dualLabelContentDescription;
            other.expandedAccessibilityClassName = expandedAccessibilityClassName;
            other.disabledByPolicy = disabledByPolicy;
            other.state = state;
            other.dualTarget = dualTarget;
            other.isTransient = isTransient;
            other.slash = slash != null ? slash.copy() : null;
            other.handlesLongClick = handlesLongClick;
            other.showRippleEffect = showRippleEffect;
            other.sideViewCustomDrawable = sideViewCustomDrawable;
            return changed;
        }

        @Override
        public String toString() {
            return toStringBuilder().toString();
        }

        // Used in dumps to determine current state of a tile.
        // This string may be used for CTS testing of tiles, so removing elements is discouraged.
        protected StringBuilder toStringBuilder() {
            final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
            sb.append("spec=").append(spec);
            sb.append(",icon=").append(icon);
            sb.append(",iconSupplier=").append(iconSupplier);
            sb.append(",label=").append(label);
            sb.append(",secondaryLabel=").append(secondaryLabel);
            sb.append(",contentDescription=").append(contentDescription);
            sb.append(",stateDescription=").append(stateDescription);
            sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
            sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName);
            sb.append(",disabledByPolicy=").append(disabledByPolicy);
            sb.append(",dualTarget=").append(dualTarget);
            sb.append(",isTransient=").append(isTransient);
            sb.append(",state=").append(state);
            sb.append(",slash=\"").append(slash).append("\"");
            sb.append(",sideViewCustomDrawable=").append(sideViewCustomDrawable);
            return sb.append(']');
        }

        public State copy() {
            State state = new State();
            copyTo(state);
            return state;
        }
    }

    @ProvidesInterface(version = BooleanState.VERSION)
    public static class BooleanState extends State {
        public static final int VERSION = 1;
        public boolean value;
        public boolean forceExpandIcon;

        @Override
        public boolean copyTo(State other) {
            final BooleanState o = (BooleanState) other;
            final boolean changed = super.copyTo(other)
                    || o.value != value
                    || o.forceExpandIcon != forceExpandIcon;
            o.value = value;
            o.forceExpandIcon = forceExpandIcon;
            return changed;
        }

        @Override
        protected StringBuilder toStringBuilder() {
            final StringBuilder rt = super.toStringBuilder();
            rt.insert(rt.length() - 1, ",value=" + value);
            rt.insert(rt.length() - 1, ",forceExpandIcon=" + forceExpandIcon);
            return rt;
        }

        @Override
        public State copy() {
            BooleanState state = new BooleanState();
            copyTo(state);
            return state;
        }
    }

    @ProvidesInterface(version = SignalState.VERSION)
    public static final class SignalState extends BooleanState {
        public static final int VERSION = 1;
        public boolean activityIn;
        public boolean activityOut;
        public boolean isOverlayIconWide;
        public int overlayIconId;

        @Override
        public boolean copyTo(State other) {
            final SignalState o = (SignalState) other;
            final boolean changed = o.activityIn != activityIn
                    || o.activityOut != activityOut
                    || o.isOverlayIconWide != isOverlayIconWide
                    || o.overlayIconId != overlayIconId;
            o.activityIn = activityIn;
            o.activityOut = activityOut;
            o.isOverlayIconWide = isOverlayIconWide;
            o.overlayIconId = overlayIconId;
            return super.copyTo(other) || changed;
        }

        @Override
        protected StringBuilder toStringBuilder() {
            final StringBuilder rt = super.toStringBuilder();
            rt.insert(rt.length() - 1, ",activityIn=" + activityIn);
            rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
            return rt;
        }

        @Override
        public State copy() {
            SignalState state = new SignalState();
            copyTo(state);
            return state;
        }
    }

    @ProvidesInterface(version = SlashState.VERSION)
    public static class SlashState {
        public static final int VERSION = 2;

        public boolean isSlashed;
        public float rotation;

        @Override
        public String toString() {
            return "isSlashed=" + isSlashed + ",rotation=" + rotation;
        }

        @Override
        public boolean equals(Object o) {
            if (o == null) return false;
            try {
                return (((SlashState) o).rotation == rotation)
                        && (((SlashState) o).isSlashed == isSlashed);
            } catch (ClassCastException e) {
                return false;
            }
        }

        public SlashState copy() {
            SlashState state = new SlashState();
            state.rotation = rotation;
            state.isSlashed = isSlashed;
            return state;
        }
    }
}
