/*
 * NewJDialog.java
 *
 * Created on 2007/11/22, 15:15
 */
package open.dolphin.client;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;


import open.dolphin.delegater.remote.RemoteUserDelegater;
import open.dolphin.infomodel.UserModel;


import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javax.security.auth.login.LoginException;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import open.dolphin.client.settings.ProjectSettingDialog;
import open.dolphin.helper.PlugInMenuSupport;
import open.dolphin.log.LogWriter;
import open.dolphin.plugin.IPlugin;
import open.dolphin.plugin.PluginWrapper;
import open.dolphin.project.DolphinPrincipal;
import open.dolphin.project.GlobalConstants;
import open.dolphin.project.GlobalVariables;
import open.socket.data.Command;
import open.socket.data.RequestObject;
import org.jdesktop.application.Application;
import org.jdesktop.application.ApplicationContext;
import org.jdesktop.application.Task;
import org.jdesktop.application.TaskMonitor;

/**
 *
 * @author  kazushi
 */
public class LoginDialog extends javax.swing.JDialog {

    /** Login Status */
    public enum LoginStatus {

        AUTHENTICATED, NOT_AUTHENTICATED, CANCELD
    };
    private BlockGlass blockGlass;
    private RemoteUserDelegater userDlg;    // 認証制御用
    private int tryCount;
    private int maxTryCount;
    private LoginStatus result;    // 認証結果のプロパティ
    private PropertyChangeSupport boundSupport;
    private DolphinPrincipal principal;    // モデル
    private StateMgr stateMgr;    // StateMgr
    private ApplicationContext context;
    private PlugInMenuSupport plugins;

    /** Creates new form NewJDialog
     * @param modal
     */
    public LoginDialog(java.awt.Frame parent, boolean modal, PlugInMenuSupport plugins) {
        super(parent, modal);
        this.plugins = plugins;
        boundSupport = new PropertyChangeSupport(this);
        initComponents();
        //バーの停止が出来ない（組み合わせ的に発生するバグの可能性）
        progressBar.setVisible(false);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jLabel1 = new javax.swing.JLabel();
        jPanel1 = new javax.swing.JPanel();
        jPanel2 = new javax.swing.JPanel();
        jLabel2 = new javax.swing.JLabel();
        userIdField = new javax.swing.JTextField();
        jLabel3 = new javax.swing.JLabel();
        passwordField = new javax.swing.JPasswordField();
        progressBar = new javax.swing.JProgressBar();
        stLabel = new javax.swing.JLabel();
        jPanel3 = new javax.swing.JPanel();
        settingBtn = new javax.swing.JButton();
        cancelBtn = new javax.swing.JButton();
        loginBtn = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        setResizable(false);

        jLabel1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/open/dolphin/resources/images/opendolphin2.JPG"))); // NOI18N
        jLabel1.setText("jLabel1");

        jPanel2.setLayout(new java.awt.GridLayout(3, 2));

        jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
        jLabel2.setText("ユーザID：");
        jPanel2.add(jLabel2);
        jPanel2.add(userIdField);

        jLabel3.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
        jLabel3.setText("パスワード：");
        jPanel2.add(jLabel3);
        jPanel2.add(passwordField);
        jPanel2.add(progressBar);
        jPanel2.add(stLabel);

        jPanel3.setLayout(new java.awt.GridLayout(1, 0));

        settingBtn.setText("設 定");
        jPanel3.add(settingBtn);

        cancelBtn.setText("取消し");
        jPanel3.add(cancelBtn);

        loginBtn.setText("ログイン");
        loginBtn.setEnabled(false);
        jPanel3.add(loginBtn);

        org.jdesktop.layout.GroupLayout jPanel1Layout = new org.jdesktop.layout.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jPanel3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 280, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
            .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                .add(jPanel2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 280, Short.MAX_VALUE))
        );
        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jPanel1Layout.createSequentialGroup()
                .addContainerGap(86, Short.MAX_VALUE)
                .add(jPanel3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 31, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
            .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                .add(jPanel1Layout.createSequentialGroup()
                    .add(jPanel2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 70, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                    .addContainerGap(47, Short.MAX_VALUE)))
        );

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .add(jLabel1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 350, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .add(18, 18, 18)
                .add(jPanel1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(23, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .add(10, 10, 10)
                .add(jPanel1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
            .add(jLabel1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 150, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton cancelBtn;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JPanel jPanel3;
    private javax.swing.JButton loginBtn;
    private javax.swing.JPasswordField passwordField;
    private javax.swing.JProgressBar progressBar;
    private javax.swing.JButton settingBtn;
    private javax.swing.JLabel stLabel;
    private javax.swing.JTextField userIdField;
    // End of variables declaration//GEN-END:variables

    /**
     * 認証結果プロパティリスナを登録する。
     * @param listener 登録する認証結果リスナ
     */
    @Override
    public void addPropertyChangeListener(String prop, PropertyChangeListener listener) {
        boundSupport.addPropertyChangeListener(prop, listener);
    }

    /**
     * 認証結果プロパティリスナを登録する。
     * @param listener 削除する認証結果リスナ
     */
    @Override
    public void removePropertyChangeListener(String prop, PropertyChangeListener listener) {
        boundSupport.addPropertyChangeListener(prop, listener);
    }

    /**
     * ログイン画面を開始する。
     */
    public void start() {
        // ダイアログモデルを生成し値を初期化する
        principal = new DolphinPrincipal();
        if (GlobalVariables.isValid()) {
            principal.setFacilityId(GlobalVariables.getFacilityId());
            principal.setUserId(GlobalVariables.getUserId());
        }

        // GUI を構築しモデルを表示する
        initCustomComponents();
        bindModelToView();

        // EDT からコールされている
        int width = getWidth();
        int height = getHeight();
        Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
        int n = GlobalConstants.isMac() ? 3 : 2;
        int left = (screen.width - width) / 2;
        int top = (screen.height - height) / n;
        setLocation(left, top);
        setVisible(true);
    }

    /**
     * 認証が成功したかどうかを返す。
     * @return true 認証が成功した場合
     */
    public LoginStatus getResult() {
        return result;
    }

    /**
     * PropertyChange で結果を受け取るアプリに通知する。
     * @param result true 認証が成功した場合
     */
    private void notifyResult(final LoginStatus ret) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                boundSupport.firePropertyChange("LOGIN_PROP", -100, ret);
            }
        });
    }

    /**
     * 認証を試みる。
     * JBoss の DatabaseLoginModule を使用しているため、UserValueが取得できた場合に認証が成功したとみなす。
     * 詳細はBusiness Delegater へ委譲。
     */
    public void tryLogin() {
        //「現在ログイン中です」を表示
        stLabel.setText("現在ログイン中です.");

        // User 情報を取得するためのデリゲータを得る
        if (userDlg == null) {
            userDlg = new RemoteUserDelegater();
        }

        // トライ出来る最大回数を得る
        if (maxTryCount == 0) {
            maxTryCount = 5;
        }

        // 試行回数 += 1
        tryCount++;

        // userIdとpasswordを取得する
        bindViewToModel();
        final String password = new String(passwordField.getPassword());

        ApplicationContext appCtx = GlobalConstants.getApplicationContext();
        Application app = appCtx.getApplication();

        Task task = new Task<UserModel, Void>(app) {

            @Override
            protected UserModel doInBackground() throws Exception {

                UserModel userModel = userDlg.login(principal, password);
                if (!onLogin(userModel)) {
                    throw new LoginException();
                }
                return userModel;
            }

            @Override
            protected void succeeded(UserModel userModel) {

                if (userModel != null) {
                    // ユーザID、施設ID、ユーザモデルを rojectStub へ保存する
                    GlobalVariables.setUserId(principal.getUserId());
                    GlobalVariables.setUserModel(userModel);
                    GlobalVariables.setDolphinPrincipal(principal);
                    result = LoginStatus.AUTHENTICATED;
                    notifyClose(result);
                }
            }

            @Override
            protected void cancelled() {
            }

            @Override
            protected void failed(java.lang.Throwable cause) {
                stLabel.setText("  ");
                if (tryCount <= maxTryCount && cause instanceof Exception) {
                    userDlg.dispatchError(this.getClass(), (Exception) cause, "");
                    String errMsg = userDlg.getErrorMessage();
                    showMessageDialog(errMsg);
                } else {
                    showMessageDialog("ログイン回数が上限に達したためログインプロセスを終了します。"); //GlobalVariables.getString("loginDialog.forceClose"));
                    result = LoginStatus.NOT_AUTHENTICATED;
                    notifyClose(result);
                }
                return;
            }

            @Override
            protected void interrupted(java.lang.InterruptedException e) {
            }
        };

        final TaskMonitor taskMonitor = appCtx.getTaskMonitor();
        taskMonitor.addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent e) {
                String propertyName = e.getPropertyName();
                if ("started".equals(propertyName)) {
                    setBusy(true);
                } else if ("done".equals(propertyName)) {
                    setBusy(false);
                    taskMonitor.removePropertyChangeListener(this);
                }
            }
        });

        appCtx.getTaskService().execute(task);
    }

    private boolean onLogin(UserModel userModel) {
        try {
            for (IPlugin plugin : plugins.values()) {
                PluginWrapper pluginWrapper = new PluginWrapper(plugin);
                RequestObject request = new RequestObject();

                request.setUserId(userModel.getId());
                request.setUserName(userModel.getSirName());
                request.setCommand(Command.LOGIN);
                Object responce = pluginWrapper.message(request);
                if (responce != null) {
                    if (pluginWrapper.IsDispatched()) {
                                 pluginWrapper.update(result);
                        return true;
                    }
                }
            }
        } catch (Exception e) {
            LogWriter.error(getClass(), e);
        }
        return true;
    }

    /**
     * データベースアクセス中の処理を行う。
     */
    private void setBusy(boolean busy) {
        if (busy) {
            blockGlass.block();
            userIdField.setEnabled(false);
            passwordField.setEnabled(false);
            settingBtn.setEnabled(false);
            loginBtn.setEnabled(false);
            cancelBtn.setEnabled(false);
        } else {
            userIdField.setEnabled(true);
            passwordField.setEnabled(true);
            settingBtn.setEnabled(true);
            loginBtn.setEnabled(true);
            cancelBtn.setEnabled(true);
            blockGlass.unblock();
        }
    }

    /**
     * 警告メッセージを表示する。
     * @param msg 表示するメッセージ
     */
    private void showMessageDialog(String msg) {
        String title = getTitle();
        JOptionPane.showMessageDialog(null, msg, title, JOptionPane.WARNING_MESSAGE);
    }

    /**
     * ログインダイアログを終了する。
     * @param result
     */
    private void notifyClose(LoginStatus result) {
        setVisible(false);
        dispose();
        notifyResult(result);
    }

    /**
     * GUI を構築する。
     */
    private void initCustomComponents() {
        setTitle("ログイン-OpenDolphin-1.9");
        getRootPane().setDefaultButton(loginBtn);
        blockGlass = new BlockGlass();
        setGlassPane(blockGlass);
        // イベント接続を行う
        connect();
    }

    /**
     * イベント接続を行う。
     */
    private void connect() {

        // Mediator ライクな StateMgr
        stateMgr = new StateMgr();

        // フィールドにリスナを登録する
        //    JTextField _userIdField = getUserIdField();
        DocumentListener dl = new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent e) {
                stateMgr.checkButtons();
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                stateMgr.checkButtons();
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                stateMgr.checkButtons();
            }
        };

        userIdField.getDocument().addDocumentListener(dl);

        userIdField.addFocusListener(AutoRomanListener.getInstance());

        userIdField.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                stateMgr.onUserIdAction();
            }
        });

        //   JPasswordField passwdField = passwordField;
        DocumentListener d2 = new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent e) {
                stateMgr.checkButtons();
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                stateMgr.checkButtons();
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                stateMgr.checkButtons();
            }
        };

        passwordField.getDocument().addDocumentListener(d2);

        passwordField.addFocusListener(AutoRomanListener.getInstance());

        passwordField.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                stateMgr.onPasswordAction();
            }
        });

        // ボタンに ActionListener を登録する
        settingBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                doSettingDialog();
            }
        });

        cancelBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                doCancel();
            }
        });

        loginBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                tryLogin();
            }
        });

        loginBtn.setEnabled(false);

        addWindowListener(stateMgr);
    }

    /**
     * モデルを表示する。
     */
    private void bindModelToView() {
        if (principal.getUserId() != null && (!principal.getUserId().equals(""))) {
            userIdField.setText(principal.getUserId());
        }
    }

    /**
     * モデル値を取得する。
     */
    private void bindViewToModel() {
        String id = userIdField.getText().trim();
        if (!id.equals("")) {
            principal.setUserId(id);
        }
    }

    /**
     * 設定ボタンがおされた時、設定画面を開始する。
     */
    public void doSettingDialog() {
        blockGlass.block();
        //  SettingDialog sd = new SettingDialog(null, true, plugins);
        //  sd.setVisible(true);
        ProjectSettingDialog sd = new ProjectSettingDialog(plugins);
        PropertyChangeListener pl = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                setNewParams((Boolean) evt.getNewValue());
            }
        };

        sd.addPropertyChangeListener("SETTING_PROP", pl);
        sd.setLoginState(false);
        sd.start();
        //    sd.setVisible(true);
    }

    /**
     * 設定ダイアログから通知を受ける。
     * 有効なプロジェクトでればユーザIDをフィールドに設定しパスワードフィールドにフォーカスする。
     **/
    public void setNewParams(Boolean newValue) {
        blockGlass.unblock();
        boolean valid = newValue.booleanValue();
        if (valid) {
            principal.setUserId(GlobalVariables.getUserId());
            principal.setFacilityId(GlobalVariables.getFacilityId());
            bindModelToView();
            passwordField.requestFocus();
        }
    }

    /**
     * ログインをキャンセルする。
     */
    public void doCancel() {
        setVisible(false);
        dispose();
        result = LoginStatus.CANCELD;
        notifyResult(result);
    }

    /**
     * ログインボタンを制御する簡易 StateMgr クラス。
     */
    class StateMgr extends WindowAdapter {

        public StateMgr() {
        }

        /**
         * ログインボタンの enable/disable を制御する。
         */
        public void checkButtons() {
            boolean userEmpty = (boolean) (userIdField.getText().length() == 0);
            boolean passwdEmpty = (boolean) (passwordField.getPassword().length == 0);
            if (userEmpty == false && passwdEmpty == false) {
                loginBtn.setEnabled(true);
            } else {
                loginBtn.setEnabled(false);
            }
        }

        /**
         * UserId フィールドでリターンきーが押された時の処理を行う。
         */
        public void onUserIdAction() {
            if (userIdField.getText().length() != 0) {
                if (passwordField.getPassword().length == 0) {
                    passwordField.requestFocus();
                } else {
                    loginBtn.doClick();
                }
            }
        }

        /**
         * Password フィールドでリターンきーが押された時の処理を行う。
         */
        public void onPasswordAction() {
            if (userIdField.getText().equals("")) {
                userIdField.requestFocus();
            } else if (passwordField.getPassword().length != 0) {
                // ログインボタンをクリックする
                loginBtn.doClick();
            }
        }

        @Override
        public void windowClosing(WindowEvent e) {
            doCancel();
        }

        @Override
        public void windowOpened(WindowEvent e) {
            if (!userIdField.getText().trim().equals("")) {
                // UserId に有効な値が設定されていれば
                // パスワードフィールドにフォーカスする
                passwordField.requestFocus();
            }
        }
    }
}
