/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.monkeyrunner.recorder;

import com.android.monkeyrunner.MonkeyDevice;
import com.android.chimpchat.core.IChimpImage;
import com.android.chimpchat.core.IChimpDevice;
import com.android.monkeyrunner.recorder.actions.Action;
import com.android.monkeyrunner.recorder.actions.DragAction;
import com.android.monkeyrunner.recorder.actions.DragAction.Direction;
import com.android.monkeyrunner.recorder.actions.PressAction;
import com.android.monkeyrunner.recorder.actions.TouchAction;
import com.android.monkeyrunner.recorder.actions.TypeAction;
import com.android.monkeyrunner.recorder.actions.WaitAction;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.FileNotFoundException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

/**
 * MainFrame for MonkeyRecorder.
 */
public class MonkeyRecorderFrame extends JFrame {
    private static final Logger LOG =
        Logger.getLogger(MonkeyRecorderFrame.class.getName());

    private final IChimpDevice device;

    private static final long serialVersionUID = 1L;
    private JPanel jContentPane = null;
    private JLabel display = null;
    private JScrollPane historyPanel = null;
    private JPanel actionPanel = null;
    private JButton waitButton = null;
    private JButton pressButton = null;
    private JButton typeButton = null;
    private JButton flingButton = null;
    private JButton exportActionButton = null;

    private JButton refreshButton = null;

    private BufferedImage currentImage;  //  @jve:decl-index=0:
    private BufferedImage scaledImage = new BufferedImage(320, 480,
            BufferedImage.TYPE_INT_ARGB);  //  @jve:decl-index=0:

    private JList historyList;
    private ActionListModel actionListModel;

    private final Timer refreshTimer = new Timer(1000, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            refreshDisplay();  //  @jve:decl-index=0:
        }
    });

    /**
     * This is the default constructor
     */
    public MonkeyRecorderFrame(IChimpDevice device) {
        this.device = device;
        initialize();
    }

    private void initialize() {
        this.setSize(400, 600);
        this.setContentPane(getJContentPane());
        this.setTitle("MonkeyRecorder");

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                refreshDisplay();
            }});
        refreshTimer.start();
    }

    private void refreshDisplay() {
        IChimpImage snapshot = device.takeSnapshot();
        currentImage = snapshot.createBufferedImage();

        Graphics2D g = scaledImage.createGraphics();
        g.drawImage(currentImage, 0, 0,
                scaledImage.getWidth(), scaledImage.getHeight(),
                null);
        g.dispose();

        display.setIcon(new ImageIcon(scaledImage));

        pack();
    }

    /**
     * This method initializes jContentPane
     *
     * @return javax.swing.JPanel
     */
    private JPanel getJContentPane() {
        if (jContentPane == null) {
            display = new JLabel();
            jContentPane = new JPanel();
            jContentPane.setLayout(new BorderLayout());
            jContentPane.add(display, BorderLayout.CENTER);
            jContentPane.add(getHistoryPanel(), BorderLayout.EAST);
            jContentPane.add(getActionPanel(), BorderLayout.NORTH);

            display.setPreferredSize(new Dimension(320, 480));

            display.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent event) {
                    touch(event);
                }
            });
        }
        return jContentPane;
    }

    /**
     * This method initializes historyPanel
     *
     * @return javax.swing.JScrollPane
     */
    private JScrollPane getHistoryPanel() {
        if (historyPanel == null) {
            historyPanel = new JScrollPane();
            historyPanel.getViewport().setView(getHistoryList());
        }
        return historyPanel;
    }

    private JList getHistoryList() {
        if (historyList == null) {
            actionListModel = new ActionListModel();
            historyList = new JList(actionListModel);
        }
        return historyList;
    }

    /**
     * This method initializes actionPanel
     *
     * @return javax.swing.JPanel
     */
    private JPanel getActionPanel() {
        if (actionPanel == null) {
            actionPanel = new JPanel();
            actionPanel.setLayout(new BoxLayout(getActionPanel(), BoxLayout.X_AXIS));
            actionPanel.add(getWaitButton(), null);
            actionPanel.add(getPressButton(), null);
            actionPanel.add(getTypeButton(), null);
            actionPanel.add(getFlingButton(), null);
            actionPanel.add(getExportActionButton(), null);
            actionPanel.add(getRefreshButton(), null);
        }
        return actionPanel;
    }

    /**
     * This method initializes waitButton
     *
     * @return javax.swing.JButton
     */
    private JButton getWaitButton() {
        if (waitButton == null) {
            waitButton = new JButton();
            waitButton.setText("Wait");
            waitButton.addActionListener(new java.awt.event.ActionListener() {
                @Override
                public void actionPerformed(java.awt.event.ActionEvent e) {
                    String howLongStr = JOptionPane.showInputDialog("How many seconds to wait?");
                    if (howLongStr != null) {
                        float howLong = Float.parseFloat(howLongStr);
                        addAction(new WaitAction(howLong));
                    }
                }
            });
        }
        return waitButton;
    }

    /**
     * This method initializes pressButton
     *
     * @return javax.swing.JButton
     */
    private JButton getPressButton() {
        if (pressButton == null) {
            pressButton = new JButton();
            pressButton.setText("Press a Button");
            pressButton.addActionListener(new java.awt.event.ActionListener() {
                @Override
                public void actionPerformed(java.awt.event.ActionEvent e) {
                    JPanel panel = new JPanel();
                    JLabel text = new JLabel("What button to press?");
                    JComboBox keys = new JComboBox(PressAction.KEYS);
                    keys.setEditable(true);
                    JComboBox direction = new JComboBox(PressAction.DOWNUP_FLAG_MAP.values().toArray());
                    panel.add(text);
                    panel.add(keys);
                    panel.add(direction);

                    int result = JOptionPane.showConfirmDialog(null, panel, "Input", JOptionPane.OK_CANCEL_OPTION);
                    if (result == JOptionPane.OK_OPTION) {
                        // Look up the "flag" value for the press choice
                        Map<String, String> lookupMap = PressAction.DOWNUP_FLAG_MAP.inverse();
                        String flag = lookupMap.get(direction.getSelectedItem());
                        addAction(new PressAction((String) keys.getSelectedItem(), flag));
                    }
                }
            });
        }
        return pressButton;
    }

    /**
     * This method initializes typeButton
     *
     * @return javax.swing.JButton
     */
    private JButton getTypeButton() {
        if (typeButton == null) {
            typeButton = new JButton();
            typeButton.setText("Type Something");
            typeButton.addActionListener(new java.awt.event.ActionListener() {
                @Override
                public void actionPerformed(java.awt.event.ActionEvent e) {
                    String whatToType = JOptionPane.showInputDialog("What to type?");
                    if (whatToType != null) {
                        addAction(new TypeAction(whatToType));
                    }
                }
            });
        }
        return typeButton;
    }

    /**
     * This method initializes flingButton
     *
     * @return javax.swing.JButton
     */
    private JButton getFlingButton() {
        if (flingButton == null) {
            flingButton = new JButton();
            flingButton.setText("Fling");
            flingButton.addActionListener(new java.awt.event.ActionListener() {
                @Override
                public void actionPerformed(java.awt.event.ActionEvent e) {
                    JPanel panel = new JPanel();
                    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                    panel.add(new JLabel("Which Direction to fling?"));
                    JComboBox directionChooser = new JComboBox(DragAction.Direction.getNames());
                    panel.add(directionChooser);
                    panel.add(new JLabel("How long to drag (in ms)?"));
                    JTextField ms = new JTextField();
                    ms.setText("1000");
                    panel.add(ms);
                    panel.add(new JLabel("How many steps to do it in?"));
                    JTextField steps = new JTextField();
                    steps.setText("10");
                    panel.add(steps);



                    int result = JOptionPane.showConfirmDialog(null, panel, "Input", JOptionPane.OK_CANCEL_OPTION);
                    if (result == JOptionPane.OK_OPTION) {
                        DragAction.Direction dir =
                            DragAction.Direction.valueOf((String) directionChooser.getSelectedItem());
                        long millis = Long.parseLong(ms.getText());
                        int numSteps = Integer.parseInt(steps.getText());

                        addAction(newFlingAction(dir, numSteps, millis));
                    }
                }
            });
        }
        return flingButton;
    }

    private DragAction newFlingAction(Direction dir, int numSteps, long millis) {
        int width = Integer.parseInt(device.getProperty("display.width"));
        int height = Integer.parseInt(device.getProperty("display.height"));

        // Adjust the w/h to a pct of the total size, so we don't hit things on the "outside"
        width = (int) (width * 0.8f);
        height = (int) (height * 0.8f);
        int minW = (int) (width * 0.2f);
        int minH = (int) (height * 0.2f);

        int midWidth = width / 2;
        int midHeight = height / 2;

        int startx = minW;
        int starty = minH;
        int endx = minW;
        int endy = minH;

        switch (dir) {
            case NORTH:
                startx = endx = midWidth;
                starty = height;
                break;
            case SOUTH:
                startx = endx = midWidth;
                endy = height;
                break;
            case EAST:
                starty = endy = midHeight;
                endx = width;
                break;
            case WEST:
                starty = endy = midHeight;
                startx = width;
                break;
        }

        return new DragAction(dir, startx, starty, endx, endy, numSteps, millis);
    }

    /**
     * This method initializes exportActionButton
     *
     * @return javax.swing.JButton
     */
    private JButton getExportActionButton() {
        if (exportActionButton == null) {
            exportActionButton = new JButton();
            exportActionButton.setText("Export Actions");
            exportActionButton.addActionListener(new java.awt.event.ActionListener() {
                @Override
                public void actionPerformed(java.awt.event.ActionEvent ev) {
                    JFileChooser fc = new JFileChooser();
                    if (fc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
                        try {
                            actionListModel.export(fc.getSelectedFile());
                        } catch (FileNotFoundException e) {
                            LOG.log(Level.SEVERE, "Unable to save file", e);
                        }
                    }
                }
            });
        }
        return exportActionButton;
    }

    /**
     * This method initializes refreshButton
     *
     * @return javax.swing.JButton
     */
    private JButton getRefreshButton() {
        if (refreshButton == null) {
            refreshButton = new JButton();
            refreshButton.setText("Refresh Display");
            refreshButton.addActionListener(new java.awt.event.ActionListener() {
                @Override
                public void actionPerformed(java.awt.event.ActionEvent e) {
                    refreshDisplay();
                }
            });
        }
        return refreshButton;
    }

    private void touch(MouseEvent event) {
        int x = event.getX();
        int y = event.getY();

        // Since we scaled the image down, our x/y are scaled as well.
        double scalex = ((double) currentImage.getWidth()) / ((double) scaledImage.getWidth());
        double scaley = ((double) currentImage.getHeight()) / ((double) scaledImage.getHeight());

        x = (int) (x * scalex);
        y = (int) (y * scaley);

        switch (event.getID()) {
            case MouseEvent.MOUSE_CLICKED:
                addAction(new TouchAction(x, y, MonkeyDevice.DOWN_AND_UP));
                break;
            case MouseEvent.MOUSE_PRESSED:
                addAction(new TouchAction(x, y, MonkeyDevice.DOWN));
                break;
            case MouseEvent.MOUSE_RELEASED:
                addAction(new TouchAction(x, y, MonkeyDevice.UP));
                break;
        }
    }

    public void addAction(Action a) {
        actionListModel.add(a);
        try {
            a.execute(device);
        } catch (Exception e) {
            LOG.log(Level.SEVERE, "Unable to execute action!", e);
        }
    }
}
