/*
 * Copyright 2020 Laszlo Balazs-Csiki and Contributors
 *
 * This file is part of Pixelitor. Pixelitor is free software: you
 * can redistribute it and/or modify it under the terms of the GNU
 * General Public License, version 3 as published by the Free
 * Software Foundation.
 *
 * Pixelitor 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 Pixelitor. If not, see <http://www.gnu.org/licenses/>.
 */

package pixelitor.guitest;

import org.assertj.swing.edt.GuiActionRunnable;
import org.assertj.swing.edt.GuiActionRunner;
import pixelitor.Canvas;
import pixelitor.Composition;
import pixelitor.OpenImages;
import pixelitor.gui.View;
import pixelitor.guides.Guides;
import pixelitor.history.History;
import pixelitor.layers.Layer;
import pixelitor.menus.view.ZoomLevel;
import pixelitor.selection.Selection;
import pixelitor.selection.ShapeCombination;
import pixelitor.tools.Tool;
import pixelitor.tools.Tools;
import pixelitor.tools.pen.Path;
import pixelitor.tools.pen.PathTransformer;
import pixelitor.tools.pen.PenTool;
import pixelitor.tools.shapes.ShapesToolState;
import pixelitor.tools.transform.TransformBox;
import pixelitor.tools.util.DraggablePoint;
import pixelitor.utils.test.Assertions;
import pixelitor.utils.test.Events;

import java.awt.Point;
import java.util.concurrent.Callable;
import java.util.function.Function;

import static pixelitor.assertions.PixelitorAssertions.assertThat;

/**
 * Utility methods to execute queries, actions and assertions
 * on the Event Dispatch Thread.
 */
public class EDT {
    private EDT() {
    }

    public static <T> T call(Callable<T> callable) {
        return GuiActionRunner.execute(callable);
    }

    public static void run(GuiActionRunnable runnable) {
        GuiActionRunner.execute(runnable);
    }

    public static View getActiveView() {
        return call(OpenImages::getActiveView);
    }

    public static Composition getComp() {
        return call(OpenImages::getActiveComp);
    }

    public static Canvas getCanvas() {
        return call(() -> OpenImages.getActiveComp().getCanvas());
    }

    /**
     * Returns the given property of the active composition
     */
    public static <T> T active(Function<Composition, ? extends T> fun) {
        return call(() -> fun.apply(OpenImages.getActiveComp()));
    }

    public static <T> T activeTool(Function<Tool, ? extends T> fun) {
        return call(() -> fun.apply(Tools.getCurrent()));
    }

    public static Selection getActiveSelection() {
        return call(OpenImages::getActiveSelection);
    }

    public static Guides getGuides() {
        return active(Composition::getGuides);
    }

    public static Layer getActiveLayer() {
        return call(OpenImages::getActiveLayer);
    }

    public static void assertThereIsSelection() {
        if (getActiveSelection() == null) {
            throw new AssertionError("no selection found");
        }
    }

    public static void assertThereIsNoSelection() {
        if (getActiveSelection() != null) {
            throw new AssertionError("selection found");
        }
    }

    public static void assertSelectionInteractionIs(ShapeCombination expected) {
        ShapeCombination actual = call(Tools.SELECTION::getCurrentInteraction);
        if (expected != actual) {
            throw new AssertionError("expected " + expected + ", found " + actual);
        }
    }

    public static void assertActiveToolIs(Tool expected) {
        Tool actual = call(Tools::getCurrent);
        if (actual != expected) {
            throw new AssertionError("Expected " + expected + ", found " + actual);
        }
    }

    public static Path getPenToolPath() {
        return call(() -> PenTool.path);
    }

    public static Point getPenToolBoxPos(int boxIndex,
                                         Function<TransformBox, DraggablePoint> pointFun) {
        return call(() -> {
            PathTransformer mode = (PathTransformer) Tools.PEN.getMode();
            TransformBox box = mode.getBox(boxIndex);
            return pointFun.apply(box).getScreenCoords();
        });
    }

    public static void undo() {
        run(History::undo);
    }

    public static void undo(String edit) {
        run(() -> History.undo(edit));
    }

    public static void redo() {
        run(History::redo);
    }

    public static void redo(String edit) {
        run(() -> History.redo(edit));
    }

    public static void assertEditToBeUndoneNameIs(String expected) {
        run(() -> History.assertEditToBeUndoneNameIs(expected));
    }

    public static void assertEditToBeRedoneNameIs(String expected) {
        run(() -> History.assertEditToBeRedoneNameIs(expected));
    }

    public static void postAssertJEvent(String evt) {
        run(() -> Events.postAssertJEvent(evt));
    }

    public static void increaseZoom() {
        run(() -> OpenImages.getActiveView().increaseZoom());
    }

    public static void decreaseZoom() {
        run(() -> OpenImages.getActiveView().decreaseZoom());
    }

    public static ZoomLevel getZoomLevelOfActive() {
        return call(() -> OpenImages.getActiveView().getZoomLevel());
    }

    public static void assertZoomOfActiveIs(ZoomLevel expected) {
        run(() -> OpenImages.assertZoomOfActiveIs(expected));
    }

    public static void assertNumOpenImagesIs(int expected) {
        run(() -> OpenImages.assertNumOpenImagesIs(expected));
    }

    public static void assertNumOpenImagesIsAtLeast(int expected) {
        run(() -> OpenImages.assertNumOpenImagesIsAtLeast(expected));
    }

    public static void assertNumLayersIs(int expected) {
        run(() -> Assertions.numLayersIs(expected));
    }

    public static void assertShapesToolStateIs(ShapesToolState expected) {
        ShapesToolState actual = call(Tools.SHAPES::getState);
        if (actual != expected) {
            throw new AssertionError("expected " + expected + ", found " + actual);
        }
    }

    public static void activate(View view) {
        run(() -> OpenImages.setActiveView(view, true));
    }

    /**
     * Returns the given property of the active layer.
     */
    public static <T> T activeLayer(Function<Layer, T> fun) {
        return call(() -> fun.apply(OpenImages.getActiveLayer()));
    }

    public static String activeLayerName() {
        return activeLayer(Layer::getName);
    }

    public static boolean activeLayerIsMaskEditing() {
        return activeLayer(Layer::isMaskEditing);
    }

    public static boolean activeLayerHasMask() {
        return activeLayer(Layer::hasMask);
    }

    public static void assertCanvasSizeIs(int expectedWidth, int expectedHeight) {
        assertThat(active(Composition::getCanvas))
            .hasWidth(expectedWidth)
            .hasHeight(expectedHeight);
    }
}