/*
 * Copyright (C) 2022 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.flags;

import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;

import androidx.annotation.NonNull;

import com.android.systemui.statusbar.commandline.Command;

import java.io.PrintWriter;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Named;

/**
 * A {@link Command} used to flip flags in SystemUI.
 */
public class FlagCommand implements Command {
    public static final String FLAG_COMMAND = "flag";

    private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
    private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
    private final List<String> mSetCommands = List.of("set", "put");
    private final FeatureFlagsDebug mFeatureFlags;
    private final Map<String, Flag<?>> mAllFlags;

    @Inject
    FlagCommand(
            FeatureFlagsDebug featureFlags,
            @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags
    ) {
        mFeatureFlags = featureFlags;
        mAllFlags = allFlags;
    }

    @Override
    public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
        if (args.size() == 0) {
            pw.println("Error: no flag name supplied");
            help(pw);
            pw.println();
            printKnownFlags(pw);
            return;
        }

        String name = args.get(0);
        if (!mAllFlags.containsKey(name)) {
            pw.println("Unknown flag name: " + name);
            pw.println();
            printKnownFlags(pw);
            return;
        }

        Flag<?> flag = mAllFlags.get(name);

        String cmd = "";
        if (args.size() > 1) {
            cmd = args.get(1).toLowerCase();
        }

        if ("erase".equals(cmd) || "reset".equals(cmd)) {
            if (args.size() > 2) {
                pw.println("Invalid number of arguments to reset a flag.");
                help(pw);
                return;
            }

            mFeatureFlags.eraseFlag(flag);
            return;
        }

        boolean shouldSet = true;
        if (args.size() == 1) {
            shouldSet = false;
        }
        if (isBooleanFlag(flag)) {
            if (args.size() > 2) {
                pw.println("Invalid number of arguments for a boolean flag.");
                help(pw);
                return;
            }
            boolean newValue = isBooleanFlagEnabled(flag);
            if ("toggle".equals(cmd)) {
                newValue = !newValue;
            } else if (mOnCommands.contains(cmd)) {
                newValue = true;
            } else if (mOffCommands.contains(cmd)) {
                newValue = false;
            } else if (shouldSet) {
                pw.println("Invalid on/off argument supplied");
                help(pw);
                return;
            }

            pw.println("Flag " + name + " is " + newValue);
            pw.flush();  // Next command will restart sysui, so flush before we do so.
            if (shouldSet) {
                mFeatureFlags.setBooleanFlagInternal(flag, newValue);
            }
            return;

        } else if (isStringFlag(flag)) {
            if (shouldSet) {
                if (args.size() != 3) {
                    pw.println("Invalid number of arguments a StringFlag.");
                    help(pw);
                    return;
                } else if (!mSetCommands.contains(cmd)) {
                    pw.println("Unknown command: " + cmd);
                    help(pw);
                    return;
                }
                String value = args.get(2);
                pw.println("Setting Flag " + name + " to " + value);
                pw.flush();  // Next command will restart sysui, so flush before we do so.
                mFeatureFlags.setStringFlagInternal(flag, args.get(2));
            } else {
                pw.println("Flag " + name + " is " + getStringFlag(flag));
            }
            return;
        } else if (isIntFlag(flag)) {
            if (shouldSet) {
                if (args.size() != 3) {
                    pw.println("Invalid number of arguments for an IntFlag.");
                    help(pw);
                    return;
                } else if (!mSetCommands.contains(cmd)) {
                    pw.println("Unknown command: " + cmd);
                    help(pw);
                    return;
                }
                int value = Integer.parseInt(args.get(2));
                pw.println("Setting Flag " + name + " to " + value);
                pw.flush();  // Next command will restart sysui, so flush before we do so.
                mFeatureFlags.setIntFlagInternal(flag, value);
            } else {
                pw.println("Flag " + name + " is " + getIntFlag(flag));
            }
            return;
        }
    }

    @Override
    public void help(PrintWriter pw) {
        pw.println("Usage: adb shell cmd statusbar flag <id> [options]");
        pw.println();
        pw.println("  Boolean Flag Options: "
                        + "[true|false|1|0|on|off|enable|disable|toggle|erase|reset]");
        pw.println("  String Flag Options: [set|put \"<value>\"]");
        pw.println("  Int Flag Options: [set|put <value>]");
        pw.println();
        pw.println("The id can either be a numeric integer or the corresponding field name");
        pw.println(
                "If no argument is supplied after the id, the flags runtime value is output");
    }

    private boolean isBooleanFlag(Flag<?> flag) {
        return (flag instanceof BooleanFlag)
                || (flag instanceof ResourceBooleanFlag)
                || (flag instanceof SysPropFlag);
    }

    private boolean isBooleanFlagEnabled(Flag<?> flag) {
        if (flag instanceof ReleasedFlag) {
            return mFeatureFlags.isEnabled((ReleasedFlag) flag);
        } else if (flag instanceof UnreleasedFlag) {
            return mFeatureFlags.isEnabled((UnreleasedFlag) flag);
        } else if (flag instanceof ResourceBooleanFlag) {
            return mFeatureFlags.isEnabled((ResourceBooleanFlag) flag);
        } else if (flag instanceof SysPropFlag) {
            return mFeatureFlags.isEnabled((SysPropBooleanFlag) flag);
        }

        return false;
    }

    private boolean isStringFlag(Flag<?> flag) {
        return (flag instanceof StringFlag) || (flag instanceof ResourceStringFlag);
    }

    private String getStringFlag(Flag<?> flag) {
        if (flag instanceof StringFlag) {
            return mFeatureFlags.getString((StringFlag) flag);
        } else if (flag instanceof ResourceStringFlag) {
            return mFeatureFlags.getString((ResourceStringFlag) flag);
        }

        return "";
    }

    private boolean isIntFlag(Flag<?> flag) {
        return (flag instanceof IntFlag) || (flag instanceof ResourceIntFlag);
    }

    private int getIntFlag(Flag<?> flag) {
        if (flag instanceof IntFlag) {
            return mFeatureFlags.getInt((IntFlag) flag);
        } else if (flag instanceof ResourceIntFlag) {
            return mFeatureFlags.getInt((ResourceIntFlag) flag);
        }

        return 0;
    }

    private int flagNameToId(String flagName) {
        Map<String, Flag<?>> flagFields = FlagsFactory.INSTANCE.getKnownFlags();
        for (String fieldName : flagFields.keySet()) {
            if (flagName.equals(fieldName)) {
                return flagFields.get(fieldName).getId();
            }
        }

        return 0;
    }

    private void printKnownFlags(PrintWriter pw) {
        Map<String, Flag<?>> fields = FlagsFactory.INSTANCE.getKnownFlags();

        int longestFieldName = 0;
        for (String fieldName : fields.keySet()) {
            longestFieldName = Math.max(longestFieldName, fieldName.length());
        }

        pw.println("Known Flags:");
        pw.print("Flag Name");
        for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
            pw.print(" ");
        }
        pw.println(" Value");
        for (int i = 0; i < longestFieldName; i++) {
            pw.print("=");
        }
        pw.println(" ========");
        for (String fieldName : fields.keySet()) {
            Flag<?> flag = fields.get(fieldName);
            if (!mAllFlags.containsKey(flag.getName())) {
                continue;
            }
            pw.print(fieldName);
            int fieldWidth = fieldName.length();
            for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
                pw.print(" ");
            }
            pw.print(" ");
            if (isBooleanFlag(flag)) {
                pw.println(isBooleanFlagEnabled(flag));
            } else if (isStringFlag(flag)) {
                pw.println(getStringFlag(flag));
            } else if (isIntFlag(flag)) {
                pw.println(getIntFlag(flag));
            } else {
                pw.println("<unknown flag type>");
            }
        }
    }
}
