import java.awt.*;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;

/**
    Klasa ScreenManager zarządza inicjalizacją 
    I wyświetlaniem pełnoekranowych trybów graficznych.
*/
public class ScreenManager {

    private GraphicsDevice device;

    /**
        Tworzy nowy obiekt ScreenManager.
    */
    public ScreenManager() {
        GraphicsEnvironment environment =
            GraphicsEnvironment.getLocalGraphicsEnvironment();
        device = environment.getDefaultScreenDevice();
    }


    /**
        Zwraca listę trybów graficznych dostępnych dla 
        domyślnego urządzenia w systemie.
    */
    public DisplayMode[] getCompatibleDisplayModes() {
        return device.getDisplayModes();
    }


    /**
        Zwraca pierwszy dostępny tryb graficzny z listy trybów.
        Jeżeli żaden tryb nie jest zgodny, zwraca wartość null.
    */
    public DisplayMode findFirstCompatibleMode(
        DisplayMode modes[])
    {
        DisplayMode goodModes[] = device.getDisplayModes();
        for (int i = 0; i < modes.length; i++) {
            for (int j = 0; j < goodModes.length; j++) {
                if (displayModesMatch(modes[i], goodModes[j])) {
                    return modes[i];
                }
            }

        }

        return null;
    }


    /**
        Zwraca bieżący tryb graficzny.
    */
    public DisplayMode getCurrentDisplayMode() {
        return device.getDisplayMode();
    }


    /**
        Sprawdza, czy dwa tryby wyświetlania "pasują do siebie". Dwa tryby
        pasują do siebie, jeżeli mają tę samą rozdzielczość, głębię kolorów 
        oraz częstotliwość odświeżania. Głębia kolorów jest ignorowana, 
        jeżeli w jednym z trybów głębia kolorów ustawiona jest jako 
        DisplayMode.BIT_DEPTH_MULTI. Podobnie częstotliwość odświeżania 
        jest ignorowana, gdy w jednym z trybów jest ona ustawiona jako
        DisplayMode.REFRESH_RATE_UNKNOWN.
    */
    public boolean displayModesMatch(DisplayMode mode1,
        DisplayMode mode2)

    {
        if (mode1.getWidth() != mode2.getWidth() ||
            mode1.getHeight() != mode2.getHeight())
        {
            return false;
        }

        if (mode1.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI &&
            mode2.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI &&
            mode1.getBitDepth() != mode2.getBitDepth())
        {
            return false;
        }

        if (mode1.getRefreshRate() !=
            DisplayMode.REFRESH_RATE_UNKNOWN &&
            mode2.getRefreshRate() !=
            DisplayMode.REFRESH_RATE_UNKNOWN &&
            mode1.getRefreshRate() != mode2.getRefreshRate())
         {
             return false;
         }

         return true;
    }


    /**
        Przechodzi w tryb pełnoekranowy i zmienia tryb graficzny.
        Jeżeli podany tryb wyświetlania jest wartością null, 
        nie jest zgodny z tym urządzeniem lub jeśli system 
        nie pozwala zmieniać trybów, wykorzystany jest 
        bieżący tryb graficzny.
        <p>
        Wykorzystany jest obiekt BufferStrategy z dwoma buforami.
    */
    public void setFullScreen(DisplayMode displayMode) {
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setUndecorated(true);
        frame.setIgnoreRepaint(true);
        frame.setResizable(false);

        device.setFullScreenWindow(frame);

        if (displayMode != null &&
            device.isDisplayChangeSupported())
        {
            try {
                device.setDisplayMode(displayMode);
            }
            catch (IllegalArgumentException ex) { }
            // Poprawka dla systemu MacOS X:
            frame.setSize(displayMode.getWidth(),
                displayMode.getHeight());
        }
        // Unikanie potencjalnego zakleszczenia w wersji 1.4.1_02:
        try {
            EventQueue.invokeAndWait(new Runnable() {
                public void run() {
                    frame.createBufferStrategy(2);
                }
            });
        }
        catch (InterruptedException ex) {
            // ignorowane
        }
        catch (InvocationTargetException  ex) {
            // ignorowane
        }
    }


    /**
        Pobiera kontekst graficzny dla ekranu. 
        ScreenManager korzysta z podwójnego buforowania, więc aplikacje 
        muszą wywoływać update() w celu pokazania narysowanej grafiki.
        <p>
        Aplikacja musi usuwać obiekt graficzny.
    */
    public Graphics2D getGraphics() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            BufferStrategy strategy = window.getBufferStrategy();
            return (Graphics2D)strategy.getDrawGraphics();
        }
        else {
            return null;
        }
    }

    /**
        Odrysowuje ekran.
    */
    public void update() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            BufferStrategy strategy = window.getBufferStrategy();
            if (!strategy.contentsLost()) {
                strategy.show();
            }
        }
        // Operacja Sync, działająca w niektórych systemach
        // (w systemie Linux naprawia problem z kolejką zdarzeń).
        Toolkit.getDefaultToolkit().sync();
    }


    /**
        Zwraca okno używane obecnie w trybie pełnoekranowym.
        Zwraca null, gdy urządzenie nie jest w trybie pełnoekranowym.
    */
    public JFrame getFullScreenWindow() {
        return (JFrame)device.getFullScreenWindow();
    }


    /**
        Zwraca szerokość okna użytego obecnie w trybie pełnoekranowym.
        Zwraca 0, jeżeli urządzenie nie jest w trybie pełnoekranowym.
    */
    public int getWidth() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            return window.getWidth();
        }
        else {
            return 0;
        }
    }


    /**
        Zwraca wysokość okna użytego obecnie w trybie pełnoekranowym.
        Zwraca 0, jeżeli urządzenie nie jest w trybie pełnoekranowym.
    */
    public int getHeight() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            return window.getHeight();
        }
        else {
            return 0;
        }
    }


    /**
        Przywraca poprzedni tryb wyświetlania.
    */
    public void restoreScreen() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            window.dispose();
        }
        device.setFullScreenWindow(null);
    }


    /**
        Tworzy obiekt Image, zgodny z bieżącym ekranem.
    */
    public BufferedImage createCompatibleImage(int w, int h,
        int transparancy)
    {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            GraphicsConfiguration gc =
                window.getGraphicsConfiguration();
            return gc.createCompatibleImage(w, h, transparancy);
        }
        return null;
    }
}
