import java.awt.event.*;
import java.awt.*;
import java.util.List;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.border.Border;

import com.brackeen.javagamebook.graphics.*;
import com.brackeen.javagamebook.input.*;

/**
    Klasa KeyConfigTest dziedziczy po MenuTest i jest rozszerzona
    o okno dialogowe, służące do konfiguracji klawiszy sterujących.
*/
public class KeyConfigTest extends MenuTest {

    public static void main(String[] args) {
        new KeyConfigTest().run();
    }

    private static final String INSTRUCTIONS =
        "<html>Kliknij pole tekstowe akcji w celu zmiany przypisanych do niego klawiszy." +
        "<br>Z akcją mogą być skojarzone najwyżej trzy klawisze." +
        "<br>Naciśnij Backspace, aby skasować klawisze akcji.";

    private JPanel dialog;
    private JButton okButton;
    private List inputs;

    public void init() {
        super.init();

        inputs = new ArrayList();

        // Tworzenie listy obiektów GameAction i skojarzonych z nimi klawiszy.
        JPanel configPanel = new JPanel(new GridLayout(5,2,2,2));
        addActionConfig(configPanel, moveLeft);
        addActionConfig(configPanel, moveRight);
        addActionConfig(configPanel, jump);
        addActionConfig(configPanel, pause);
        addActionConfig(configPanel, exit);

        // Tworzenie panela zawierającego klawisz OK.
        JPanel bottomPanel = new JPanel(new FlowLayout());
        okButton = new JButton("OK");
        okButton.setFocusable(false);
        okButton.addActionListener(this);
        bottomPanel.add(okButton);

        // Tworzenie panela zawierającego instrukcję.
        JPanel topPanel = new JPanel(new FlowLayout());
        topPanel.add(new JLabel(INSTRUCTIONS));

        // Tworzenie brzegu okna dialogowego.
        Border border =
            BorderFactory.createLineBorder(Color.black);

        // Tworzenie okna dialogowego konfiguracji.
        dialog = new JPanel(new BorderLayout());
        dialog.add(topPanel, BorderLayout.NORTH);
        dialog.add(configPanel, BorderLayout.CENTER);
        dialog.add(bottomPanel, BorderLayout.SOUTH);
        dialog.setBorder(border);
        dialog.setVisible(false);
        dialog.setSize(dialog.getPreferredSize());

        // Wyśrodkowanie okna dialogowego.
        dialog.setLocation(
            (screen.getWidth() - dialog.getWidth()) / 2,
            (screen.getHeight() - dialog.getHeight()) / 2);

        // Dodanie okna dialogowego do warstwy "okien modalnych"
        // panelu ekranu.
        screen.getFullScreenWindow().getLayeredPane().add(dialog,
            JLayeredPane.MODAL_LAYER);
    }

    /**
        Dodanie etykiety zawierającej nazwę obiektu GameAction oraz
        obiektu InputComponent, wykorzystywanego do zmiany mapowania klawiszy.
    */
    private void addActionConfig(JPanel configPanel,
        GameAction action)
    {
        JLabel label = new JLabel(action.getName(), JLabel.RIGHT);
        InputComponent input = new InputComponent(action);
        configPanel.add(label);
        configPanel.add(input);
        inputs.add(input);
    }

    public void actionPerformed(ActionEvent e) {
        super.actionPerformed(e);
        if (e.getSource() == okButton) {
            // Ukrywa okno konfiguracyjne.
            configAction.tap();
        }
    }

    public void checkSystemInput() {
        super.checkSystemInput();
        if (configAction.isPressed()) {
            // Pokazywanie lub ukrywanie okna konfiguracyjnego.
            boolean show = !dialog.isVisible();
            dialog.setVisible(show);
            setPaused(show);
        }
    }

    /**
        Kasuje tekst wyświetlany w każdym obiekcie InputComponent, 
        zawierający nazwy skojarzonych klawiszy.
    */
    private void resetInputs() {
        for (int i=0; i<inputs.size(); i++) {
            ((InputComponent)inputs.get(i)).setText();
        }
    }

    /**
        Klasa InputComponent pozwala na wyświetlanie klawiszy 
        skojarzonych z określonymi akcjami i zmianę tych 
        klawiszy przez użytkownika. Użytkownik wybiera obiekt InputComponent, 
        klikając go, a następnie naciska klawisz lub przycisk myszy
        (lub obraca kółkiem myszy) w celu zmiany skojarzonej wartości.
    */
    class InputComponent extends JTextField  {

        private GameAction action;

        /**
            Tworzy nowy obiekt InputComponent dla 
            podanego obiektu GameAction.
        */
        public InputComponent(GameAction action) {
            this.action = action;
            setText();
            enableEvents(KeyEvent.KEY_EVENT_MASK |
                MouseEvent.MOUSE_EVENT_MASK |
                MouseEvent.MOUSE_MOTION_EVENT_MASK |
                MouseEvent.MOUSE_WHEEL_EVENT_MASK);
        }

        /**
            Ustawia nazwy klawiszy wyświetlanych w tym polu InputComponent.
        */
        private void setText() {
            String text = "";
            List list = inputManager.getMaps(action);
            if (list.size() > 0) {
                for (int i=0; i<list.size(); i++) {
                    text+=(String)list.get(i) + ", ";
                }
                // Usunięcie ostatniego przecinka.
                text = text.substring(0, text.length() - 2);
            }

            // Upewnij się, że nie wystąpi zakleszczenie.
            synchronized (getTreeLock()) {
                setText(text);
            }

        }

        /**
            Kojarzy GameAction z tego obiektu InputComponent z określonym
            klawiszem lub zdarzeniem myszy.
        */
        private void mapGameAction(int code, boolean isMouseMap) {
            if (inputManager.getMaps(action).size() >= 3) {
                inputManager.clearMap(action);
            }
            if (isMouseMap) {
                inputManager.mapToMouse(action, code);
            }
            else {
                inputManager.mapToKey(action, code);
            }
            resetInputs();
            screen.getFullScreenWindow().requestFocus();
        }

        // Inny sposób przechwytywania zdarzeń klawiatury
        protected void processKeyEvent(KeyEvent e) {
            if (e.getID() == e.KEY_PRESSED) {
                // Jeżeli naciśnięto Backspace, skojarzenie jest kasowane.
                if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE &&
                    inputManager.getMaps(action).size() > 0)
                {
                    inputManager.clearMap(action);
                    setText("");
                    screen.getFullScreenWindow().requestFocus();
                }
                else {
                    mapGameAction(e.getKeyCode(), false);
                }
            }
            e.consume();
        }

        // Inny sposób przechwytywania zdarzeń myszy.
        protected void processMouseEvent(MouseEvent e) {
            if (e.getID() == e.MOUSE_PRESSED) {
                if (hasFocus()) {
                    int code = InputManager.getMouseButtonCode(e);
                    mapGameAction(code, true);
                }
                else {
                    requestFocus();
                }
            }
            e.consume();
        }

        // Inny sposób przechwytywania zdarzeń myszy.
        protected void processMouseMotionEvent(MouseEvent e) {
            e.consume();
        }

        // Inny sposób przechwytywania zdarzeń myszy.
        protected void processMouseWheelEvent(MouseWheelEvent e) {
            if (hasFocus()) {
                int code = InputManager.MOUSE_WHEEL_DOWN;
                if (e.getWheelRotation() < 0) {
                    code = InputManager.MOUSE_WHEEL_UP;
                }
                mapGameAction(code, true);
            }
            e.consume();
        }
    }
}
