package de.visone.crawl.gui.editor;

import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.net.URL;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import org.apache.log4j.Logger;

import de.visone.crawl.gui.AbstractCrawlCard;
import de.visone.crawl.rules.BlacklistFilter;
import de.visone.crawl.rules.HtmlQuery;
import de.visone.crawl.rules.RuleFile;
import de.visone.crawl.rules.RuleManager;
import de.visone.crawl.sys.CrawlState;
import de.visone.crawl.sys.Helper4Crawler;
import de.visone.crawl.sys.UrlPool;
import de.visone.crawl.sys.UserAgents;
import de.visone.crawl.texter.TreeTexterImpl;

public final class RuleEditorDialog extends JDialog implements QueryManager {

    private static final Logger logger = Logger
            .getLogger(RuleEditorDialog.class);

    private static final String TXT_EXAMPLE = "example url";

    private static class URLRulePanel extends JPanel {

        private static final long serialVersionUID = -4009099799776631260L;

        private final JTextField url;

        private final JLabel urlLabel;

        private final JTextField rule;

        private final JLabel hint;

        private boolean doCancel;

        private final GridBagConstraints gbc;

        public URLRulePanel(final String srule, final String example,
                final boolean urlMayChange) {
            doCancel = true;
            setLayout(new GridBagLayout());
            gbc = new GridBagConstraints();
            gbc.insets = new Insets(2, 0, 2, 0);
            gbc.fill = GridBagConstraints.BOTH;
            gbc.gridy = 0;

            // setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
            final DocumentListener dl = new DocumentListener() {

                @Override
                public void removeUpdate(final DocumentEvent e) {
                    setHint();
                }

                @Override
                public void insertUpdate(final DocumentEvent e) {
                    setHint();
                }

                @Override
                public void changedUpdate(final DocumentEvent e) {
                    setHint();
                }
            };
            if (urlMayChange) {
                url = new JTextField(example, 30);
                addOption(TXT_EXAMPLE, url);
                url.getDocument().addDocumentListener(dl);
                urlLabel = null;
            } else {
                urlLabel = new JLabel(TXT_EXAMPLE + ": " + example);
                addWide(urlLabel, 0);
                url = null;
            }
            rule = new JTextField(srule, 20);
            addOption("rule file", rule);
            hint = new JLabel(" ");
            addWide(hint, 0);
            rule.getDocument().addDocumentListener(dl);
        }

        public boolean isValidRule() {
            return rule.getText().endsWith(".rule");
        }

        public boolean isValidURL() {
            return url == null || getUrl() != null;
        }

        public void setHint() {
            if (!isValidRule()) {
                hint.setText("rule must end with .rule");
                return;
            }
            if (!isValidURL()) {
                hint.setText("url is not valid");
                return;
            }
            hint.setText(" ");
        }

        public String getRule() {
            return rule.getText();
        }

        public URL getUrl() {
            return url != null ? Helper4Crawler.getURL(url.getText()) : null;
        }

        public void doOk() {
            doCancel = false;
        }

        public boolean shouldCancel() {
            return doCancel;
        }

        private void addOption(final String label, final Component comp) {
            gbc.gridx = 0;
            add(new JLabel(label), gbc);
            gbc.gridx = 1;
            gbc.weightx = 1;
            gbc.insets.left += 5;
            add(comp, gbc);
            gbc.insets.left -= 5;
            gbc.weightx = 0;
            gbc.gridy++;
        }

        private void addWide(final JComponent c, double weighty) {
            gbc.gridx = 0;
            gbc.gridwidth = 2;
            gbc.weightx = 1;
            gbc.weighty = weighty;
            add(c, gbc);
            gbc.weighty = 0;
            gbc.gridwidth = 1;
            gbc.weightx = 0;
            gbc.gridy++;
        }
    }

    private static void addOkCancel(final URLRulePanel pane, final Action ok,
            final Action cancel) {
        final JButton bok = new JButton(ok);
        final JButton bcancel = new JButton(cancel);
        final JPanel bottomPane = new JPanel();
        bottomPane.setLayout(new BoxLayout(bottomPane, BoxLayout.X_AXIS));
        bottomPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        bottomPane.add(Box.createHorizontalGlue());
        bottomPane.add(bok);
        bottomPane.add(Box.createRigidArea(AbstractCrawlCard.DIM));
        bottomPane.add(bcancel);
        pane.addWide(bottomPane, 0);
    }

    public static String showRuleEditor(final JFrame parent, String rule,
            URL example, final boolean skipIntro) {
        if (!skipIntro || rule == null || example == null) {
            if (rule == null && example != null) {
                rule = RuleManager.getFullRuleForURL(example);
            }
            final JDialog d = new JDialog(parent, "determine example url", true);
            final URLRulePanel urp = new URLRulePanel(rule,
                    example != null ? example.toString() : "http://", true);
            final Action ok = new AbstractAction("ok") {

                private static final long serialVersionUID = 2085161425644687775L;

                @Override
                public void actionPerformed(final ActionEvent e) {
                    if (!urp.isValidRule() || !urp.isValidURL()) {
                        urp.setHint();
                        return;
                    }
                    urp.doOk();
                    d.dispose();
                }

            };
            final Action cancel = new AbstractAction("cancel") {

                private static final long serialVersionUID = -2962626346775304277L;

                @Override
                public void actionPerformed(final ActionEvent e) {
                    d.dispose();
                }
            };
            addOkCancel(urp, ok, cancel);
            d.add(urp);
            d.pack();
            if (parent != null)
                d.setLocationRelativeTo(parent);
            d.toFront();
            d.setVisible(true);
            if (urp.shouldCancel()) {
                return null;
            }
            rule = urp.getRule();
            example = urp.getUrl();
        }
        RuleFile startRule = RuleManager.getFileFromRule(rule);
        startRule = startRule.exists() ? startRule : null;
        RuleEditorDialog red;
        try {
            red = new RuleEditorDialog(parent, rule, startRule, example);
            if (parent != null)
                red.setLocationRelativeTo(parent);
            red.toFront();
            red.setVisible(true);
            return red.getResult();
        } catch (final Exception e) {
            logger.error("cannot show the rule editor dialog", e);
        }
        return null;
    }

    private static final long serialVersionUID = -6640590225128359797L;

    private final RuleFile startRule;

    private final URLRulePanel rulePane;

    private TextRuleEditor textRules;

    private BlacklistEditor blRules;

    private String result;

    private RuleEditorDialog(final JFrame parent, final String rule,
            final RuleFile startRule, final URL exampleUrl) throws Exception {
        super(parent, startRule == null ? "new rule" : "edit rule", true);
        this.startRule = startRule;
        result = null;
        rulePane = new URLRulePanel(rule, exampleUrl.toString(), false);
        ini(rule, exampleUrl);
        final Action ok = new AbstractAction("ok") {
            private static final long serialVersionUID = -2300894939574085490L;

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (saveRules()) {
                    result = rulePane.getRule();
                    dispose();
                }
            }
        };
        final Action cancel = new AbstractAction("cancel") {
            private static final long serialVersionUID = 3826443728155869671L;

            @Override
            public void actionPerformed(final ActionEvent e) {
                dispose();
            }
        };
        addOkCancel(rulePane, ok, cancel);
        add(rulePane);
        pack();
    }

    @Deprecated
    public static void main(String... args) throws Exception {
        new RuleEditorDialog(null, "~matthias.localhost.rule", null, new URL(
                "http://localhost/~matthias/")).setVisible(true);
    }

    public String getResult() {
        return result;
    }

    private static String CHGR_EXT = "<html>the rule name has changed!<br>"
            + "do you want to keep the old rule?";

    private static String CHGR = "rule name changed";

    private boolean saveRules() {
        if (!rulePane.isValidRule()) {
            rulePane.setHint();
            return false;
        }
        final RuleFile saveTo = RuleManager.getFileFromRule(rulePane.getRule());
        try {
            RuleManager.writeRules(saveTo, blRules.getRules(), textRules
                    .getRules());
        } catch (final IOException e) {
            logger.error("cannot save rule", e);
            return false;
        }
        if (startRule != null && !saveTo.equals(startRule)
                && startRule.isLocal()) {
            final int choice = JOptionPane.showOptionDialog(this, CHGR_EXT,
                    CHGR, JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE, null, new String[] { "keep",
                            "remove" }, 0);
            if (choice == 1) {
                startRule.delete();
            }
        }
        return true;
    }

    private void ini(final String rule, final URL example) throws Exception {
        final HtmlQuery q = new HtmlQuery(rule);
        final BlacklistFilter bf = new BlacklistFilter(null);
        if (startRule != null) {
            RuleManager.addRule(rule, bf, q);
        } else {
            HtmlQuery.addStd(q);
        }
        final UrlPool pool = new UrlPool();
        final TreeTexterImpl tti = new TreeTexterImpl(example);
        final CrawlState state = new CrawlState(example, 0, tti);
        Helper4Crawler.crawl(state, pool, UserAgents.getDefault());
        textRules = new TextRuleEditor(tti.getRoot(), q.getRules());
        blRules = new BlacklistEditor(bf.getRules(), bf.getHosts());
        final LinkViewer linkView = new LinkViewer(tti.getRoot(), this);
        textRules.setRuleListener(linkView);
        blRules.setRuleListener(linkView);
        final JTabbedPane tabbedPane = new JTabbedPane();
        tabbedPane.addTab("text / links", textRules);
        tabbedPane.addTab("blacklist", blRules);
        tabbedPane.addTab("links", new JScrollPane(linkView));
        rulePane.addWide(tabbedPane, 1);
    }

    @Override
    public void addBlacklistEntry(final String entry) {
        blRules.addBlacklistRule(entry);
    }

    @Override
    public void addRule(final String rule, final boolean inc) {
        textRules.addRule((inc ? "+ " : "- ") + rule);
    }

}
