/**
 * Copyright (C) 2000-2010 Atomikos <info@atomikos.com>
 *
 * This code ("Atomikos TransactionsEssentials"), by itself,
 * is being distributed under the
 * Apache License, Version 2.0 ("License"), a copy of which may be found at
 * http://www.atomikos.com/licenses/apache-license-2.0.txt .
 * You may not use this file except in compliance with the License.
 *
 * While the License grants certain patent license rights,
 * those patent license rights only extend to the use of
 * Atomikos TransactionsEssentials by itself.
 *
 * This code (Atomikos TransactionsEssentials) contains certain interfaces
 * in package (namespace) com.atomikos.icatch
 * (including com.atomikos.icatch.Participant) which, if implemented, may
 * infringe one or more patents held by Atomikos.
 * It should be appreciated that you may NOT implement such interfaces;
 * licensing to implement these interfaces must be obtained separately from Atomikos.
 *
 * 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.
 */

package com.atomikos.icatch.admin.imp;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ResourceBundle;
import java.util.Vector;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import com.atomikos.icatch.HeuristicMessage;
import com.atomikos.icatch.TxState;
import com.atomikos.icatch.admin.AdminTransaction;
import com.atomikos.icatch.admin.LogControl;
import com.atomikos.persistence.LogException;
import com.atomikos.swing.ExtensionsFileFilter;
import com.atomikos.swing.PropertiesPanel;
import com.atomikos.swing.PropertiesTableModel;
import com.atomikos.swing.PropertyListener;

/**
 * 
 * 
 * An inspection tool for viewing and editing log contents. To be used with
 * care, since editing can influence the 2PC interactions in the system!
 */

class AdminTool implements PropertyListener, ListSelectionListener
{
    private LogControl control_;
    // the tx service, null if standalone

    private JFrame frame_;

    private LocalLogAdministratorTableModel model_;

    private Vector data_;
    // the data, for easy editing of coordinator states
    // and easy inspection on selection of a row in the table

    private javax.swing.Timer timer_;
    // for refreshing table contents

    private JFileChooser fc_;
    // for keeping context of where to archive

    private PropertyListener pListener_;
    // the property listener
    // that listens on the state list on transaction inspection

    private ResourceBundle messages_;

    // locale-specific text

    /**
     * Check whether more info should be displayed for the given state. Based on
     * this value, a GUI indication will be shown that marks the row as having
     * details.
     */

    static boolean hasDetails ( int state )
    {
        boolean ret = false;
        switch ( state ) {
        case AdminTransaction.STATE_PREPARED:
            ret = true;
            break;
        case AdminTransaction.STATE_HEUR_MIXED:
            ret = true;
            break;
        case AdminTransaction.STATE_HEUR_HAZARD:
            ret = true;
            break;
        case AdminTransaction.STATE_HEUR_COMMITTED:
            ret = true;
            break;
        case AdminTransaction.STATE_HEUR_ABORTED:
            ret = true;
            break;
        case AdminTransaction.STATE_COMMITTING:
            ret = false;
            break;
        case AdminTransaction.STATE_ABORTING:
            ret = false;
            break;
        case AdminTransaction.STATE_TERMINATED:
            ret = false;
            break;

        default:
            break;
        }

        return ret;
    }

    /**
     * Convert the given int state.
     * 
     * @param state
     *            The given int state.
     * @return Object The object state, or null if not found.
     */

    static Object convertState ( int state )
    {
        Object ret = TxState.ACTIVE;

        switch ( state ) {
        case AdminTransaction.STATE_PREPARED:
            ret = TxState.IN_DOUBT;
            break;
        case AdminTransaction.STATE_HEUR_MIXED:
            ret = TxState.HEUR_MIXED;
            break;
        case AdminTransaction.STATE_HEUR_HAZARD:
            ret = TxState.HEUR_HAZARD;
            break;
        case AdminTransaction.STATE_HEUR_COMMITTED:
            ret = TxState.HEUR_COMMITTED;
            break;
        case AdminTransaction.STATE_HEUR_ABORTED:
            ret = TxState.HEUR_ABORTED;
            break;
        case AdminTransaction.STATE_COMMITTING:
            ret = TxState.COMMITTING;
            break;
        case AdminTransaction.STATE_ABORTING:
            ret = TxState.ABORTING;
            break;
        case AdminTransaction.STATE_TERMINATED:
            ret = TxState.TERMINATED;
            break;

        default:
            break;
        }

        return ret;
    }

    private static StateDescriptor getStateDescriptor ( AdminTransaction tx ,
            int heuristicState )
    {
        Object state = convertState ( heuristicState );
        HeuristicMessage[] msgs = tx.getHeuristicMessages ( heuristicState );
        return new StateDescriptor ( state, msgs );
    }

    private static Vector getStateDescriptors ( Object admintx )
    {
        AdminTransaction tx = (AdminTransaction) admintx;
        Vector ret = new Vector ();
        StateDescriptor desc = null;

        desc = getStateDescriptor ( tx, AdminTransaction.STATE_HEUR_MIXED );
        if ( !(desc.messages == null || desc.messages.length == 0) )
            ret.addElement ( desc );

        desc = getStateDescriptor ( tx, AdminTransaction.STATE_HEUR_HAZARD );
        if ( !(desc.messages == null || desc.messages.length == 0) )
            ret.addElement ( desc );

        desc = getStateDescriptor ( tx, AdminTransaction.STATE_HEUR_COMMITTED );
        if ( !(desc.messages == null || desc.messages.length == 0) )
            ret.addElement ( desc );

        desc = getStateDescriptor ( tx, AdminTransaction.STATE_HEUR_ABORTED );
        if ( !(desc.messages == null || desc.messages.length == 0) )
            ret.addElement ( desc );

        desc = getStateDescriptor ( tx, AdminTransaction.STATE_TERMINATED );
        if ( !(desc.messages == null || desc.messages.length == 0) )
            ret.addElement ( desc );

        return ret;
    }

    private static JPanel getMessagePanel ( HeuristicMessage[] msgs )
    {
        MessageTableModel model = new MessageTableModel ( msgs );
        JTable table = new JTable ( model );
        table.setPreferredScrollableViewportSize ( new Dimension ( 300, 70 ) );
        JScrollPane scrollPane = new JScrollPane ( table );
        JPanel ret = new JPanel ();
        ret.setLayout ( new BorderLayout () );
        ret.add ( scrollPane, BorderLayout.CENTER );
        return ret;
    }

    AdminTool ( LogControl control )
    {
        messages_ = ResourceBundle
                .getBundle ( "com.atomikos.icatch.admin.imp.AdminToolResourceBundle" );
        frame_ = new JFrame ( messages_.getString ( "adminToolTitle" ) );
        control_ = control;
        // add timer to reflect updated tx states
        timer_ = new javax.swing.Timer ( 7000, new ActionListener () {
            public void actionPerformed ( ActionEvent evt )
            {
                // System.out.println ( "Timer refresh event" );
                model_.refresh ();
                // System.out.println ( "Timer refresh done" );
            }
        } );
        timer_.start ();
        init ();
    }

    private void init ()
    {
        JTable table = null;
        try {
            table = getJTable ();
        } catch ( LogException e ) {
            throw new RuntimeException ( e.getMessage () );
        }
        // Create the scroll pane and add the table to it.
        JScrollPane scrollPane = new JScrollPane ( table );
        File tmpfile = new File ( "archive.txt" );
        try {
            tmpfile.createNewFile ();
        } catch ( IOException io ) {
            // file creation failed; this merely implies that no default archive
            // file exists
        }
        fc_ = new JFileChooser ( tmpfile );
        String[] extensions = { "txt" };
        fc_.setFileFilter ( new ExtensionsFileFilter ( extensions ) );
        fc_.setDialogTitle ( messages_.getString ( "appendDialogTitle" ) );
        fc_.setFileSelectionMode ( JFileChooser.FILES_ONLY );
        // Add the scroll pane to this window.
        frame_.getContentPane ().add ( scrollPane, BorderLayout.CENTER );

        frame_.addWindowListener ( new WindowAdapter () {

            public void windowClosing ( WindowEvent e )
            {
                if ( timer_ != null )
                    timer_.stop ();

            }
        } );
        frame_.pack ();
        frame_.setVisible ( true );
        pListener_ = this;
    }

    private void forget ( Object part , int row )
    {

        AdminTransaction p = (AdminTransaction) part;
        p.forceForget ();
        model_.rowDeleted ( row );

    }

    private void commit ( Object part , int row )
    {
        try {
            AdminTransaction p = (AdminTransaction) part;
            p.forceCommit ();
            model_.refresh ();
        } catch ( Exception e ) {
            e.printStackTrace ();
        }
    }

    private void rollback ( Object part , int row )
    {
        try {
            AdminTransaction p = (AdminTransaction) part;
            p.forceRollback ();
            model_.refresh ();
        } catch ( Exception e ) {
            e.printStackTrace ();
        }
    }

    private String getHeuristicDetails ( AdminTransaction p , int heuristicState )
    {
        StringBuffer ret = new StringBuffer ();
        HeuristicMessage[] msgs = p.getHeuristicMessages ( heuristicState );
        for ( int i = 0; i < msgs.length; i++ ) {
            if ( i == 0 )
                ret.append ( convertState ( heuristicState ) + ": " );
            ret.append ( msgs[i] );
            if ( i < msgs.length - 1 )
                ret.append ( " -- " );
        }
        return ret.toString ();
    }

    private String getMessages ( Object part )
    {
        HeuristicMessage[] msgs = null;
        StringBuffer ret = new StringBuffer ();

        AdminTransaction p = (AdminTransaction) part;
        Object state = convertState ( p.getState () );

        // in the general case, no distinction has to be made between different
        // outcomes of different participants.
        msgs = p.getHeuristicMessages ();

        if ( p.wasCommitted () )
            ret.append ( messages_.getString ( "commitAttemptedMessage" ) );
        else if ( p.getState () != AdminTransaction.STATE_PREPARED )
            ret.append ( messages_.getString ( "rollbackAttemptedMessage" ) );

        for ( int i = 0; i < msgs.length; i++ ) {
            ret.append ( msgs[i] );
            if ( i < msgs.length - 1 )
                ret.append ( " -- " );
        }
        if ( p.getState () == AdminTransaction.STATE_HEUR_MIXED
                || p.getState () == AdminTransaction.STATE_HEUR_HAZARD ) {

            // in this case, we should add extra information about
            // which task was in a different outcome
            ret.append ( messages_.getString ( "ofWhichMessage" ) );
            ret.append ( getHeuristicDetails ( p,
                    AdminTransaction.STATE_HEUR_COMMITTED ) );
            ret.append ( getHeuristicDetails ( p,
                    AdminTransaction.STATE_HEUR_ABORTED ) );
            ret.append ( getHeuristicDetails ( p,
                    AdminTransaction.STATE_HEUR_MIXED ) );
            ret.append ( getHeuristicDetails ( p,
                    AdminTransaction.STATE_HEUR_HAZARD ) );

        }

        return ret.toString ();

    }

    /**
     * Gets a JTable with 2 cols (1 for root ID, 1 for the global tx state). The
     * table has a row for each coordinator in the log.
     * 
     * @return JTable The table.
     */

    private JTable getJTable () throws LogException
    {
        JTable table = null;
        // this will be the returned table
        Vector coordinators = new Vector ();

        AdminTransaction[] txs = control_.getAdminTransactions ();
        if ( txs != null && txs.length > 0 ) {
            for ( int i = 0; i < txs.length; i++ ) {
                coordinators.addElement ( txs[i] );
            }
        }
        data_ = coordinators;

        LocalLogAdministratorTableModel model = new LocalLogAdministratorTableModel (
                coordinators );

        table = new JTable ( model );
        table.setSelectionMode ( ListSelectionModel.SINGLE_SELECTION );
        // only a single row can be selected at a time
        model_ = model;

        ListSelectionModel rowSM = table.getSelectionModel ();

        rowSM.addListSelectionListener ( this );

        table.setPreferredScrollableViewportSize ( new Dimension ( 500, 70 ) );

        return table;
    }

    /**
     * @see ListSelectionListener
     */

    public void valueChanged ( ListSelectionEvent e )
    {
        processEvent ( e );
    }

    /**
     * Utility method to handle display event of one particular tx.
     * 
     */

    void processEvent ( ListSelectionEvent e )
    {
        // Ignore extra messages.
        if ( e.getValueIsAdjusting () )
            return;

        try {
			ListSelectionModel lsm = (ListSelectionModel) e.getSource ();
			if ( lsm.isSelectionEmpty () ) {
			    // no rows are selected
			} else {
			    int selectedRow = lsm.getMinSelectionIndex ();
			    AdminTransaction rec = (AdminTransaction) data_
			            .elementAt ( selectedRow );
			    Object id = null;
			    Object state = null;

			    id = rec.getTid ();
			    state = convertState ( rec.getState () );

			    if ( !state.equals ( TxState.IN_DOUBT ) ) {
			        if ( hasDetails ( rec.getState () ) ) {
			            Object[] options = {
			                    messages_.getString ( "forgetNoArchiveOption" ),
			                    messages_.getString ( "forgetAndArchiveOption" ),
			                    messages_.getString ( "keepInLogOption" ) };
			            Vector descriptors = getStateDescriptors ( rec );
			            StateTableModel table = new StateTableModel ( descriptors );
			            PropertiesPanel panel = new PropertiesPanel ( table, true );
			            String outcome = null;
			            if ( rec.wasCommitted () )
			                outcome = messages_.getString ( "commitOutcomeMessage" );
			            else
			                outcome = messages_
			                        .getString ( "rollbackOutcomeMessage" );
			            panel.addPropertyListener ( pListener_ );
			            int n = JOptionPane.showOptionDialog ( frame_, panel
			                    .getPanel (), outcome + id.toString (),
			                    JOptionPane.YES_NO_CANCEL_OPTION,
			                    JOptionPane.QUESTION_MESSAGE, null, options,
			                    options[2] );

			            if ( n == JOptionPane.YES_OPTION
			                    || n == JOptionPane.NO_OPTION ) {
			                // System.out.println ( "Terminating transaction" );
			                if ( n == JOptionPane.NO_OPTION ) {
			                    // ask user where to archive
			                    fc_.setDialogTitle ( messages_
			                            .getString ( "appendDialogTitle" ) );
			                    int ret = fc_.showOpenDialog ( frame_ );
			                    if ( ret == JFileChooser.APPROVE_OPTION ) {
			                        try {
			                            File file = fc_.getSelectedFile ();
			                            FileWriter writer = new FileWriter ( file
			                                    .getPath (), true );
			                            writer
			                                    .write ( messages_
			                                            .getString ( "rootTransactionMessage" )
			                                            + id.toString () + " " );
			                            writer.write ( getMessages ( data_
			                                    .elementAt ( selectedRow ) ) );
			                            writer.write ( "\r" );
			                            writer.flush ();
			                            writer.close ();
			                            forget ( data_.elementAt ( selectedRow ),
			                                    selectedRow );
			                        } catch ( Exception err ) {
			                            err.printStackTrace ();
			                        }
			                    }
			                }

			                else {
			                    // Forget no archive
			                    forget ( data_.elementAt ( selectedRow ),
			                            selectedRow );
			                }
			            } else {
			                // CANCEL option -> just invalidate selection, to allow
			                // next pop-up to happen
			                lsm.removeSelectionInterval ( selectedRow, selectedRow );
			            }
			        } else {
			        	//no details available -> show warning for the user
			        	String message = messages_.getString ( "noDetailsAvailableMessage" );
			        	String title = messages_.getString( "noDetailsAvailableTitle" );
			        	JOptionPane.showMessageDialog (
			                    frame_ , message , title , JOptionPane.INFORMATION_MESSAGE );
			        }
			    } else {
			        // indoubt instance -> present commit/rollback/keep option
			        Object[] options = { messages_.getString ( "commitOption" ),
			                messages_.getString ( "rollbackOption" ),
			                messages_.getString ( "keepInLogOption" ) };
			        AdminTransaction tx = (AdminTransaction) data_
			                .elementAt ( selectedRow );
			        JPanel panel = getMessagePanel ( tx.getHeuristicMessages () );

			        int n = JOptionPane
			                .showOptionDialog ( frame_, panel, messages_
			                        .getString ( "selectedTransactionMessage" )
			                        + id.toString (),
			                        JOptionPane.YES_NO_CANCEL_OPTION,
			                        JOptionPane.QUESTION_MESSAGE, null, options,
			                        options[2] );

			        if ( n == JOptionPane.YES_OPTION ) {
			            commit ( data_.elementAt ( selectedRow ), selectedRow );
			        } else if ( n == JOptionPane.NO_OPTION ) {
			            rollback ( data_.elementAt ( selectedRow ), selectedRow );
			        }
			        // CANCEL option -> just invalidate selection, to allow next
			        // pop-up to happen
			        lsm.removeSelectionInterval ( selectedRow, selectedRow );
			    }

			    // clear the selection in order to allow new selection events
			    // IMPORTANT to allow re-selection events!
			    lsm.clearSelection ();
			}
		} catch ( Exception err ) {
			err.printStackTrace();
		}

    }

    /**
     * @see PropertyListener
     */

    public void newProperty ( PropertiesTableModel model )
    {
        throw new RuntimeException ( "Should not be called" );
    }

    /**
     * @see PropertyListener
     */

    public void deleteProperty ( PropertiesTableModel model , int index )
    {
        throw new RuntimeException ( "Should not be called" );
    }

    /**
     * @see PropertyListener
     */

    public void editProperty ( PropertiesTableModel model , int index )
    {
        Vector data = ((StateTableModel) model).getData ();
        StateDescriptor desc = (StateDescriptor) data.elementAt ( index );
        JPanel panel = getMessagePanel ( desc.messages );
        int answer = JOptionPane.showConfirmDialog ( frame_, panel, messages_
                .getString ( "stateDetailsTitle" ), JOptionPane.PLAIN_MESSAGE );

    }

}
