/**
 * Copyright (c) 2016, 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.server.utils;

import android.annotation.IntDef;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

/**
 * Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the
 * {@link #PRIORITY_ARG} and {@link #PROTO_ARG} arguments.
 * <p>
 * Typical usage:
 *
 * <pre><code>
public class SpringfieldNuclearPowerPlant extends Binder {

 private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {

     @Override
     public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
       if (asProto) {
         ProtoOutputStream proto = new ProtoOutputStream(fd);
         proto.write(SpringfieldProto.DONUTS, 1);
         proto.flush();
       } else {
         pw.println("Donuts in the box: 1");
       }
     }

     @Override
     public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
        if (asProto) {
          ProtoOutputStream proto = new ProtoOutputStream(fd);
          proto.write(SpringfieldProto.REACTOR_STATUS, DANGER_MELTDOWN_IMMINENT);
          proto.flush();
        } else {
          pw.println("Nuclear reactor status: DANGER - MELTDOWN IMMINENT");
        }
     }
  };

  @Override
  protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
      PriorityDump.dump(mPriorityDumper, fd, pw, args);
  }
}

 * </code></pre>
 *
 * <strong>Disclaimer</strong>: a real-life service should prioritize core status over donuts :-)
 *
 * <p>Then to invoke it:
 *
 * <pre><code>
 *
    $ adb shell dumpsys snpp
    Donuts in the box: 1
    Nuclear reactor status: DANGER - MELTDOWN IMMINENT

    $ adb shell dumpsys snpp --dump-priority CRITICAL
    Donuts in the box: 1

    $ adb shell dumpsys snpp --dump-priority NORMAL
    Nuclear reactor status: DANGER - MELTDOWN IMMINENT

    $ adb shell dumpsys snpp --dump-priority CRITICAL --proto
    //binary output

 * </code></pre>
 *
 *
 *
 * <p>To run the unit tests:
 * <pre><code>
 *
 mmm -j32 frameworks/base/services/tests/servicestests/ && \
 adb install -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && \
 adb shell am instrument -e class "com.android.server.utils.PriorityDumpTest" \
 -w "com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner"

 * </code></pre>
 *
 *
 * @hide
 */
public final class PriorityDump {

    public static final String PRIORITY_ARG = "--dump-priority";
    public static final String PROTO_ARG = "--proto";

    private PriorityDump() {
        throw new UnsupportedOperationException();
    }

    /** Enum to switch through supported priority types */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({PRIORITY_TYPE_INVALID, PRIORITY_TYPE_CRITICAL, PRIORITY_TYPE_HIGH,
            PRIORITY_TYPE_NORMAL})
    private @interface PriorityType { }
    private static final int PRIORITY_TYPE_INVALID = 0;
    private static final int PRIORITY_TYPE_CRITICAL = 1;
    private static final int PRIORITY_TYPE_HIGH = 2;
    private static final int PRIORITY_TYPE_NORMAL = 3;

    /**
     * Parses {@code args} matching {@code --dump-priority} and/or {@code --proto}. The matching
     * arguments are stripped.
     * <p>
     * If priority args are passed as an argument, it will call the appropriate method and if proto
     * args are passed then the {@code asProto} flag is set.
     * <p>
     * For example, if called as {@code --dump-priority HIGH arg1 arg2 arg3}, it will call
     * <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}, false) </code>
     * <p>
     * If the {@code --dump-priority} is not set, it calls
     * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[], boolean)} passing the whole
     * {@code args} instead.
     */
    public static void dump(PriorityDumper dumper, FileDescriptor fd, PrintWriter pw,
            String[] args) {
        boolean asProto = false;
        @PriorityType int priority = PRIORITY_TYPE_INVALID;

        if (args == null) {
            dumper.dump(fd, pw, args, asProto);
            return;
        }

        String[] strippedArgs = new String[args.length];
        int strippedCount = 0;
        for (int argIndex = 0; argIndex < args.length; argIndex++) {
            if (args[argIndex].equals(PROTO_ARG)) {
                asProto = true;
            } else if (args[argIndex].equals(PRIORITY_ARG)) {
                if (argIndex + 1 < args.length) {
                    argIndex++;
                    priority = getPriorityType(args[argIndex]);
                }
            } else {
                strippedArgs[strippedCount++] = args[argIndex];
            }
        }

        if (strippedCount < args.length) {
            strippedArgs = Arrays.copyOf(strippedArgs, strippedCount);
        }

        switch (priority) {
            case PRIORITY_TYPE_CRITICAL: {
                dumper.dumpCritical(fd, pw, strippedArgs, asProto);
                return;
            }
            case PRIORITY_TYPE_HIGH: {
                dumper.dumpHigh(fd, pw, strippedArgs, asProto);
                return;
            }
            case PRIORITY_TYPE_NORMAL: {
                dumper.dumpNormal(fd, pw, strippedArgs, asProto);
                return;
            }
            default: {
                dumper.dump(fd, pw, strippedArgs, asProto);
                return;
            }
        }
    }

    /**
     * Converts priority argument type to enum.
     */
    private static @PriorityType int getPriorityType(String arg) {
        switch (arg) {
            case "CRITICAL": {
                return PRIORITY_TYPE_CRITICAL;
            }
            case "HIGH": {
                return PRIORITY_TYPE_HIGH;
            }
            case "NORMAL": {
                return PRIORITY_TYPE_NORMAL;
            }
        }
        return PRIORITY_TYPE_INVALID;
    }

    /**
     * Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the
     * {@link #PRIORITY_ARG} and {@link #PROTO_ARG} arguments.
     *
     * @hide
     */
    public interface PriorityDumper {

        /**
         * Dumps only the critical section.
         */
        @SuppressWarnings("unused")
        default void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
                boolean asProto) {
        }

        /**
         * Dumps only the high-priority section.
         */
        @SuppressWarnings("unused")
        default void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
        }

        /**
         * Dumps only the normal section.
         */
        @SuppressWarnings("unused")
        default void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
        }

        /**
         * Dumps all sections.
         * <p>
         * This method is called when
         * {@link PriorityDump#dump(PriorityDumper, FileDescriptor, PrintWriter, String[], boolean)}
         * is called without priority arguments. By default, it calls the 3 {@code dumpTYPE}
         * methods, so sub-classes just need to implement the priority types they support.
         */
        @SuppressWarnings("unused")
        default void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
            dumpCritical(fd, pw, args, asProto);
            dumpHigh(fd, pw, args, asProto);
            dumpNormal(fd, pw, args, asProto);
        }
    }
}
