package com.vaadin.tests.tickets;

import java.io.ByteArrayInputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;

import com.vaadin.Application;
import com.vaadin.terminal.ApplicationResource;
import com.vaadin.terminal.DownloadStream;
import com.vaadin.terminal.ParameterHandler;
import com.vaadin.terminal.URIHandler;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.Embedded;
import com.vaadin.ui.Label;
import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.Window.Notification;

/**
 * Proof of concept how to create a decent login screen that works with browsers
 * PW managers. (Browsers don't autofill generated login forms)
 * 
 * TODO generalize js to work inside iframes (if a Vaadin application is inside
 * iframe)
 * 
 * TODO extract login form to a external template.
 * 
 * TODO theme
 * 
 * TODO refine docs, make LoginBox a standard Vaadin component
 * 
 * TODO article
 * 
 */
public class Ticket1362Login extends Application {

    public class LoginBox extends CustomComponent {

        Embedded iframe = new Embedded();

        ApplicationResource loginPage = new ApplicationResource() {

            private byte[] loginHTML = (""
                    + "<html>"
                    + "<head><script type='text/javascript'>"
                    + "var setTarget = function() {document.getElementById('loginf').action = top.location;};"
                    + "</script></head>"
                    + "<body onload='setTarget();'>"
                    + "Iframe generated by LoginBox. PW managers can autofill form. Form handled by LoginBox "
                    + "that will fire LoginEvents. Will post into another iframe, from where the script "
                    + "will find Vaadin client that will be force synced. <form id='loginf' target='logintarget'>"
                    + "Username : <input type='text' name='username'>"
                    + "Password : <input type='password' name='password'>"
                    + "<input type='submit' value='login'>" + "</form>"
                    + "<iframe name='logintarget'></iframe>" + "</body>"
                    + "</html>").getBytes();

            public Application getApplication() {
                return LoginBox.this.getApplication();
            }

            public int getBufferSize() {
                return loginHTML.length;
            }

            public long getCacheTime() {
                return 0;
            }

            public String getFilename() {
                return "login.html";
            }

            public DownloadStream getStream() {
                return new DownloadStream(new ByteArrayInputStream(loginHTML),
                        getMIMEType(), getFilename());
            }

            public String getMIMEType() {
                return "text/html";
            }
        };

        private ParameterHandler paramHandler = new ParameterHandler() {

            public void handleParameters(Map<String, String[]> parameters) {
                if (parameters.containsKey("username")) {
                    getWindow().addURIHandler(uriHandler);

                    HashMap<String, String> params = new HashMap<String, String>();
                    // expecting single params
                    for (Iterator<String> it = parameters.keySet().iterator(); it
                            .hasNext();) {
                        String key = it.next();
                        String value = (parameters.get(key))[0];
                        params.put(key, value);
                    }
                    LoginEvent event = new LoginEvent(params);
                    for (Iterator<LoginListener> iterator = listeners
                            .iterator(); iterator.hasNext();) {
                        LoginListener listener = iterator.next();
                        listener.onLogin(event);
                    }
                }
            }
        };

        private URIHandler uriHandler = new URIHandler() {
            public DownloadStream handleURI(URL context, String relativeUri) {
                if (window != null) {
                    window.removeURIHandler(this);
                }
                return new DownloadStream(
                        new ByteArrayInputStream(
                                "<html><body>Login form handeled.<script type='text/javascript'>top.vaadin.forceSync();</script></body></html>"
                                        .getBytes()), "text/html",
                        "loginSuccesfull.html");
            }
        };

        private LinkedList<LoginListener> listeners = new LinkedList<LoginListener>();

        private Window window;

        LoginBox() {
            iframe.setType(Embedded.TYPE_BROWSER);
            iframe.setSizeFull();
            setCompositionRoot(iframe);
        }

        @Override
        public void attach() {
            super.attach();
            getApplication().addResource(loginPage);
            getWindow().addParameterHandler(paramHandler);
            iframe.setSource(loginPage);
        }

        @Override
        public void detach() {
            getApplication().removeResource(loginPage);
            getWindow().removeParameterHandler(paramHandler);
            // store window temporary to properly remove uri handler once
            // response is handled. (May happen if login handler removes login
            // box
            window = getWindow();
            super.detach();
        }

        /**
         * This event is sent when login form is submitted.
         */
        public class LoginEvent {

            private Map<String, String> params;

            private LoginEvent(Map<String, String> params) {
                this.params = params;
            }

            /**
             * Returns form value by field name.
             * 
             * @param name
             * @return value in given field
             */
            public String getLoginParameter(String name) {
                if (params.containsKey(name)) {
                    return params.get(name);
                } else {
                    return null;
                }
            }
        }

        /**
         * Adds LoginListener to handle login logic
         * 
         * @param listener
         */
        public void addLoginListener(LoginListener listener) {
            listeners.add(listener);
        }

        /**
         * Removes LoginListener
         * 
         * @param listener
         */
        public void removeLoginListener(LoginListener listener) {
            listeners.remove(listener);
        }

    }

    /**
     * Login listener is a class capable to listen LoginEvents sent from
     * LoginBox
     */
    public interface LoginListener {
        /**
         * This method is fired on each login form post.
         * 
         * @param event
         */
        public void onLogin(LoginBox.LoginEvent event);
    }

    final static String GUEST = "guest";

    LoginBox loginBox = new LoginBox();

    Label currentUser = new Label(GUEST);

    private Panel mainPanel;

    private VerticalLayout el;

    @Override
    public void init() {

        final Window mainWin = new Window(
                "Test app with password manager savvy login functionality");

        el = new VerticalLayout();

        currentUser.setCaption("Currennt user");
        el.addComponent(currentUser);

        el.addComponent(loginBox);
        el.setExpandRatio(loginBox, 1);

        mainWin.setContent(el);

        setMainWindow(mainWin);

        mainPanel = new Panel("Test app");
        mainPanel.setSizeFull();
        mainPanel.addComponent(new Label("User is logged in"));
        mainPanel.addComponent(new Button("Logout", new Button.ClickListener() {

            public void buttonClick(ClickEvent event) {
                Ticket1362Login.this.close();
            }
        }));

        loginBox.addLoginListener(new LoginListener() {
            public void onLogin(LoginBox.LoginEvent event) {
                String pw = event.getLoginParameter("password");
                String username = event.getLoginParameter("username");
                if (pw.equals("1234")) {
                    setUser(username);
                    currentUser.setValue(username);
                    currentUser.getWindow().showNotification(
                            "Logged in user: " + username);
                    getMainWindow().getContent().replaceComponent(loginBox,
                            mainPanel);
                    el.setExpandRatio(mainPanel, 1);
                } else {
                    getMainWindow().showNotification(
                            "Wrong password. Hint, try '1234' ",
                            Notification.TYPE_WARNING_MESSAGE);
                }
            }
        });

    }

}