package net.kenevans.dilbert;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.HeadlessException;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.BevelBorder;

import net.kenevans.dilbert.utils.AboutBoxPanel;
import net.kenevans.dilbert.utils.Utils;
import net.kenevans.imagemodel.ImageModel;
import net.kenevans.imagemodel.ImagePanel;

public class DailyDilbert extends JFrame
{
    private static final long serialVersionUID = 1L;
    private static final String urlPrefix = "http://www.dilbert.com";
    private static final String dateUrlPrefix = "http://www.dilbert.com/strips/comic/";

    private static final int WIDTH = 600;
    private static final int HEIGHT = 300;

    private ImageModel imageModel = new ImageModel();
    private ImagePanel imagePanel = null;

    private static String title = "Daily Dilbert";
    private static String version = "4.2.0";
    private JMenuItem menuFileExit = new JMenuItem();
    private JMenu menuFile = new JMenu();
    private JMenu menuEdit = new JMenu();
    private JMenuBar menuBar = new JMenuBar();
    private JMenuItem menuFilePrint = new JMenuItem();
    private JMenuItem menuFilePrintPreview = new JMenuItem();
    private JMenuItem menuFilePageSetup = new JMenuItem();
    private JMenuItem menuEditCopy = new JMenuItem();
    private JMenuItem menuFileSaveAs = new JMenuItem();
    private JMenuItem menuSelectDate = new JMenuItem();
    private JMenuItem menuToday = new JMenuItem();
    private JMenuItem menuFirst = new JMenuItem();
    private JMenuItem menuPrev = new JMenuItem();
    private JMenuItem menuNext = new JMenuItem();
    private JPanel panelCenter = new JPanel();
    private GridBagLayout gridBagLayout1 = new GridBagLayout();
    private JLabel statusBar = new JLabel();
    private JMenu menuHelp = new JMenu();
    private JMenuItem menuHelpAbout = new JMenuItem();

    private String selectedFile = null;
    private Date date;
    private CalendarDay cDay = CalendarDay.now();

    /**
     * DailyDilbert constructor
     * 
     * @throws HeadlessException
     */
    public DailyDilbert() throws HeadlessException {
        this.setTitle(title);
        this.date = new Date();
        try {
            menuInit();
            jbInit();
            getStrip(CalendarDay.now());
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Swing initialization
     * 
     * @throws java.lang.Exception
     */
    private void jbInit() throws Exception {
        this.setBounds(new Rectangle(10, 10, WIDTH, HEIGHT));
        this.getContentPane().setLayout(gridBagLayout1);

        // Image panel
        imagePanel = new ImagePanel(imageModel);
        imagePanel.setPreferredSize(new Dimension(WIDTH, HEIGHT));

        this.getContentPane().add(
            imagePanel,
            new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0,
                GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(
                    4, 2, 2, 2), 0, 0));

        // Center panel
        panelCenter.setLayout(null);
        panelCenter.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        panelCenter.setBorder(BorderFactory
            .createBevelBorder(BevelBorder.LOWERED));

        // Status bar
        statusBar.setText("Starting");
        statusBar.setToolTipText("Status");
        statusBar.setBorder(BorderFactory
            .createBevelBorder(BevelBorder.LOWERED));
        statusBar.setMaximumSize(new Dimension(WIDTH, 18));
        statusBar.setMinimumSize(new Dimension(101, 18));
        statusBar.setPreferredSize(new Dimension(WIDTH, 18));

        this.getContentPane().add(
            statusBar,
            new GridBagConstraints(0, 1, GridBagConstraints.REMAINDER, 1, 1.0,
                0.0, GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL,
                new Insets(2, 2, 2, 2), 0, 0));
    }

    /**
     * Menu initialization.
     * 
     * @throws Exception
     */
    private void menuInit() throws Exception {
        this.setJMenuBar(menuBar);
        menuFile.setText("File");
        menuBar.add(menuFile);

        // Select date
        menuSelectDate.setText("Select Date...");
        menuSelectDate.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D,
            InputEvent.CTRL_MASK));
        menuSelectDate.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                if(imagePanel != null) {
                    JCalendarDialog dlg = new JCalendarDialog(
                        DailyDilbert.this, date);
                    dlg.setModal(true);
                    dlg.setVisible(true);
                    // Repaint to move traces of the dialog
                    DailyDilbert.this.update(DailyDilbert.this.getGraphics());
                    if(!dlg.getCanceled()) {
                        Date newDate = dlg.getDate();
                        if(date != null) {
                            CalendarDay cDay = CalendarDay.now();
                            cDay.set(newDate);
                            getStrip(cDay);
                        }
                    }
                }
            }
        });
        menuFile.add(menuSelectDate);

        // Today
        menuToday.setText("Today");
        menuToday.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T,
            InputEvent.CTRL_MASK));
        menuToday.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                getStrip(CalendarDay.now());
            }
        });
        menuFile.add(menuToday);

        // File Save as
        menuFileSaveAs.setText("Save As...");
        menuFileSaveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
            InputEvent.CTRL_MASK));
        menuFileSaveAs.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                if(imagePanel != null) {
                    Date date = new Date();
                    SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy");
                    String suggestedName = "Dilbert-" + sdf.format(date)
                        + ".png";
                    if(selectedFile == null) {
                        selectedFile = suggestedName;
                    }
                    imagePanel.setSelectedFile(selectedFile);
                    imagePanel.saveAsWithSelectedFile();
                    selectedFile = imagePanel.getSelectedFile();
                }
            }
        });
        menuFile.add(menuFileSaveAs);

        // First
        menuFirst.setText("First");
        menuFirst.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F,
            InputEvent.CTRL_MASK));
        menuFirst.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                getStrip(CalendarDay.first());
            }
        });
        menuFile.add(menuFirst);

        // Next
        menuNext.setText("Next");
        menuNext.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,
            InputEvent.CTRL_MASK));
        menuNext.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                getStrip(cDay.incrementDay(1));
            }
        });
        menuFile.add(menuNext);

        // Prev
        menuPrev.setText("Previous");
        menuPrev.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,
            InputEvent.CTRL_MASK));
        menuPrev.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                getStrip(cDay.incrementDay(-1));
            }
        });
        menuFile.add(menuPrev);

        // File Save as
        menuFileSaveAs.setText("Save As...");
        menuFileSaveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
            InputEvent.CTRL_MASK));
        menuFileSaveAs.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                if(imagePanel != null) {
                    Date date = new Date();
                    SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy");
                    String suggestedName = "Dilbert-" + sdf.format(date)
                        + ".png";
                    if(selectedFile == null) {
                        selectedFile = suggestedName;
                    }
                    imagePanel.setSelectedFile(selectedFile);
                    imagePanel.saveAsWithSelectedFile();
                    selectedFile = imagePanel.getSelectedFile();
                }
            }
        });
        menuFile.add(menuFileSaveAs);

        // File Print
        menuFilePrint.setText("Print...");
        menuFilePrint.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P,
            InputEvent.CTRL_MASK));
        menuFilePrint.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                if(imagePanel != null) imagePanel.print();
            }
        });
        menuFile.add(menuFilePrint);

        // File Print Preview
        menuFilePrintPreview.setText("Print Preview...");
        menuFilePrintPreview.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                if(imagePanel != null) imagePanel.printPreview();
            }
        });
        menuFile.add(menuFilePrintPreview);

        // File Page Setup
        menuFilePageSetup.setText("Page Setup...");
        menuFilePageSetup.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                if(imagePanel != null) imagePanel.pageSetup();
            }
        });
        menuFile.add(menuFilePageSetup);

        // File Exit
        menuFileExit.setText("Exit");
        menuFileExit.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                quit(ae);
            }
        });
        menuFile.add(menuFileExit);

        // Edit
        menuEdit.setText("Edit");
        menuBar.add(menuEdit);

        // Edit Copy
        menuEditCopy.setText("Copy");
        menuEditCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C,
            InputEvent.CTRL_MASK));
        menuEditCopy.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                if(imagePanel != null) imagePanel.copy();
            }
        });
        menuEdit.add(menuEditCopy);

        // Help
        menuHelp.setText("Help");
        menuBar.add(menuHelp);

        menuHelpAbout.setText("About");
        menuHelpAbout.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                JOptionPane.showMessageDialog(null, new AboutBoxPanel(title
                    + " " + version), "About", JOptionPane.PLAIN_MESSAGE);
            }
        });
        menuHelp.add(menuHelpAbout);
    }

    /**
     * Gets the URL for the image by parsing the page at dilbert.com.
     */
    private String getImageUrl(CalendarDay cDay) {
        String imageUrlString = null;
        try {
            URL url = null;
            String dateString = null;
            // Get it from the Dilbert site
            CalendarDay cDayFirst = CalendarDay.first();
            CalendarDay cDayNow = CalendarDay.now();
            dateString = cDay.toString();
            // Check it is not in the future as the site doesn't give a
            // sensible result in this case
            if(cDay.compareTo(cDayNow) == 1) {
                Utils.errMsg(dateString + " is in the future");
                return null;
            }
            // Check it isn't before the first one
            if(cDay.compareTo(cDayFirst) == -1) {
                Utils.errMsg(dateString
                    + " is before the first available strip");
                return null;
            }
            // Store it
            this.cDay = cDay;

            url = new URL(dateUrlPrefix + dateString);

            // Look for /dyn/str_strip/xxx.strip.gif
            String regex = "(/dyn/str_strip/[^.]*\\.strip\\.gif)";
            Pattern pattern = Pattern.compile(regex);
            BufferedReader br = new BufferedReader(new InputStreamReader(
                url.openStream()));
            String line;
            while((line = br.readLine()) != null) {
                Matcher matcher = pattern.matcher(line);
                if(matcher.find()) {
                    imageUrlString = urlPrefix + matcher.group();
                    break;
                }
            }
            if(br != null) {
                br.close();
            }

            // May 2011: Seems to have the Sunday strip at strip.sunday.gif
            // Try this if the above fails
            if(imageUrlString == null) {
                regex = "(/dyn/str_strip/[^.]*\\.strip\\.sunday\\.gif)";
                pattern = Pattern.compile(regex);
                br = new BufferedReader(new InputStreamReader(url.openStream()));
                while((line = br.readLine()) != null) {
                    Matcher matcher = pattern.matcher(line);
                    if(matcher.find()) {
                        imageUrlString = urlPrefix + matcher.group();
                        break;
                    }
                }
                if(br != null) {
                    br.close();
                }
            }
            if(imageUrlString == null) {
                Utils.errMsg("Failed to find image URL");
            }
        } catch(Exception ex) {
            Utils.excMsg("Getting image URL failed:", ex);
        }
        return imageUrlString;
    }

    /**
     * Gets the strip corresponding to the given calendar day.
     * 
     * @param cDay
     */
    private void getStrip(CalendarDay cDay) {
        String imageURL = getImageUrl(cDay);
        if(imageURL == null) {
            return;
        }
        try {
            URL url = new URL(imageURL);
            BufferedImage image = ImageIO.read(url);
            if(image == null) {
                updateStatus("No image");
            } else {
                updateStatus(this.cDay.toString());
            }
            imageModel.replaceImage(image);
            imagePanel.repaint();
            this.pack();
        } catch(Exception ex) {
            Utils.excMsg("Getting image failed:", ex);
        }
    }

    /**
     * Updates the status bar
     * 
     * @param status Status bar text
     */
    public void updateStatus(final String status) {
        if(status != null) {
            // Set this to execute setText in the AWT thread
            final boolean later = true;
            if(later) {
                // Make it run in the AWT thread
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        statusBar.setText(status);
                    }
                });
            } else {
                // Run it in this thread
                statusBar.setText(status);
            }
        }
    }

    /**
     * File | Exit handler
     * 
     * @param e
     */
    private void quit(ActionEvent e) {
        int retVal = 0;
        System.exit(retVal);
    }

    /**
     * Main program
     * 
     * @param args arguments
     */
    public static void main(String[] args) {
        try {
            // Set the native look and feel
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

            // Make the job run in the AWT thread
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    JFrame frame = new DailyDilbert();
                    JFrame.setDefaultLookAndFeelDecorated(true);
                    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                    frame.setLocationRelativeTo(null);
                    frame.pack();
                    frame.setVisible(true);
                }
            });
        } catch(Throwable t) {
            t.printStackTrace();
        }
    }

}
