/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: AnalyzeHeap.java
 * Written by: Dmitry Nadezhin, Sun Microsystems.
 *
 * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.database;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

/**
 * Class to analyze dump of JVM heap.
 */
public class AnalyzeHeap {

    private static final boolean REFERENCES = true;
    ArrayList<MyObject> objs = new ArrayList<MyObject>();

    private AnalyzeHeap() {
    }

    public static void analyze(String fileName) {
        AnalyzeHeap dumpHeap = new AnalyzeHeap();
        try {
            DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(fileName)));
            dumpHeap.read(in);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println((dumpHeap.objs.size() - 1) + " objects");

        dumpHeap.garbageCollect();
        dumpHeap.dump("heapdump.txt");
        dumpHeap.makePaths();
        dumpHeap.dump("heapdump2.txt");
    }

    private void read(DataInputStream in) throws IOException {
        int numObjs = in.readInt();
        objs.clear();
        while (objs.size() <= numObjs) {
            objs.add(null);
        }
        ArrayList<String> staticFields = new ArrayList<String>();
        ArrayList<String> fields = new ArrayList<String>();
        for (;;) {
            int h = in.readInt();
            if (h == 0) {
                break;
            }
            String className = in.readUTF();
            int mode = in.readByte();
            int staticLength = in.readInt();
            staticFields.clear();
            fields.clear();
            for (int i = 0; i < staticLength; i++) {
                staticFields.add(in.readUTF());
            }
            int length = in.readInt();
            for (int i = 0; i < length; i++) {
                fields.add(in.readUTF());
            }
            MyClass cls = new MyClass(className, mode, staticFields, fields);
            objs.set(h, cls);
        }
        for (;;) {
            int h = in.readInt();
            if (h == 0) {
                break;
            }
            MyString s = new MyString(in.readUTF());
            objs.set(h, s);
        }
        for (int h = 1; h < objs.size(); h++) {
            MyObject obj = objs.get(h);
            if (obj == null) {
                objs.set(h, new MyObject());
            }
        }
        for (int h = 1;; h++) {
            int classH = in.readInt();
            if (classH == 0) {
                break;
            }
            MyObject obj = objs.get(h);
            MyClass cls = (MyClass) objs.get(classH);
            obj.id = h;
            obj.setClass(cls);
            switch (cls.mode) {
                case MyClass.ARRAY:
                    int length = in.readInt();
                    for (int i = 0; i < length; i++) {
                        int elem = in.readInt();
                        new Link(obj, MyField.getElem(i), objs.get(elem));
                        //                          System.out.println("\t" + elem);
                    }
                    break;
                case MyClass.MAP:
                    int mapLength = in.readInt();
                    for (int i = 0; i < mapLength; i++) {
                        int key = in.readInt();
                        new Link(obj, MyField.getKey(i), objs.get(key));
                        int value = in.readInt();
                        new Link(obj, MyField.getElem(i), objs.get(value));
                    }
                    break;
                case MyClass.STRING:
                    break;
                case MyClass.CLASS:
                    obj.pathLink = new Link(null, ((MyClass) obj).classField, obj);
                    for (int i = 0; i < cls.fields.length; i++) {
                        int value = in.readInt();
                        new Link(obj, cls.fields[i], objs.get(value));
                    }
                    cls = (MyClass) objs.get(h);
                    for (int i = 0; i < cls.staticFields.length; i++) {
                        int value = in.readInt();
                        new Link(obj, cls.staticFields[i], objs.get(value));
                    }
                    break;
                case MyClass.NORMAL:
                    for (int i = 0; i < cls.fields.length; i++) {
                        int value = in.readInt();
                        new Link(obj, cls.fields[i], objs.get(value));
                    }
                    break;
            }
        }
    }

    private void garbageCollect(MyObject obj, HashSet<MyObject> visited) {
        if (obj == null || visited.contains(obj)) {
            return;
        }
        visited.add(obj);
        for (Iterator<Link> it = obj.linksFrom.iterator(); it.hasNext();) {
            Link l = it.next();
            if (!REFERENCES && l.field.referent) {
                continue;
            }
            garbageCollect(l.to, visited);
        }
    }

    private void garbageCollect() {
        HashSet<MyObject> visited = new HashSet<MyObject>();
        for (int h = 1; h < objs.size(); h++) {
            MyObject obj = objs.get(h);
            if (obj instanceof MyClass) {
                garbageCollect(obj, visited);
            }
        }
        int collected = 0, remained = 0;
        for (int h = 1; h < objs.size(); h++) {
            MyObject obj = objs.get(h);
            if (obj == null) {
                continue;
            }
            if (!visited.contains(obj)) {
                objs.set(h, null);
                collected++;
            }
            for (Iterator<Link> it = obj.linksTo.iterator(); it.hasNext();) {
                Link l = it.next();
                if (l.from != null && !visited.contains(l.from)) {
                    it.remove();
                }
            }
            remained++;
        }
        System.out.println(collected + " objects collected  " + remained + " remained");
    }

    private void makePaths() {
        for (int k = 0; k < 100; k++) {
            int named = stepPath(true, false, false);
            if (named == 0) {
                break;
            }
        }
        if (REFERENCES) {
            for (int k = 0; k < 100; k++) {
                int named = stepPath(true, true, false);
                if (named == 0) {
                    break;
                }
            }
        }
//        stepPath(true, false);
//        for (int k = 0; k < 100; k++) {
//            int named = stepPath(false, false, true);
//            if (named == 0) break;
//        }
        countUnnamed();

        int singleRefered = 0;
        for (int h = 0; h < objs.size(); h++) {
            MyObject obj = objs.get(h);
            if (obj == null) {
                continue;
            }
            if (!obj.isSingleOwned()) {
                continue;
            }
            if (obj.linksTo.size() == 0) {
                System.out.println(obj + " has no access");
                continue;
            }
            singleRefered++;
            Link pathLink = obj.linksTo.get(0);
            assert obj.pathLink == null || obj.pathLink == pathLink;
            obj.pathLink = pathLink;
            ;
        }
        System.out.println(singleRefered + " single-refered");
        countUnnamed();
    }

    private int stepPath(boolean doMaps, boolean trackReferents, boolean verbose) {
        HashSet<MyObject> named = new HashSet<MyObject>();
        for (int h = 1; h < objs.size(); h++) {
            MyObject obj = objs.get(h);
            if (obj == null || obj.pathLink == null) {
                continue;
            }
            if (named.contains(obj)) {
                continue;
            }
            boolean doAll = doMaps || obj.cls.mode != MyClass.MAP && obj.cls.mode != MyClass.ARRAY;
            for (Iterator<Link> it = obj.linksFrom.iterator(); it.hasNext();) {
                Link l = it.next();
                if (l.to == null || l.to.pathLink != null) {
                    continue;
                }
                if (!trackReferents && l.field.referent) {
                    continue;
                }
                boolean single = l.to.isSingleOwned();
                if (doAll || single) {
                    l.to.pathLink = l;
                    named.add(l.to);
                    if (verbose && !single) {
                        System.out.println(l.to.toString());
                    }
                }
            }
        }
        System.out.println(named.size() + " named");
//        countUnnamed();
        return named.size();
    }

    private void countUnnamed() {
        int unnamed = 0;
        for (int h = 0; h < objs.size(); h++) {
            MyObject obj = objs.get(h);
            if (obj != null && obj.pathLink == null) {
                unnamed++;
            }
        }
        System.out.println(unnamed + " unnamed");
    }

    private void dump(String dumpName) {
        try {
            PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(dumpName)));
            for (int h = 0; h < objs.size(); h++) {
                MyObject obj = objs.get(h);
                if (obj == null) {
                    continue;
                }
                out.println(obj.toString());
                for (Iterator<Link> it = obj.linksFrom.iterator(); it.hasNext();) {
                    Link l = it.next();
                    if (l.to == null) {
                        continue;
                    }
                    out.println("\t" + l.field.name + "\t" + (l.to != null ? l.to.toString() : "null"));
                }
                out.println("\t-");
                for (Iterator<Link> it = obj.linksTo.iterator(); it.hasNext();) {
                    Link l = it.next();
                    if (l == obj.pathLink) {
                        continue;
                    }
                    out.println("\t" + (l.from != null ? l.from.path() + "." : "") + l.field.name);
                }
                out.println();
            }
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class MyObject {

    MyClass cls;
    int id;
    Link pathLink;
    ArrayList<Link> linksFrom = new ArrayList<Link>();
    ArrayList<Link> linksTo = new ArrayList<Link>(1);

    void setClass(MyClass cls) {
        this.cls = cls;
    }

    boolean isSingleOwned() {
        return linksTo.size() <= 1;
    }

    public String path() {
        if (pathLink == null) {
            return (isSingleOwned() ? "!" : "*") + id;
        } else if (pathLink.from == null) {
            return pathLink.field.name;
        } else {
            return pathLink.from.path() + "." + pathLink.field.name;
        }
    }

    public String toString() {
        return path() + "(" + (isSingleOwned() ? "!" : "*") + id + ") " + cls.className;
    }
}

class MyString extends MyObject {

    String value;

    MyString(String value) {
        this.value = value;
    }

    public String toString() {
        return super.toString() + " \"" + value + "\"";
    }
}

class MyClass extends MyObject {

    static final int NORMAL = 0;
    static final int STRING = 1;
    static final int ARRAY = 2;
    static final int MAP = 3;
    static final int CLASS = 4;
    String className;
    int mode;
    MyField[] staticFields;
    MyField[] fields;
    MyField classField;

    MyClass(String className, int mode, List<String> staticFieldList, List<String> fieldList) {
        this.className = className;
        classField = new MyField(0, MyField.CLASS, className);
        this.mode = mode;
        staticFields = new MyField[staticFieldList.size()];
        for (int i = 0; i < staticFields.length; i++) {
            staticFields[i] = new MyField(i, MyField.STATICFIELD, staticFieldList.get(i));
        }
        fields = new MyField[fieldList.size()];
        for (int i = 0; i < fields.length; i++) {
            String fieldName = fieldList.get(i);
            MyField f = new MyField(i, MyField.FIELD, fieldName);
            if (fieldName.equals("referent") && className.startsWith("java.lang.ref.")) {
                f.referent = true;
            }
            fields[i] = f;
        }
    }

    public String toString() {
        String modeString = "";
        switch (mode) {
            case STRING:
                modeString = " (STRING)";
                break;
            case ARRAY:
                modeString = " (ARRAY)";
                break;
            case MAP:
                modeString = " (MAP)";
                break;
            case CLASS:
                modeString = " (CLASS)";
                break;
        }
        return super.toString() + modeString;
    }
}

class MyField {

    static final int FIELD = 0;
    static final int STATICFIELD = 1;
    static final int ELEM = 2;
    static final int KEY = 3;
    static final int CLASS = 5;
    int index;
    int mode;
    String name;
    boolean referent;
    static final ArrayList<MyField> elems = new ArrayList<MyField>();
    static final ArrayList<MyField> keys = new ArrayList<MyField>();

    MyField(int index, int mode, String name) {
        this.index = index;
        this.mode = mode;
        this.name = name;
    }

    static MyField getElem(int index) {
        while (elems.size() <= index) {
            elems.add(new MyField(elems.size(), ELEM, elems.size() + ""));
        }
        return elems.get(index);
    }

    static MyField getKey(int index) {
        while (keys.size() <= index) {
            keys.add(new MyField(keys.size(), KEY, keys.size() + "k"));
        }
        return keys.get(index);
    }
}

class Link {

    MyObject from;
    MyField field;
    MyObject to;

    Link(MyObject from, MyField field, MyObject to) {
        this.from = from;
        this.field = field;
        this.to = to;
        if (from != null) {
            from.linksFrom.add(this);
        }
        if (to != null) {
            to.linksTo.add(this);
        }
    }
}
