/*
 * Copyright (C) 2013 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.app.procstats;

import android.os.UserHandle;
import android.service.procstats.ProcessStatsEnums;
import android.service.procstats.ProcessStatsStateProto;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;

import static com.android.internal.app.procstats.ProcessStats.*;

import java.io.PrintWriter;
import java.util.ArrayList;

/**
 * Utilities for dumping.
 */
public final class DumpUtils {
    public static final String[] STATE_NAMES;
    public static final String[] STATE_LABELS;
    public static final String STATE_LABEL_TOTAL;
    public static final String STATE_LABEL_CACHED;
    public static final String[] STATE_NAMES_CSV;
    static final String[] STATE_TAGS;
    static final int[] STATE_PROTO_ENUMS;

    // Make the mapping easy to update.
    static {
        STATE_NAMES = new String[STATE_COUNT];
        STATE_NAMES[STATE_PERSISTENT]               = "Persist";
        STATE_NAMES[STATE_TOP]                      = "Top";
        STATE_NAMES[STATE_IMPORTANT_FOREGROUND]     = "ImpFg";
        STATE_NAMES[STATE_IMPORTANT_BACKGROUND]     = "ImpBg";
        STATE_NAMES[STATE_BACKUP]                   = "Backup";
        STATE_NAMES[STATE_SERVICE]                  = "Service";
        STATE_NAMES[STATE_SERVICE_RESTARTING]       = "ServRst";
        STATE_NAMES[STATE_RECEIVER]                 = "Receivr";
        STATE_NAMES[STATE_HEAVY_WEIGHT]             = "HeavyWt";
        STATE_NAMES[STATE_HOME]                     = "Home";
        STATE_NAMES[STATE_LAST_ACTIVITY]            = "LastAct";
        STATE_NAMES[STATE_CACHED_ACTIVITY]          = "CchAct";
        STATE_NAMES[STATE_CACHED_ACTIVITY_CLIENT]   = "CchCAct";
        STATE_NAMES[STATE_CACHED_EMPTY]             = "CchEmty";

        STATE_LABELS = new String[STATE_COUNT];
        STATE_LABELS[STATE_PERSISTENT]              = "Persistent";
        STATE_LABELS[STATE_TOP]                     = "       Top";
        STATE_LABELS[STATE_IMPORTANT_FOREGROUND]    = "    Imp Fg";
        STATE_LABELS[STATE_IMPORTANT_BACKGROUND]    = "    Imp Bg";
        STATE_LABELS[STATE_BACKUP]                  = "    Backup";
        STATE_LABELS[STATE_SERVICE]                 = "   Service";
        STATE_LABELS[STATE_SERVICE_RESTARTING]      = "Service Rs";
        STATE_LABELS[STATE_RECEIVER]                = "  Receiver";
        STATE_LABELS[STATE_HEAVY_WEIGHT]            = " Heavy Wgt";
        STATE_LABELS[STATE_HOME]                    = "    (Home)";
        STATE_LABELS[STATE_LAST_ACTIVITY]           = "(Last Act)";
        STATE_LABELS[STATE_CACHED_ACTIVITY]         = " (Cch Act)";
        STATE_LABELS[STATE_CACHED_ACTIVITY_CLIENT]  = "(Cch CAct)";
        STATE_LABELS[STATE_CACHED_EMPTY]            = "(Cch Emty)";
        STATE_LABEL_CACHED                          = "  (Cached)";
        STATE_LABEL_TOTAL                           = "     TOTAL";

        STATE_NAMES_CSV = new String[STATE_COUNT];
        STATE_NAMES_CSV[STATE_PERSISTENT]               = "pers";
        STATE_NAMES_CSV[STATE_TOP]                      = "top";
        STATE_NAMES_CSV[STATE_IMPORTANT_FOREGROUND]     = "impfg";
        STATE_NAMES_CSV[STATE_IMPORTANT_BACKGROUND]     = "impbg";
        STATE_NAMES_CSV[STATE_BACKUP]                   = "backup";
        STATE_NAMES_CSV[STATE_SERVICE]                  = "service";
        STATE_NAMES_CSV[STATE_SERVICE_RESTARTING]       = "service-rs";
        STATE_NAMES_CSV[STATE_RECEIVER]                 = "receiver";
        STATE_NAMES_CSV[STATE_HEAVY_WEIGHT]             = "heavy";
        STATE_NAMES_CSV[STATE_HOME]                     = "home";
        STATE_NAMES_CSV[STATE_LAST_ACTIVITY]            = "lastact";
        STATE_NAMES_CSV[STATE_CACHED_ACTIVITY]          = "cch-activity";
        STATE_NAMES_CSV[STATE_CACHED_ACTIVITY_CLIENT]   = "cch-aclient";
        STATE_NAMES_CSV[STATE_CACHED_EMPTY]             = "cch-empty";

        STATE_TAGS = new String[STATE_COUNT];
        STATE_TAGS[STATE_PERSISTENT]                = "p";
        STATE_TAGS[STATE_TOP]                       = "t";
        STATE_TAGS[STATE_IMPORTANT_FOREGROUND]      = "f";
        STATE_TAGS[STATE_IMPORTANT_BACKGROUND]      = "b";
        STATE_TAGS[STATE_BACKUP]                    = "u";
        STATE_TAGS[STATE_SERVICE]                   = "s";
        STATE_TAGS[STATE_SERVICE_RESTARTING]        = "x";
        STATE_TAGS[STATE_RECEIVER]                  = "r";
        STATE_TAGS[STATE_HEAVY_WEIGHT]              = "w";
        STATE_TAGS[STATE_HOME]                      = "h";
        STATE_TAGS[STATE_LAST_ACTIVITY]             = "l";
        STATE_TAGS[STATE_CACHED_ACTIVITY]           = "a";
        STATE_TAGS[STATE_CACHED_ACTIVITY_CLIENT]    = "c";
        STATE_TAGS[STATE_CACHED_EMPTY]              = "e";

        STATE_PROTO_ENUMS = new int[STATE_COUNT];
        STATE_PROTO_ENUMS[STATE_PERSISTENT] = ProcessStatsEnums.PROCESS_STATE_PERSISTENT;
        STATE_PROTO_ENUMS[STATE_TOP] = ProcessStatsEnums.PROCESS_STATE_TOP;
        STATE_PROTO_ENUMS[STATE_IMPORTANT_FOREGROUND] =
                ProcessStatsEnums.PROCESS_STATE_IMPORTANT_FOREGROUND;
        STATE_PROTO_ENUMS[STATE_IMPORTANT_BACKGROUND] =
                ProcessStatsEnums.PROCESS_STATE_IMPORTANT_BACKGROUND;
        STATE_PROTO_ENUMS[STATE_BACKUP] = ProcessStatsEnums.PROCESS_STATE_BACKUP;
        STATE_PROTO_ENUMS[STATE_SERVICE] = ProcessStatsEnums.PROCESS_STATE_SERVICE;
        STATE_PROTO_ENUMS[STATE_SERVICE_RESTARTING] =
                ProcessStatsEnums.PROCESS_STATE_SERVICE_RESTARTING;
        STATE_PROTO_ENUMS[STATE_RECEIVER] = ProcessStatsEnums.PROCESS_STATE_RECEIVER;
        STATE_PROTO_ENUMS[STATE_HEAVY_WEIGHT] = ProcessStatsEnums.PROCESS_STATE_HEAVY_WEIGHT;
        STATE_PROTO_ENUMS[STATE_HOME] = ProcessStatsEnums.PROCESS_STATE_HOME;
        STATE_PROTO_ENUMS[STATE_LAST_ACTIVITY] = ProcessStatsEnums.PROCESS_STATE_LAST_ACTIVITY;
        STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY] = ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY;
        STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY_CLIENT] =
                ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
        STATE_PROTO_ENUMS[STATE_CACHED_EMPTY] = ProcessStatsEnums.PROCESS_STATE_CACHED_EMPTY;
    }

    public static final String[] ADJ_SCREEN_NAMES_CSV = new String[] {
            "off", "on"
    };

    public static final String[] ADJ_MEM_NAMES_CSV = new String[] {
            "norm", "mod",  "low", "crit"
    };

    // State enum is defined in frameworks/base/core/proto/android/service/procstats.proto
    // Update states must sync enum definition as well, the ordering must not be changed.
    static final String[] ADJ_SCREEN_TAGS = new String[] {
            "0", "1"
    };

    static final int[] ADJ_SCREEN_PROTO_ENUMS = new int[] {
            ProcessStatsEnums.SCREEN_STATE_OFF,
            ProcessStatsEnums.SCREEN_STATE_ON
    };

    static final String[] ADJ_MEM_TAGS = new String[] {
            "n", "m",  "l", "c"
    };

    static final int[] ADJ_MEM_PROTO_ENUMS = new int[] {
            ProcessStatsEnums.MEMORY_STATE_NORMAL,
            ProcessStatsEnums.MEMORY_STATE_MODERATE,
            ProcessStatsEnums.MEMORY_STATE_LOW,
            ProcessStatsEnums.MEMORY_STATE_CRITICAL
    };

    static final String CSV_SEP = "\t";

    /**
     * No instantiate
     */
    private DumpUtils() {
    }

    public static void printScreenLabel(PrintWriter pw, int offset) {
        switch (offset) {
            case ADJ_NOTHING:
                pw.print("     ");
                break;
            case ADJ_SCREEN_OFF:
                pw.print("SOff/");
                break;
            case ADJ_SCREEN_ON:
                pw.print(" SOn/");
                break;
            default:
                pw.print("????/");
                break;
        }
    }

    public static void printScreenLabelCsv(PrintWriter pw, int offset) {
        switch (offset) {
            case ADJ_NOTHING:
                break;
            case ADJ_SCREEN_OFF:
                pw.print(ADJ_SCREEN_NAMES_CSV[0]);
                break;
            case ADJ_SCREEN_ON:
                pw.print(ADJ_SCREEN_NAMES_CSV[1]);
                break;
            default:
                pw.print("???");
                break;
        }
    }

    public static void printMemLabel(PrintWriter pw, int offset, char sep) {
        switch (offset) {
            case ADJ_NOTHING:
                pw.print("    ");
                if (sep != 0) pw.print(' ');
                break;
            case ADJ_MEM_FACTOR_NORMAL:
                pw.print("Norm");
                if (sep != 0) pw.print(sep);
                break;
            case ADJ_MEM_FACTOR_MODERATE:
                pw.print(" Mod");
                if (sep != 0) pw.print(sep);
                break;
            case ADJ_MEM_FACTOR_LOW:
                pw.print(" Low");
                if (sep != 0) pw.print(sep);
                break;
            case ADJ_MEM_FACTOR_CRITICAL:
                pw.print("Crit");
                if (sep != 0) pw.print(sep);
                break;
            default:
                pw.print("????");
                if (sep != 0) pw.print(sep);
                break;
        }
    }

    public static void printMemLabelCsv(PrintWriter pw, int offset) {
        if (offset >= ADJ_MEM_FACTOR_NORMAL) {
            if (offset <= ADJ_MEM_FACTOR_CRITICAL) {
                pw.print(ADJ_MEM_NAMES_CSV[offset]);
            } else {
                pw.print("???");
            }
        }
    }

    public static void printPercent(PrintWriter pw, double fraction) {
        fraction *= 100;
        if (fraction < 1) {
            pw.print(String.format("%.2f", fraction));
        } else if (fraction < 10) {
            pw.print(String.format("%.1f", fraction));
        } else {
            pw.print(String.format("%.0f", fraction));
        }
        pw.print("%");
    }

    public static void printProcStateTag(PrintWriter pw, int state) {
        state = printArrayEntry(pw, ADJ_SCREEN_TAGS,  state, ADJ_SCREEN_MOD*STATE_COUNT);
        state = printArrayEntry(pw, ADJ_MEM_TAGS,  state, STATE_COUNT);
        printArrayEntry(pw, STATE_TAGS,  state, 1);
    }

    public static void printProcStateTagProto(ProtoOutputStream proto, long screenId, long memId,
            long stateId, int state) {
        state = printProto(proto, screenId, ADJ_SCREEN_PROTO_ENUMS,
                state, ADJ_SCREEN_MOD * STATE_COUNT);
        state = printProto(proto, memId, ADJ_MEM_PROTO_ENUMS, state, STATE_COUNT);
        printProto(proto, stateId, STATE_PROTO_ENUMS, state, 1);
    }

    public static void printAdjTag(PrintWriter pw, int state) {
        state = printArrayEntry(pw, ADJ_SCREEN_TAGS,  state, ADJ_SCREEN_MOD);
        printArrayEntry(pw, ADJ_MEM_TAGS, state, 1);
    }

    public static void printProcStateAdjTagProto(ProtoOutputStream proto, long screenId, long memId,
            int state) {
        state = printProto(proto, screenId, ADJ_SCREEN_PROTO_ENUMS,
                state, ADJ_SCREEN_MOD * STATE_COUNT);
        printProto(proto, memId, ADJ_MEM_PROTO_ENUMS, state, STATE_COUNT);
    }

    public static void printProcStateDurationProto(ProtoOutputStream proto, long fieldId,
            int procState, long duration) {
        final long stateToken = proto.start(fieldId);
        DumpUtils.printProto(proto, ProcessStatsStateProto.PROCESS_STATE,
                DumpUtils.STATE_PROTO_ENUMS, procState, 1);
        proto.write(ProcessStatsStateProto.DURATION_MS, duration);
        proto.end(stateToken);
    }

    public static void printProcStateTagAndValue(PrintWriter pw, int state, long value) {
        pw.print(',');
        printProcStateTag(pw, state);
        pw.print(':');
        pw.print(value);
    }

    public static void printAdjTagAndValue(PrintWriter pw, int state, long value) {
        pw.print(',');
        printAdjTag(pw, state);
        pw.print(':');
        pw.print(value);
    }

    public static long dumpSingleTime(PrintWriter pw, String prefix, long[] durations,
            int curState, long curStartTime, long now) {
        long totalTime = 0;
        int printedScreen = -1;
        for (int iscreen=0; iscreen<ADJ_COUNT; iscreen+=ADJ_SCREEN_MOD) {
            int printedMem = -1;
            for (int imem=0; imem<ADJ_MEM_FACTOR_COUNT; imem++) {
                int state = imem+iscreen;
                long time = durations[state];
                String running = "";
                if (curState == state) {
                    time += now - curStartTime;
                    if (pw != null) {
                        running = " (running)";
                    }
                }
                if (time != 0) {
                    if (pw != null) {
                        pw.print(prefix);
                        printScreenLabel(pw, printedScreen != iscreen
                                ? iscreen : STATE_NOTHING);
                        printedScreen = iscreen;
                        printMemLabel(pw, printedMem != imem ? imem : STATE_NOTHING, (char)0);
                        printedMem = imem;
                        pw.print(": ");
                        TimeUtils.formatDuration(time, pw); pw.println(running);
                    }
                    totalTime += time;
                }
            }
        }
        if (totalTime != 0 && pw != null) {
            pw.print(prefix);
            pw.print("    TOTAL: ");
            TimeUtils.formatDuration(totalTime, pw);
            pw.println();
        }
        return totalTime;
    }

    public static void dumpAdjTimesCheckin(PrintWriter pw, String sep, long[] durations,
            int curState, long curStartTime, long now) {
        for (int iscreen=0; iscreen<ADJ_COUNT; iscreen+=ADJ_SCREEN_MOD) {
            for (int imem=0; imem<ADJ_MEM_FACTOR_COUNT; imem++) {
                int state = imem+iscreen;
                long time = durations[state];
                if (curState == state) {
                    time += now - curStartTime;
                }
                if (time != 0) {
                    printAdjTagAndValue(pw, state, time);
                }
            }
        }
    }

    private static void dumpStateHeadersCsv(PrintWriter pw, String sep, int[] screenStates,
            int[] memStates, int[] procStates) {
        final int NS = screenStates != null ? screenStates.length : 1;
        final int NM = memStates != null ? memStates.length : 1;
        final int NP = procStates != null ? procStates.length : 1;
        for (int is=0; is<NS; is++) {
            for (int im=0; im<NM; im++) {
                for (int ip=0; ip<NP; ip++) {
                    pw.print(sep);
                    boolean printed = false;
                    if (screenStates != null && screenStates.length > 1) {
                        printScreenLabelCsv(pw, screenStates[is]);
                        printed = true;
                    }
                    if (memStates != null && memStates.length > 1) {
                        if (printed) {
                            pw.print("-");
                        }
                        printMemLabelCsv(pw, memStates[im]);
                        printed = true;
                    }
                    if (procStates != null && procStates.length > 1) {
                        if (printed) {
                            pw.print("-");
                        }
                        pw.print(STATE_NAMES_CSV[procStates[ip]]);
                    }
                }
            }
        }
    }

    public static void dumpProcessSummaryLocked(PrintWriter pw, String prefix, String header,
            ArrayList<ProcessState> procs, int[] screenStates, int[] memStates, int[] procStates,
            long now, long totalTime) {
        for (int i=procs.size()-1; i>=0; i--) {
            final ProcessState proc = procs.get(i);
            proc.dumpSummary(pw, prefix, header, screenStates, memStates, procStates, now,
                    totalTime);
        }
    }

    public static void dumpProcessListCsv(PrintWriter pw, ArrayList<ProcessState> procs,
            boolean sepScreenStates, int[] screenStates, boolean sepMemStates, int[] memStates,
            boolean sepProcStates, int[] procStates, long now) {
        pw.print("process");
        pw.print(CSV_SEP);
        pw.print("uid");
        pw.print(CSV_SEP);
        pw.print("vers");
        dumpStateHeadersCsv(pw, CSV_SEP, sepScreenStates ? screenStates : null,
                sepMemStates ? memStates : null,
                sepProcStates ? procStates : null);
        pw.println();
        for (int i=procs.size()-1; i>=0; i--) {
            ProcessState proc = procs.get(i);
            pw.print(proc.getName());
            pw.print(CSV_SEP);
            UserHandle.formatUid(pw, proc.getUid());
            pw.print(CSV_SEP);
            pw.print(proc.getVersion());
            proc.dumpCsv(pw, sepScreenStates, screenStates, sepMemStates,
                    memStates, sepProcStates, procStates, now);
            pw.println();
        }
    }

    public static int printArrayEntry(PrintWriter pw, String[] array, int value, int mod) {
        int index = value/mod;
        if (index >= 0 && index < array.length) {
            pw.print(array[index]);
        } else {
            pw.print('?');
        }
        return value - index*mod;
    }

    public static int printProto(ProtoOutputStream proto, long fieldId,
            int[] enums, int value, int mod) {
        int index = value/mod;
        if (index >= 0 && index < enums.length) {
            proto.write(fieldId, enums[index]);
        } // else enum default is always zero in proto3
        return value - index*mod;
    }

    public static String collapseString(String pkgName, String itemName) {
        if (itemName.startsWith(pkgName)) {
            final int ITEMLEN = itemName.length();
            final int PKGLEN = pkgName.length();
            if (ITEMLEN == PKGLEN) {
                return "";
            } else if (ITEMLEN >= PKGLEN) {
                if (itemName.charAt(PKGLEN) == '.') {
                    return itemName.substring(PKGLEN);
                }
            }
        }
        return itemName;
    }
}
