package com.srbenoit.filter;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import com.srbenoit.ui.UIUtilities;

/**
 * A renderer that draws a graphical representation of a filter.
 */
public class FilterRenderer {

    /** insets between filter box and inner content */
    public final static int INSETS = 4;

    /** length of stub */
    public final static int STUB = 2;

    /** the shadow color */
    private static final Color SHADOW;

    /** the shadow color */
    private static final Color HIGHLIGHT;

    /** the shadow color */
    private static final Color BACKGROUND;

    /** the shadow color */
    private static final Color SELECTED;

    /** the shadow color */
    private static final Color RUNNING;

    /** the shadow color */
    private static final Color COMPLETED;

    /** the font to use to render the filter label */
    private final Font font;

    /** the filter to render */
    private final AbstractFilter filter;

    /** the bounding rectangle for the filter */
    private final Rectangle bounds;

    /** the X coordinate (relative to left edge) of each input */
    private final int[] inputX;

    /** the Y coordinate (relative to top edge) of each output */
    private final int[] outputY;

    static {
        BACKGROUND = new Color(200, 200, 180);
        HIGHLIGHT = new Color(255, 255, 220);
        SHADOW = new Color(150, 150, 100);

        SELECTED = new Color(220, 220, 200);
        RUNNING = new Color(200, 200, 255);
        COMPLETED = new Color(200, 255, 180);
    }

    /**
     * Constructs a new <code>FilterRenderer</code>.
     *
     * @param  theFilter  the filter to render
     */
    public FilterRenderer(final AbstractFilter theFilter) {

        Graphics grx;
        FontMetrics met;
        int width;
        int boxHeight;
        int perOut;
        int outHeight;
        int height;

        this.filter = theFilter;
        this.font = new Font("SansSerif", Font.PLAIN, 12);

        this.inputX = new int[theFilter.getNumInputs()];
        this.outputY = new int[theFilter.getNumOutputs()];

        grx = UIUtilities.getGraphics();
        grx.setFont(this.font);
        met = grx.getFontMetrics();

        width = 2 + INSETS + met.stringWidth(theFilter.getName()) + INSETS + 2;

        if (theFilter.getNumInputs() > 0) {

            for (int i = 0; i < theFilter.getNumInputs(); i++) {
                this.inputX[i] = width + 12 + (28 * i);
            }

            width += 28 * theFilter.getNumInputs();
        }

        if (theFilter.getNumOutputs() > 0) {
            width += 28;
        }

        boxHeight = 2 + INSETS + met.getAscent() + met.getDescent() + INSETS + 2;

        perOut = ((met.getHeight() > 16) ? met.getHeight() : 16) + 6;
        outHeight = (perOut * theFilter.getNumOutputs()) + 6;

        height = (boxHeight > outHeight) ? boxHeight : outHeight;

        for (int i = 0; i < theFilter.getNumOutputs(); i++) {
            this.outputY[i] = (perOut * (i + 1)) - 4;
        }

        if (theFilter.getNumInputs() > 0) {
            height += STUB;

            for (int i = 0; i < theFilter.getNumOutputs(); i++) {
                this.outputY[i] += STUB;
            }
        }

        this.bounds = new Rectangle(0, 0, width, height);
    }

    /**
     * Sets the location of the filter's rendered box.
     *
     * @param  xPos  the X coordinate of the left edge
     * @param  yPos  the Y coordinate of the top edge
     */
    public void setLocation(final int xPos, final int yPos) {

        this.bounds.x = xPos;
        this.bounds.y = yPos;
    }

    /**
     * Gets a copy of the bounding rectangle for the filter.
     *
     * @return  the copy of the bounding rectangle (clients are free to alter this rectangle, but
     *          changes will not affect the bounds of the filter)
     */
    public Rectangle getBounds() {

        return new Rectangle(this.bounds);
    }

    /**
     * Gets the X coordinate, relative to the left edge of the bounding rectangle, of a particular
     * input's stub.
     *
     * @param   index  the index of the input
     * @return  the X coordinate
     */
    public int getInputX(final int index) {

        return this.inputX[index];
    }

    /**
     * Gets the Y coordinate, relative to the top edge of the bounding rectangle, of a particular
     * output's stub.
     *
     * @param   index  the index of the output
     * @return  the Y coordinate
     */
    public int getOutputY(final int index) {

        return this.outputY[index];
    }

    /**
     * Draws the filter.
     *
     * @param  grx  the <code>Graphics</code> on which to draw
     */
    public void draw(final Graphics grx) {

        int left;
        int right;
        int top;
        int bottom;
        int xPos;
        int yPos;
        FontMetrics met;

        grx.setFont(this.font);
        met = grx.getFontMetrics();

        left = this.bounds.x;
        right = this.bounds.x + this.bounds.width - 1;

        if (this.filter.getNumOutputs() > 0) {
            right -= 28;
        }

        top = this.bounds.y + STUB;
        bottom = this.bounds.y + this.bounds.height - 1;

        if (filter.isSelected()) {
            grx.setColor(SELECTED);
        } else {

            switch (filter.getState()) {

            case RUNNING:
                grx.setColor(RUNNING);
                break;

            case COMPLETED:
                grx.setColor(COMPLETED);
                break;

            default:
                grx.setColor(BACKGROUND);
                break;
            }
        }

        grx.fillRect(left, top, right - left, bottom - top);

        grx.setColor(SHADOW);
        grx.drawLine(left + 2, bottom - 1, right - 1, bottom - 1);
        grx.drawLine(right - 1, top + 2, right - 1, bottom - 1);

        grx.setColor(HIGHLIGHT);
        grx.drawLine(left + 1, top + 1, right - 2, top + 1);
        grx.drawLine(left + 1, top + 1, left + 1, bottom - 2);

        if (filter.isProvisional()) {
            grx.setColor(Color.GRAY);
        } else {
            grx.setColor(Color.BLACK);
        }

        grx.drawRect(left, top, right - left, bottom - top);

        grx.drawString(this.filter.getName(), left + INSETS, top + INSETS + met.getAscent());

        // Draw the inputs
        yPos = top + INSETS;

        for (int i = 0; i < this.filter.getNumInputs(); i++) {
            xPos = bounds.x + this.getInputX(i);
            grx.drawImage(this.filter.getInputFormat(i).getIcon(), xPos - 12, yPos, null);
            grx.drawLine(xPos, this.bounds.y, xPos, top);
        }

        // Draw the outputs
        xPos = right + 4;
        yPos = this.bounds.y;

        if (this.filter.getNumInputs() > 0) {
            yPos += STUB;
        }

        for (int i = 0; i < this.filter.getNumOutputs(); i++) {
            yPos = bounds.y + this.getOutputY(i);
            grx.drawImage(this.filter.getOutputFormat(i).getIcon(), xPos, yPos - 18, null);
            grx.drawLine(right, yPos, this.bounds.x + this.bounds.width, yPos);
        }
    }
}
