/*
 * Copyright (c) 2005, the JUNG Project and the Regents of the University of
 * California All rights reserved.
 *
 * This software is open-source under the BSD license; see either "license.txt"
 * or http://jung.sourceforge.net/license.txt for a description.
 *
 * Created on Jun 17, 2005
 */

package edu.uci.ics.jung.visualization;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;


/**
 * Provides factory methods that, given a BufferedImage, an Image,
 * or the fileName of an image, will return a java.awt.Shape that
 * is the contiguous traced outline of the opaque part of the image.
 * This could be used to define an image for use in a Vertex, where
 * the shape used for picking and edge-arrow placement follows the
 * opaque part of an image that has a transparent background.
 * The methods try to detect lines in order to minimize points
 * in the path
 * 
 * @author Tom Nelson - RABA Technologies
 *
 * 
 */
public class PivotingImageShaper {
    
    /**
     * the number of pixels to skip while sampling the
     * images edges
     */
    static int sample = 1;
    /**
     * the first x coordinate of the shape. Used to discern
     * when we are done 
     */
    static int firstx = 0;
    
    public static Shape getShape(String fileName) {
        return getShape(fileName, Integer.MAX_VALUE);
    }
    public static Shape getShape(String fileName, int max) {
        BufferedImage image = null;
        try {
            image = ImageIO.read(FourPassImageShaper.class.getResource(fileName));
        } catch(IOException ex) {
            ex.printStackTrace();
        }
        return getShape(image, max);
    }
    
    /**
     * Given an image, possibly with a transparent background, return
     * the Shape of the opaque part of the image
     * @param image
     * @return the Shape
     */
    public static Shape getShape(Image image) {
        return getShape(image, Integer.MAX_VALUE);
    }
    public static Shape getShape(Image image, int max) {
        BufferedImage bi = 
            new BufferedImage(image.getWidth(null), image.getHeight(null), 
                    BufferedImage.TYPE_INT_ARGB);
        Graphics g = bi.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return getShape(bi, max);
    }
    
    /**
     * Given an image, possibly with a transparent background, return
     * the Shape of the opaque part of the image
     * @param image
     * @return the Shape
     */
    public static Shape getShape(BufferedImage image, int max) {
        
        float width = image.getWidth();
        float height = image.getHeight();
        if(width > max || height > max) {
            BufferedImage smaller = 
                new BufferedImage(max, max, BufferedImage.TYPE_INT_ARGB);
            Graphics g = smaller.createGraphics();
            AffineTransform at = AffineTransform.getScaleInstance(max/width,max/height);
            AffineTransform back = AffineTransform.getScaleInstance(width/max,height/max);
            Graphics2D g2 = (Graphics2D)g;
            g2.drawImage(image, at, null);
            g2.dispose();
            return back.createTransformedShape(getShape(smaller));
        } else {
            return getShape(image);
        }
    }
    
    /**
     * Given an image, possibly with a transparent background, return
     * the Shape of the opaque part of the image
     * @param image
     * @return the Shape
     */
    public static Shape getShape(BufferedImage image) {
        firstx = 0;
        return leftEdge(image, new GeneralPath());
    }
    
    private static Point2D detectLine(Point2D p1, Point2D p2, Point2D p, 
            Line2D line, GeneralPath path) {
        if(p2 == null) {
            p2 = p;
            line.setLine(p1,p2);
        }
        // check for line
        else if(line.ptLineDistSq(p) < 1) { // its on the line
            // make it p2
            p2.setLocation(p);
        } else { // its not on the current line
            p1.setLocation(p2);
            p2.setLocation(p);
            line.setLine(p1,p2);
            path.lineTo((float)p1.getX(), (float)p1.getY());
        }
        return p2;
    }
    /**
     * trace the left side of the image
     * @param image
     * @param path
     * @return
     */
    private static Shape leftEdge(BufferedImage image, GeneralPath path) {
        int lastj = 0;
        Point2D p1 = null;
        Point2D p2 = null;
        Line2D line = new Line2D.Float();
        for(int i=0; i<image.getHeight(); i+=sample) {
            boolean aPointExistsOnThisLine = false;
            // go until we reach an opaque point, then stop
            for(int j=0; j<image.getWidth(); j+=sample) {
                if((image.getRGB(j,i) & 0xff000000) != 0) {
                    // this is a point I want
                    Point2D p = new Point2D.Float(j,i);
                    aPointExistsOnThisLine = true;
                    if(path.getCurrentPoint() != null) {
                        // this is a continuation of a path
                        p2 = detectLine(p1,p2,p,line,path);
                    } else {
                        // this is the first point in the path
                        path.moveTo(j,i);
                        firstx = j;
                        p1 = p;
                    }
                    lastj = j;
                    break;
                }
            }
            if(aPointExistsOnThisLine == false) {
                break;
            }
        }
        return bottomEdge(image, path, lastj);
    }
    
    /**
     * trace the bottom of the image
     * @param image
     * @param path
     * @param start
     * @return
     */
    private static Shape bottomEdge(BufferedImage image, GeneralPath path, int start) {
        int lastj = 0;
        Point2D p1 = path.getCurrentPoint();
        Point2D p2 = null;
        Line2D line = new Line2D.Float();
        for(int i=start; i<image.getWidth(); i+=sample) {
            boolean aPointExistsOnThisLine = false;
            for(int j=image.getHeight()-1; j>=0; j-=sample) {
                if((image.getRGB(i,j) & 0xff000000) != 0) {
                    // this is a point I want
                    Point2D p = new Point2D.Float(i,j);
                    aPointExistsOnThisLine = true;
                    p2 = detectLine(p1,p2,p,line,path);
                    lastj = j;
                    break;
                }
            }
            if(aPointExistsOnThisLine == false) {
                break;
            }
        }
        return rightEdge(image, path, lastj);
    }
    
    /**
     * trace the right side of the image
     * @param image
     * @param path
     * @param start
     * @return
     */
    private static Shape rightEdge(BufferedImage image, GeneralPath path, int start) {
        int lastj = 0;
        Point2D p1 = path.getCurrentPoint();
        Point2D p2 = null;
        Line2D line = new Line2D.Float();
        for(int i=start; i>=0; i-=sample) {
            boolean aPointExistsOnThisLine = false;

            for(int j=image.getWidth()-1; j>=0; j-=sample) {
                if((image.getRGB(j,i) & 0xff000000) != 0) {
                    // this is a point I want
                    Point2D p = new Point2D.Float(j,i);
                    aPointExistsOnThisLine = true;
                    p2 = detectLine(p1,p2,p,line,path);
                    lastj=j;
                    break;
                }
            }
            if(aPointExistsOnThisLine == false) {
                break;
            }
        }
        return topEdge(image, path, lastj);
    }
    
    /**
     * trace the top of the image
     * @param image
     * @param path
     * @param start
     * @return
     */
    private static Shape topEdge(BufferedImage image, GeneralPath path, int start) {
        Point2D p1 = path.getCurrentPoint();
        Point2D p2 = null;
        Line2D line = new Line2D.Float();
        for(int i=start; i>=firstx; i-=sample) {
            boolean aPointExistsOnThisLine = false;
            for(int j=0; j<image.getHeight(); j+=sample) {
                if((image.getRGB(i,j) & 0xff000000) != 0) {
                    // this is a point I want
                    Point2D p = new Point2D.Float(i,j);
                    aPointExistsOnThisLine = true;
                    p2 = detectLine(p1,p2,p,line,path);
                    break;
                }
            }
            if(aPointExistsOnThisLine == false) {
                break;
            }
        }
        path.closePath();
        return path;
    }
}
