/* Copyright (C) 2005-2011 Fabio Riccardi */

package com.lightcrafts.model.ImageEditor;

import com.lightcrafts.jai.JAIContext;
import com.lightcrafts.jai.LCROIShape;
import com.lightcrafts.jai.utils.Transform;
import com.lightcrafts.model.*;

import com.lightcrafts.mediax.jai.*;
import com.lightcrafts.ui.editor.EditorMode;

import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.image.renderable.ParameterBlock;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;

public class CloneOperationImpl extends BlendedOperation implements CloneOperation {
    public CloneOperationImpl(Rendering rendering) {
        super(rendering, type);
    }

    public EditorMode getPreferredMode() {
        return EditorMode.REGION;
    }

    public boolean neutralDefault() {
        return true;
    }

    public void setRegionInverted(boolean inverted) {
        // Inverted regions have no meaning for the Clone Tool
        // super.setRegionInverted(inverted);
    }

    static final OperationType type = new OperationTypeImpl("Clone");

    static PlanarImage buildCloner(Region region, Rendering rendering, PlanarImage back) {
        PlanarImage image = back;

        Collection contours = region.getContours();
        for (Iterator i = contours.iterator(); i.hasNext();) {
            Contour c = (Contour) i.next();
            // Protect from clone copy bug
            // TODO: Anton fixme
            if (!(c instanceof CloneContour))
                continue;

            CloneContour cloneContour = (CloneContour) c;

            // This is the mask for the clone operation:
            final Contour contour = cloneContour;
            final Point2D translation = contour.getTranslation();

            // And this is the extra point that says where to clone from:
            final Point2D clonePoint = cloneContour.getClonePoint();

            Point2D source = new Point2D.Double(clonePoint.getX() + (translation != null ? translation.getX() : 0),
                                                clonePoint.getY() + (translation != null ? translation.getY() : 0));

            source = rendering.getInputTransform().transform(source, null);

            final Rectangle bounds = contour.getOuterShape().getBounds();
            Point2D target = new Point2D.Double(bounds.getCenterX() + (translation != null ? translation.getX() : 0),
                                                bounds.getCenterY() + (translation != null ? translation.getY() : 0));
            target = rendering.getInputTransform().transform(target, null);

            int dx = (int) (target.getX() - source.getX());
            int dy = (int) (target.getY() - source.getY());

            ParameterBlock pb = new ParameterBlock();
            pb.addSource(back);
            pb.add((float) dx);
            pb.add((float) dy);
            RenderedOp translated = JAI.create("Translate", pb, JAIContext.noCacheHint);

            pb = new ParameterBlock();
            pb.addSource(translated);
            pb.add((int) (dx > 0 ? dx : 0));
            pb.add((int) (dx < 0 ? -dx : 0));
            pb.add((int) (dy > 0 ? dy : 0));
            pb.add((int) (dy < 0 ? -dy : 0));
            pb.add(BorderExtender.createInstance(BorderExtender.BORDER_ZERO));
            RenderedOp border = JAI.create("Border", pb, JAIContext.noCacheHint);

            pb = new ParameterBlock();
            pb.addSource(border);
            pb.add((float) back.getMinX());
            pb.add((float) back.getMinY());
            pb.add((float) back.getWidth());
            pb.add((float) back.getHeight());
            RenderedOp crop = JAI.create("Crop", pb, JAIContext.noCacheHint);

            // Format retiles the image
            // TODO: this needs better understanding, can we just specify the layout for Crop?
            pb = new ParameterBlock();
            pb.addSource(crop);
            pb.add(back.getSampleModel().getDataType());
            RenderingHints formatHints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, new ImageLayout(back));
            formatHints.add(JAIContext.noCacheHint);
            RenderedOp formatted = JAI.create("Format", pb, formatHints);

            Region r = new Region() {
                public Collection getContours() {
                    return Collections.singleton(contour);
                }

                public Shape getOuterShape() {
                    return contour.getOuterShape();
                }

                public float getWidth() {
                    return contour.getWidth();
                }

                public Point2D getTranslation() {
                    return contour.getTranslation();
                }
            };

            LCROIShape mask = new LCROIShape(r, rendering.getInputTransform());

            pb = new ParameterBlock();
            pb.addSource(formatted);
            pb.addSource(image);
            pb.add("Normal");
            pb.add(new Double(1));
            pb.add(mask);

            // RenderingHints blendHints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, new ImageLayout(back));
            // NOTE: I guess that it is more efficient to cache the different cloned areas...
            // blendHints.add(JAIContext.noCacheHint);
            image = JAI.create("Blend", pb, null);
        }

        return image;
    }

    class Cloner extends BlendedTransform {
        Cloner(PlanarImage source) {
            super(source);
        }

        public PlanarImage setFront() {
            if (getRegion() != null)
                return buildCloner(getRegion(), rendering, back);
            else
                return back;
        }
    }

    protected void updateOp(Transform op) {
        op.update();
    }

    protected BlendedTransform createBlendedOp(PlanarImage source) {
        return new Cloner(source);
    }

    public OperationType getType() {
        return type;
    }
}
