/*
 *  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 jpa.tools.swing;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import openbook.client.Images;

/**
 * Displays and navigates PowerPoint slides.
 *
 * @author Pinaki Poddar
 *
 */
public class PowerPointViewer extends JPanel {
    private static final long serialVersionUID = 1L;
    private List<WeakReference<ImageIcon>> _slides = new ArrayList<>();
    private int _total;      // Total number of slides in the deck.
    private String _dir;
    private List<URL> _slideURLs;
    private JButton _prev, _next;
    private JSpinner _goto;
    private JButton[] _navButtons;
    private int MAX_BUTTONS = 10; // Total number of navigation buttons excluding the PREVIOUS and NEXT button.

    // The slide
    private JLabel _view;

    // The key for client property in the navigation buttons denoting the 0-based index of the slides.
    private static final String SLIDE_INDEX = "slide.index";

    /**
     * Create a viewer with slides in the specified directory.
     *
     * @param dir path to a directory containing PowerPoint slides.
     * @param slides name of the slides
     */
    public PowerPointViewer(String dir, List<String> slides) {
        super(true);
        _dir = dir.endsWith("/") ? dir : dir + "/";
        _slideURLs = validateSlides(_dir, slides);
        _total  = _slideURLs.size();
        for (int i = 0; i < _total; i++) {
            _slides.add(null);
        }

        setLayout(new BorderLayout());
        _view = new JLabel(getSlideAt(0));
        add(new JScrollPane(_view), BorderLayout.CENTER);
        add(createNavigationButtons(), BorderLayout.SOUTH);
    }

    /**
     * Create buttons to navigate the slides.
     *
     * @return a panel containing the navigation buttons.
     */
    private JPanel createNavigationButtons() {
        JPanel buttons = new JPanel();
        _navButtons = new JButton[Math.min(MAX_BUTTONS, _total)];
        _prev = new RoundButton(Images.BACK);
        buttons.add(_prev);
        _prev.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                prevPage();
            }
        });
        buttons.add(Box.createHorizontalGlue());
        for (int i = 0; i < _navButtons.length; i++) {
            if (i == _navButtons.length/2) {
                JLabel gotoLabel = new JLabel("Go to ");
                _goto = new JSpinner(new SpinnerNumberModel(1,1,_total,1));
                buttons.add(gotoLabel);
                buttons.add(_goto);
                _goto.addChangeListener(new ChangeListener() {
                    @Override
                    public void stateChanged(ChangeEvent e) {
                        Integer page = (Integer)_goto.getValue();
                        showPage(page-1);
                    }
                });
            }
            int slideIndex = i + 2;
            _navButtons[i] = new JButton(String.format("%02d", slideIndex));
            buttons.add(_navButtons[i]);
            _navButtons[i].putClientProperty(SLIDE_INDEX, i+1);
            _navButtons[i].addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    JButton button = (JButton)e.getSource();
                    int index = (Integer)button.getClientProperty(SLIDE_INDEX);
                    showPage(index);
                }
            });
        }
        _next = new RoundButton(Images.NEXT);
        buttons.add(Box.createHorizontalGlue());
        buttons.add(_next);
        _next.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                nextPage();
            }
        });
        return buttons;

    }

    /**
     * Show the next page.
     */
    private void nextPage() {
        int current = getCurrentPageIndex();
        if (current >= _total-1)
            return;
        current += 1;
        showPage(current);
    }

    private void prevPage() {
        int current = getCurrentPageIndex();
        if (current <= 0)
            return;
        current -= 1;
        showPage(current);
    }

    int getCurrentPageIndex() {
        return (Integer)_goto.getValue()-1;
    }

    /**
     * Shows the slide at the given index.
     *
     * @param index 0-based index of the slides.
     */
    private void showPage(int index) {
        _view.setIcon(getSlideAt(index));
        updateButtons(index);
    }

    /**
     * Updates the buttons.
     *
     * @param current 0-based index of the currently displayed slide.
     */
    private void updateButtons(int index) {
        _goto.setValue(index+1);

        int last  = index + _navButtons.length;
        if (last >= _total)
            return;

        for (int i = 0; i < _navButtons.length; i++) {
            int slideIndex = index+i+2;
            _navButtons[i].setText(String.format("%02d", slideIndex));
            _navButtons[i].putClientProperty(SLIDE_INDEX, (index+i+1));
        }
    }

    public int getSlideCount() {
        return _total;
    }

    public ImageIcon getSlideAt(int index) {
        WeakReference<ImageIcon> weak = _slides.get(index);
        if (weak == null) {
            return loadSlide(index);
        }
        return (weak.get() == null) ? loadSlide(index) : weak.get();
    }

    ImageIcon loadSlide(int index) {
        URL imgURL = _slideURLs.get(index);
        ImageIcon icon = new ImageIcon(imgURL);
        _slides.add(index, new WeakReference<>(icon));
        return icon;
    }

    List<URL> validateSlides(String dir, List<String> slides) {
        List<URL> valid = new ArrayList<>();
        for (String slide : slides) {
            URL url = findResource(dir + slide);
            if (url != null) {
                valid.add(url);
            }
        }
        return valid;
    }

    private URL findResource(String path) {
        if (path == null)
            return null;
        URL imgURL = Thread.currentThread().getContextClassLoader().getResource(path);
        if (imgURL == null) {
            imgURL = getClass().getResource(path);
            if (imgURL == null) {
                System.err.println("Couldn't find file: " + path);
                return null;
            }
        }
        return imgURL;
    }

}
