/***
 * ASM examples: examples showing how ASM can be used
 * Copyright (c) 2000-2005 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.objectweb.asm.depend;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.imageio.ImageIO;

import org.objectweb.asm.ClassReader;

/**
 * DependencyTracker
 * 
 * @author Eugene Kuleshov
 * 
 * @see http://www.onjava.com/pub/a/onjava/2005/08/17/asm3.html
 */
public class DependencyTracker {

    private static final int CELL_PAD = 1;

    private static final int GRID_SIZE = 10;

    private static final int CELLS_SIZE = 8;

    private static final int LABEL_WIDTH = 200;

    private static final String LABEL_FONT = "Tahoma-9";

    public static void main(String[] args) throws IOException {
        DependencyVisitor v = new DependencyVisitor();

        ZipFile f = new ZipFile(args[0]);

        long l1 = System.currentTimeMillis();
        Enumeration< ? extends ZipEntry> en = f.entries();
        while (en.hasMoreElements()) {
            ZipEntry e = en.nextElement();
            String name = e.getName();
            if (name.endsWith(".class")) {
                new ClassReader(f.getInputStream(e)).accept(v, false);
            }
        }
        long l2 = System.currentTimeMillis();

        Map<String, Map<String, Integer>> globals = v.getGlobals();
        Set<String> jarPackages = globals.keySet();
        Set<String> classPackages = v.getPackages();
        int size = classPackages.size();
        System.err.println("time: " + (l2 - l1) / 1000f + "  " + size);

        String[] jarNames = jarPackages.toArray(new String[jarPackages.size()]);
        String[] classNames = classPackages.toArray(new String[classPackages.size()]);
        Arrays.sort(jarNames);
        Arrays.sort(classNames);

        buildDiagram(jarNames, classNames, globals);
    }

    public static void buildDiagram(
        String[] jarNames,
        String[] classNames,
        Map<String, Map<String, Integer>> globals) throws IOException
    {
        // normalize
        int max = 0;
        for (int i = 0; i < classNames.length; i++) {
            Map<String, Integer> map = globals.get(classNames[i]);
            if (map == null)
                continue;
            Integer maxCount = Collections.max(map.values());
            if (maxCount > max) {
                max = maxCount;
            }
        }

        List<Color> colors = new ArrayList<Color>();
        for (int i = LABEL_WIDTH; i >= 0; i--) {
            colors.add(new Color(i, i, 255));
        }
        for (int i = 255; i >= 128; i--) {
            colors.add(new Color(0, 0, i));
        }
        int maxcolor = colors.size() - 1;

        int heigh = CELL_PAD + (CELLS_SIZE + CELL_PAD) * classNames.length;
        int width = CELL_PAD + (CELLS_SIZE + CELL_PAD) * jarNames.length;

        BufferedImage img = new BufferedImage(width + LABEL_WIDTH, heigh
                + LABEL_WIDTH, BufferedImage.TYPE_INT_RGB);

        Graphics2D g = img.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, width + LABEL_WIDTH, heigh + LABEL_WIDTH);

        // draw lines
        g.setColor(Color.LIGHT_GRAY);
        for (int y = GRID_SIZE; y < classNames.length; y += GRID_SIZE) {
            g.drawLine(0, y * (CELLS_SIZE + CELL_PAD), width, y
                    * (CELLS_SIZE + CELL_PAD));
        }
        for (int x = GRID_SIZE; x < jarNames.length; x += GRID_SIZE) {
            g.drawLine(x * (CELLS_SIZE + CELL_PAD), 0, x
                    * (CELLS_SIZE + CELL_PAD), heigh);
        }

        // draw diagram
        for (int y = 0; y < classNames.length; y++) {
            // System.err.println( y+" : "+classNames[ y]);

            for (int x = 0; x < jarNames.length; x++) {
                Map<String, Integer> map = globals.get(jarNames[x]);

                Integer count = map == null ? null : map.get(classNames[y]);
                if (count != null) {
                    int b = (int) ((float) count * maxcolor / max);

                    g.setColor(colors.get(b));
                    g.fillRect(CELL_PAD + (x * (CELLS_SIZE + CELL_PAD)),
                            CELL_PAD + (y * (CELLS_SIZE + CELL_PAD)),
                            CELLS_SIZE,
                            CELLS_SIZE);
                }

            }

        }

        // draw labels
        Font f = Font.decode(LABEL_FONT);
        g.setFont(f);
        // g.setColor( new Color( 70, 70, 255));
        g.setColor(Color.GRAY);

        for (int y = 0; y < classNames.length; y++) {
            AffineTransform trans = g.getTransform();
            g.transform(AffineTransform.getTranslateInstance(CELL_PAD * 2
                    + width, CELLS_SIZE + y * (CELLS_SIZE + CELL_PAD)));
            g.transform(AffineTransform.getRotateInstance(Math.PI / 12));
            g.drawString(classNames[y], 0, 0);
            g.setTransform(trans);
        }

        for (int x = 0; x < jarNames.length; x++) {
            AffineTransform trans = g.getTransform();
            g.transform(AffineTransform.getTranslateInstance(CELL_PAD * 2 + x
                    * (CELLS_SIZE + CELL_PAD), heigh + CELL_PAD * 2));
            g.transform(AffineTransform.getRotateInstance(Math.PI / 2.5));
            g.drawString(jarNames[x], 0, 0);
            g.setTransform(trans);
        }

        FileOutputStream fos = new FileOutputStream("test.png");
        ImageIO.write(img, "png", fos);
        fos.flush();
        fos.close();
    }
}
