/*
 * Copyright (C) 2012 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.gallery3d.util;

import android.util.Log;

import com.android.gallery3d.common.Utils;

import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;

// ProfileData keeps profiling samples in a tree structure.
// The addSample() method adds a sample. The dumpToFile() method saves the data
// to a file. The reset() method clears all samples.
public class ProfileData {
    @SuppressWarnings("unused")
    private static final String TAG = "ProfileData";

    private static class Node {
        public int id;  // this is the name of this node, mapped from mNameToId
        public Node parent;
        public int sampleCount;
        public ArrayList<Node> children;
        public Node(Node parent, int id) {
            this.parent = parent;
            this.id = id;
        }
    }

    private Node mRoot;
    private int mNextId;
    private HashMap<String, Integer> mNameToId;
    private DataOutputStream mOut;
    private byte mScratch[] = new byte[4];  // scratch space for writeInt()

    public ProfileData() {
        mRoot = new Node(null, -1);  // The id of the root node is unused.
        mNameToId = new HashMap<String, Integer>();
    }

    public void reset() {
        mRoot = new Node(null, -1);
        mNameToId.clear();
        mNextId = 0;
    }

    private int nameToId(String name) {
        Integer id = mNameToId.get(name);
        if (id == null) {
            id = ++mNextId;  // The tool doesn't want id=0, so we start from 1.
            mNameToId.put(name, id);
        }
        return id;
    }

    public void addSample(String[] stack) {
        int[] ids = new int[stack.length];
        for (int i = 0; i < stack.length; i++) {
            ids[i] = nameToId(stack[i]);
        }

        Node node = mRoot;
        for (int i = stack.length - 1; i >= 0; i--) {
            if (node.children == null) {
                node.children = new ArrayList<Node>();
            }

            int id = ids[i];
            ArrayList<Node> children = node.children;
            int j;
            for (j = 0; j < children.size(); j++) {
                if (children.get(j).id == id) break;
            }
            if (j == children.size()) {
                children.add(new Node(node, id));
            }

            node = children.get(j);
        }

        node.sampleCount++;
    }

    public void dumpToFile(String filename) {
        try {
            mOut = new DataOutputStream(new FileOutputStream(filename));
            // Start record
            writeInt(0);
            writeInt(3);
            writeInt(1);
            writeInt(20000);  // Sampling period: 20ms
            writeInt(0);

            // Samples
            writeAllStacks(mRoot, 0);

            // End record
            writeInt(0);
            writeInt(1);
            writeInt(0);
            writeAllSymbols();
        } catch (IOException ex) {
            Log.w("Failed to dump to file", ex);
        } finally {
            Utils.closeSilently(mOut);
        }
    }

    // Writes out one stack, consisting of N+2 words:
    // first word: sample count
    // second word: depth of the stack (N)
    // N words: each word is the id of one address in the stack
    private void writeOneStack(Node node, int depth) throws IOException {
        writeInt(node.sampleCount);
        writeInt(depth);
        while (depth-- > 0) {
            writeInt(node.id);
            node = node.parent;
        }
    }

    private void writeAllStacks(Node node, int depth) throws IOException {
        if (node.sampleCount > 0) {
            writeOneStack(node, depth);
        }

        ArrayList<Node> children = node.children;
        if (children != null) {
            for (int i = 0; i < children.size(); i++) {
                writeAllStacks(children.get(i), depth + 1);
            }
        }
    }

    // Writes out the symbol table. Each line is like:
    // 0x17e java.util.ArrayList.isEmpty(ArrayList.java:319)
    private void writeAllSymbols() throws IOException {
        for (Entry<String, Integer> entry : mNameToId.entrySet()) {
            mOut.writeBytes(String.format("0x%x %s\n", entry.getValue(), entry.getKey()));
        }
    }

    private void writeInt(int v) throws IOException {
        mScratch[0] = (byte) v;
        mScratch[1] = (byte) (v >> 8);
        mScratch[2] = (byte) (v >> 16);
        mScratch[3] = (byte) (v >> 24);
        mOut.write(mScratch);
    }
}
