/*
 * Copyright (c) 2002-2007 TeamDev Ltd. All rights reserved.
 *
 * Use is subject to license terms.
 *
 * The complete licence text can be found at
 * http://www.teamdev.com/winpack/license.jsf
 */
package teamdev.jxcapture.samples.demo;

import teamdev.jxcapture.*;
import teamdev.jxcapture.controllers.ObjectOnScreenController;
import teamdev.jxcapture.controllers.RegionController;
import teamdev.jxcapture.controllers.SelectionController;
import teamdev.jxcapture.events.CaptureAdapter;
import teamdev.jxcapture.painters.ObjectOnScreenPainter;
import teamdev.jxcapture.painters.RegionPainter;
import teamdev.jxcapture.painters.SelectionPainter;
import teamdev.jxcapture.positioners.Positioner;
import teamdev.jxcapture.positioners.DiagonalPositioner;
import teamdev.jxcapture.toolkit.DialogComponent;
import teamdev.jxcapture.toolkit.Positions;
import teamdev.jxcapture.toolkit.DrawableArea;
import teamdev.jxdesktop.win32.ui.Wnd;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

/**
 * @author Ikryanov Vladimir
 */
public class CaptureOperations {
    private static CaptureOperations instance;

    private ApplicationSettings appSettings = ApplicationSettings.getInstance();
    private CaptureSettings captureSettings = CaptureSettings.getInstance();

    private CaptureListener listener = new CaptureListener();
    private List imageViewers = new LinkedList();
    private List listeners = new LinkedList();

    private boolean busy;

    private CaptureOperations() {
    }

    public static CaptureOperations getInstance() {
        return instance == null ? instance = new CaptureOperations() : instance;
    }

    public void activeWindowCapture() {
        prepareOperation();
        CaptureOperation captureOperation = new ActiveWindowCapture();
        captureOperation.addCaptureListener(listener);
        waitBeforeCapture(captureOperation);
    }

    public void objectCapture() {
        prepareOperation();
        SelectionPainter painter = new ObjectOnScreenPainter();
        SelectionController controller = new ObjectOnScreenController(painter);
        CaptureOperation captureOperation = new ObjectOnScreenCapture(controller);
        captureOperation.addCaptureListener(listener);
        waitBeforeCapture(captureOperation);
    }

    public void regionCapture() {
        prepareOperation();
        SelectionPainter painter = new RegionPainter();
        SelectionController controller = new RegionController(painter);
        DrawableCapture captureOperation = new RegionCapture(controller);
        DrawableArea drawableArea = captureOperation.getDrawableArea();
        RegionCaptureDialog captureDialog = new RegionCaptureDialog(drawableArea, (RegionPainter) painter);
        drawableArea.setDialogComponent(captureDialog);
        captureOperation.addCaptureListener(listener);
        waitBeforeCapture(captureOperation);
    }

    public void desktopCapture() {
        prepareOperation();
        CaptureOperation captureOperation = new DesktopCapture();
        captureOperation.addCaptureListener(listener);
        waitBeforeCapture(captureOperation);
    }

    public void addCaptureOperationsListener(CaptureOperationsListener listener) {
        if (!listeners.contains(listener)) {
            listeners.add(listener);
        }
    }

    public void removeCaptureOperationsListener(CaptureOperationsListener listener) {
        listeners.remove(listener);
    }

    public boolean isBusy() {
        return busy;
    }

    public List getActiveViewers() {
        return imageViewers;
    }

    private void fireOperationBegin() {
        busy = true;
        for (int i = 0; i < listeners.size(); i++) {
            ((CaptureOperationsListener) listeners.get(i)).operationBegin();
        }
    }

    private void fireOperationComplete(boolean isCanceled) {
        busy = false;
        for (int i = 0; i < listeners.size(); i++) {
            ((CaptureOperationsListener) listeners.get(i)).operationComplete(isCanceled);
        }
    }

    private void prepareOperation() {
        fireOperationBegin();
        updateSettings();
        restorePrevWindow();
    }

    private void updateSettings() {
        captureSettings.setIncludeCursor(appSettings.isIncludeCursor());
        captureSettings.setCaptureTransparentWindow(appSettings.isCaptureTransparentWindows());
    }

    // TODO: [Ikryanov] Make this functionality cross-platform. Instead of using Wnd we need to use UIElement
    private void restorePrevWindow() {
        List list = teamdev.jxdesktop.win32.process.Process.getApplicationWindows();
        if (!list.isEmpty()) {
            Wnd wnd = (Wnd) list.get(1);
            wnd.setForeground();
        }
    }

    private void waitBeforeCapture(final CaptureOperation captureOperation) {
        if (appSettings.isDelayBeforeCapture()) {
            int delayTime = appSettings.getDelayTime() / 1000;
            TimerDialog timerDialog = new TimerDialog(delayTime);
            timerDialog.show(new TimerDialog.TimerListener() {
                public void complete(boolean isCanceled) {
                    if(!isCanceled) {
                        captureOperation.execute();
                    } else {
                        fireOperationComplete(isCanceled);
                    }
                }
            });
        } else {
            captureOperation.execute();
        }
    }

    private void saveImage(final BufferedImage image) {
        if (appSettings.isCaptureToViewer()) {
            final ImageViewer viewer = new ImageViewer();
            viewer.addWindowListener(new WindowAdapter() {
                public void windowClosed(WindowEvent e) {
                    imageViewers.remove(viewer);
                }
            });
            viewer.setImage(image);
            viewer.setVisible(true);
            viewer.toFront();

            imageViewers.add(viewer);
        } else if (appSettings.isCaptureToFile()) {
            File file = appSettings.isAutoSave() ? getFileBySettings() : getFileByChooser();
            try {
                if (file != null) {
                    String fileName = file.getName();
                    int index = fileName.indexOf(".");
                    if (index > 0) {
                        String format = fileName.substring(index + 1);
                        /**
                         * BMP and GIF image format support only since JDK 1.5
                         */
                        ImageIO.write(image, format, file);
                    }
                }
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        } else if (appSettings.isCaptureToClipboard()) {
            copyToClipboard(image);
        }
    }

    private File getFileByChooser() {
        ImageFileChooser fileChooser = new ImageFileChooser();
        return fileChooser.saveImageFile();
    }

    private File getFileBySettings() {
        // retrieve format
        String[] formats = appSettings.getImageFormats();
        String format = formats[appSettings.getImageFormatIndex()];

        // retrieve output folder
        String outputFolder = appSettings.getOutputFolder();
        File outputDirectory = new File(outputFolder);
        if (!outputDirectory.exists()) {
            outputDirectory.mkdir();
        }

        // creates the file name
        String templateName = appSettings.getTemplateFileName();
        int number = appSettings.getTemplateNumber();
        appSettings.setTemplateNumber(number + 1);
        String result = templateName.replaceFirst("#", Integer.toString(number));
        String fileName = outputFolder + File.separator + result + "." + format;

        return new File(fileName);
    }

    public void copyToClipboard(BufferedImage image) {
        int width = image.getWidth();
        int height = image.getHeight();
        BufferedImage resultImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = resultImage.createGraphics();
        graphics.drawImage(image, 0, 0, null);
        ImageTransferData transferData = new ImageTransferData(resultImage);
        DemoClipboardOwner clipboardOwner = new DemoClipboardOwner();
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents(transferData, clipboardOwner);
    }

    public static interface CaptureOperationsListener {
        public void operationBegin();
        public void operationComplete(boolean isCanceled);
    }

    private class CaptureListener extends CaptureAdapter {
        public void cancel() {
            fireOperationComplete(true);
        }

        public void complete(BufferedImage image) {
            saveImage(image);
            fireOperationComplete(false);
        }
    }

    private class RegionCaptureDialog implements DialogComponent {

        private HelperComponent helperDialog;
        private DrawableArea drawableArea;
        private RegionPainter painter;

        public RegionCaptureDialog(DrawableArea drawableArea, RegionPainter painter) {
            this.drawableArea = drawableArea;
            this.painter = painter;
        }

        public JComponent createComponent() {
            helperDialog = new HelperComponent(drawableArea.getBackgroundImage());
            Component component = drawableArea.getComponent();
            component.addMouseMotionListener(new MouseMotionListener() {
                public void mouseDragged(MouseEvent e) {
                    updateDialogData(e.getPoint());
                }

                public void mouseMoved(MouseEvent e) {
                    updateDialogData(e.getPoint());
                }

                public void updateDialogData(Point point) {
                    Rectangle rectangle = painter.getCurrentSelectedRegion();
                    helperDialog.updateComponent(point, rectangle);
                }
            });
            return helperDialog;
        }

        public int getInvisibleBorder() {
            return 10;
        }

        public int getLocation() {
            return Positions.TOP_LEFT;
        }

        public Positioner getPositioner() {
            return new DiagonalPositioner();
        }
    }

    private class DemoClipboardOwner implements ClipboardOwner {
        public void lostOwnership(Clipboard clipboard, Transferable contents) {
        }
    }

    private class ImageTransferData implements Transferable {
        private Image data;

        public ImageTransferData(Image data) {
            this.data = data;
        }

        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[]{DataFlavor.imageFlavor};
        }

        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return flavor.equals(DataFlavor.imageFlavor);
        }

        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
            if (DataFlavor.imageFlavor.equals(flavor)) {
                return data;
            } else {
                throw new UnsupportedFlavorException(flavor);
            }
        }
    }
}
