
package org.jgroups.demos;


import org.jgroups.Address;
import org.jgroups.MembershipListener;
import org.jgroups.View;
import org.jgroups.blocks.Cache;
import org.jgroups.blocks.ReplCache;
import org.jgroups.jmx.JmxConfigurator;

import javax.management.MBeanServer;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.lang.management.ManagementFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;

/**
 * GUI demo of ReplCache
 * @author Bela Ban
 */
public class ReplCacheDemo extends JPanel implements ActionListener {
    private ReplCache<String,String> cache;
    private static final String BASENAME="replcache";

    private JFrame       frame;
    private JTabbedPane  root_pane=new JTabbedPane();
    private JTable       table;
    private JTextField   key_field=createTextField(null, 10);
    private JTextField   value_field=createTextField(null, 10);
    private JTextField   repl_count_field=createTextField("1", 3);
    private JTextField   timeout_field=createTextField("0", 5);
    private JTextField   perf_key_prefix=createTextField("key", 5);
    private JTextField   perf_num_keys=createTextField("1000", 5);
    private JTextField   perf_size=createTextField("1000", 5);
    private JTextField   perf_repl_count_field=createTextField("1", 3);
    private JTextField   perf_timeout_field=createTextField("0", 5);
    private JTextArea    status=new JTextArea("Status area", 10, 5);
    private JLabel       num_elements=new JLabel("0 elements");
    private MyTableModel model=null;


    public void actionPerformed(ActionEvent event) {
        String command = event.getActionCommand();

        if(command.equals("Put")) {
            String key=key_field.getText();
            String value=value_field.getText();
            String repl_count=repl_count_field.getText();
            String timeout=timeout_field.getText();

            if(key == null || value == null)
                return;

            if(repl_count == null)
                repl_count="1";
            if(timeout == null)
                timeout="0";

            cache.put(key, value, Short.valueOf(repl_count), Long.valueOf(timeout));
        }
        else if(command.equals("Remove")) {
            int[] rows=table.getSelectedRows();
            if(rows != null) {
                for(int row: rows) {
                    String key=(String)model.getValueAt(row, 0);
                    if(key != null)
                        cache.remove(key);
                }
            }
        }
        else if(command.equals("Clear")) {
            clear();
        }
        else if(command.equals("Rebalance")) {
            cache.mcastEntries();
        }
        else if(command.equals("Reset")) {
            status.setText("");
        }
        else if(command.equals("Start")) {
            startPerfTest();
        }
        else if(command.equals("Stop")) {

        }
        else if(command.equals("Exit")) {
            if(cache != null)
                cache.stop();
            frame.dispose();
            System.exit(1); // or can we break out of mainLoop() somehow else ?
        }
    }

    private void clear() {
        cache.clear();
    }

    private void startPerfTest() {
        int   num_puts=1000;
        int   size=1000;
        short repl_count=1;
        long  timeout=0;
        String key_prefix="key";

        String tmp=perf_key_prefix.getText();
        if(tmp != null)
            key_prefix=tmp;
        tmp=perf_num_keys.getText();
        if(tmp != null)
            num_puts=Integer.valueOf(tmp);
        tmp=perf_size.getText();
        if(tmp != null)
            size=Integer.valueOf(tmp);
        tmp=perf_repl_count_field.getText();
        if(tmp != null)
            repl_count=Short.valueOf(tmp);
        tmp=perf_timeout_field.getText();
        if(tmp != null)
            timeout=Long.valueOf(tmp);

        long start=System.currentTimeMillis();
        for(int i=0; i < num_puts; i++) {
            String key=key_prefix + "-" + i;
            String value="val-" + i;
            cache.put(key, value, repl_count,  timeout);
        }
        long diff=System.currentTimeMillis() - start;
        status.setText("It took " + diff + " ms to insert " + num_puts + " elements");
    }


    private void start(String props, String cluster_name,
                       long rpc_timeout, long caching_time, boolean migrate_data, boolean use_l1_cache,
                       int l1_max_entries, long l1_reaping_interval,
                       int l2_max_entries, long l2_reaping_interval) throws Exception {
        MBeanServer server=ManagementFactory.getPlatformMBeanServer();

        cache=new ReplCache<String,String>(props, cluster_name);
        cache.setCallTimeout(rpc_timeout);
        cache.setCachingTime(caching_time);
        cache.setMigrateData(migrate_data);
        JmxConfigurator.register(cache, server, BASENAME + ":name=cache");
        JmxConfigurator.register(cache.getL2Cache(), server, BASENAME + ":name=l2-cache");

        if(use_l1_cache) {
            Cache<String,String> l1_cache=new Cache<String,String>();
            cache.setL1Cache(l1_cache);
            if(l1_reaping_interval > 0)
                l1_cache.enableReaping(l1_reaping_interval);
            if(l1_max_entries > 0)
                l1_cache.setMaxNumberOfEntries(l1_max_entries);
            JmxConfigurator.register(cache.getL1Cache(), server, BASENAME + ":name=l1-cache");
        }

        if(l2_max_entries > 0 || l2_reaping_interval > 0) {
            Cache<String, ReplCache.Value<String>> l2_cache=cache.getL2Cache();
            if(l2_max_entries > 0)
                l2_cache.setMaxNumberOfEntries(l2_max_entries);
            if(l2_reaping_interval > 0)
                l2_cache.enableReaping(l2_reaping_interval);
        }


        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                cache.stop();
            }
        });

        cache.start();

        model=new MyTableModel<String,String>();
        model.setMap(cache.getL2Cache().getInternalMap());
        cache.addChangeListener(model);

        frame=new JFrame("ReplCacheDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

        table=new MyTable(model);
        table.setPreferredScrollableViewportSize(new Dimension(500, 200));
        // table.setFillsViewportHeight(true); // JDK 6 specific
        table.setShowGrid(false);
        table.setFont(table.getFont().deriveFont(Font.BOLD));
        add(new JScrollPane(table));

        JPanel key=new JPanel(new FlowLayout(FlowLayout.LEFT));
        key.add(new JLabel("Key  "));
        key.add(key_field);
        add(key);

        JPanel value=new JPanel(new FlowLayout(FlowLayout.LEFT));
        value.add(new JLabel("Value"));
        value.add(value_field);
        add(value);

        JPanel repl_count=new JPanel(new FlowLayout(FlowLayout.LEFT));
        repl_count.add(new JLabel("Replication count"));
        repl_count.add(repl_count_field);
        add(repl_count);

        JPanel timeout=new JPanel(new FlowLayout(FlowLayout.LEFT));
        timeout.add(new JLabel("Timeout"));
        timeout.add(timeout_field);
        add(timeout);

        JPanel buttons=new JPanel();
        JButton put_button=createButton("Put");
        buttons.add(createButton("Put"));
        buttons.add(createButton("Remove"));
        buttons.add(createButton("Clear"));
        buttons.add(createButton("Rebalance"));
        buttons.add(createButton("Exit"));
        buttons.add(num_elements);
        add(buttons);
        setOpaque(true);

        root_pane.addTab("Data", this);
        JPanel perf_panel=new JPanel();
        perf_panel.setLayout(new BoxLayout(perf_panel, BoxLayout.Y_AXIS));
        perf_panel.setOpaque(true);
        root_pane.addTab("Perf test", perf_panel);

        perf_panel.add(status);
        status.setForeground(Color.BLUE);

        JPanel prefix=new JPanel(new FlowLayout(FlowLayout.LEFT));
        prefix.add(new JLabel("Key prefix"));
        prefix.add(perf_key_prefix);
        perf_panel.add(prefix);

        JPanel keys=new JPanel(new FlowLayout(FlowLayout.LEFT));
        keys.add(new JLabel("Number of keys to insert"));
        keys.add(perf_num_keys);
        perf_panel.add(keys);

        JPanel size=new JPanel(new FlowLayout(FlowLayout.LEFT));
        size.add(new JLabel("Size of each key (bytes)"));
        size.add(perf_size); size.add(new JLabel("    (ignored for now)"));
        perf_panel.add(size);

        JPanel perf_repl_count=new JPanel(new FlowLayout(FlowLayout.LEFT));
        perf_repl_count.add(new JLabel("Replication count"));
        perf_repl_count.add(perf_repl_count_field);
        perf_panel.add(perf_repl_count);

        JPanel perf_timeout=new JPanel(new FlowLayout(FlowLayout.LEFT));
        perf_timeout.add(new JLabel("Timeout"));
        perf_timeout.add(perf_timeout_field);
        perf_panel.add(perf_timeout);

        JPanel perf_buttons=new JPanel(new FlowLayout(FlowLayout.LEFT));
        perf_buttons.add(createButton("Start"));
        perf_buttons.add(createButton("Stop"));
        perf_buttons.add(createButton("Reset"));
        perf_buttons.add(createButton("Exit"));
        perf_panel.add(perf_buttons);

        frame.setContentPane(root_pane);
        frame.pack();
        frame.getRootPane().setDefaultButton(put_button);
        frame.setVisible(true);
        setTitle("ReplCacheDemo");

        cache.addMembershipListener(new MembershipListener() {
            public void viewAccepted(View new_view) {
                setTitle("ReplCacheDemo");
            }

            public void suspect(Address suspected_mbr) {
            }

            public void block() {
            }

            public void unblock() {
            }
        });
    }



    private JButton createButton(String text) {
        JButton retval=new JButton(text);
        retval.addActionListener(this);
        return retval;
    }

    private static JTextField createTextField(String name, int length) {
        JTextField retval=new JTextField(name, length);
        retval.addFocusListener(new MyFocusListener(retval));
        return retval;
    }



    public static void main(String[] args) throws Exception {
        String props="udp.xml";
        String cluster_name="replcache-cluster";
        long rpc_timeout=1500L, caching_time=30000L;
        boolean migrate_data=true, use_l1_cache=true;
        int l1_max_entries=5000, l2_max_entries=-1;
        long l1_reaping_interval=-1, l2_reaping_interval=30000L;

        for(int i=0; i < args.length; i++) {
            if(args[i].equals("-props")) {
                props=args[++i];
                continue;
            }
            if(args[i].equals("-cluster_name")) {
                cluster_name=args[++i];
                continue;
            }
            if(args[i].equals("-rpc_timeout")) {
                rpc_timeout=Long.parseLong(args[++i]);
                continue;
            }
            if(args[i].equals("-caching_time")) {
                caching_time=Long.parseLong(args[++i]);
                continue;
            }
            if(args[i].equals("-migrate_data")) {
                migrate_data=Boolean.parseBoolean(args[++i]);
                continue;
            }
            if(args[i].equals("-use_l1_cache")) {
                use_l1_cache=Boolean.parseBoolean(args[++i]);
                continue;
            }
            if(args[i].equals("-l1_max_entries")) {
                l1_max_entries=Integer.parseInt(args[++i]);
                continue;
            }
            if(args[i].equals("-l1_reaping_interval")) {
                l1_reaping_interval=Long.parseLong(args[++i]);
                continue;
            }
            if(args[i].equals("-l2_max_entries")) {
                l2_max_entries=Integer.parseInt(args[++i]);
                continue;
            }
            if(args[i].equals("-l2_reaping_interval")) {
                l2_reaping_interval=Long.parseLong(args[++i]);
                continue;
            }

            help();
            return;
        }

        ReplCacheDemo demo = new ReplCacheDemo();
        demo.start(props, cluster_name, rpc_timeout, caching_time,
                   migrate_data, use_l1_cache, l1_max_entries, l1_reaping_interval,
                   l2_max_entries, l2_reaping_interval);
    }


    void setTitle(String title) {
        String local_addr=cache != null? cache.getLocalAddressAsString() : null;
        int num_nodes=cache != null? cache.getClusterSize() : 0;
        frame.setTitle(title + ": " + local_addr + " (" + num_nodes + ")");
    }


    private static void help() {
        System.out.println("ReplCacheServer [-help] [-props <props>] [-cluster_name <name>]" +
                "[-rpc_timeout <ms>] [-caching_time <ms>] " +
                "[-migrate_data <true|false>] [-use_l1_cache <true|false>] " +
                "[-l1_max_entries <num>] [-l1_reaping_interval <ms>] " +
                "[-l2_max_entries <num>] [-l2_reaping_interval <ms>] ");
    }



    private static class MyFocusListener implements FocusListener {
        private final JTextField field;

        public MyFocusListener(JTextField field) {
            this.field=field;
        }

        public void focusGained(FocusEvent e) {
            String value=field.getText();
            if(value != null && value.length() > 0) {
                field.selectAll();
            }
        }

        public void focusLost(FocusEvent e) {
        }
    }

    private static class MyTable extends JTable {

        private MyTable(TableModel dm) {
            super(dm);
        }

        public boolean getScrollableTracksViewportHeight() {
            Container viewport=getParent();
            return viewport instanceof JViewport && getPreferredSize().height < viewport.getHeight();
        }

    }

    private class MyTableModel<K,V> extends AbstractTableModel implements ReplCache.ChangeListener {
        private ConcurrentMap<K, Cache.Value<ReplCache.Value<V>>> map;
        private final String[] columnNames = {"Key", "Value", "Replication Count", "Timeout"};
        private static final long serialVersionUID=1314724464389654329L;

        public void setMap(ConcurrentMap<K, Cache.Value<ReplCache.Value<V>>> map) {
            this.map=map;
        }

        public int getColumnCount() {
            return columnNames.length;
        }

        public int getRowCount() {
            return map.size();
        }

        public String getColumnName(int col) {
            return columnNames[col];
        }


        public Object getValueAt(int row, int col) {
            int count=0;

            for(Map.Entry<K,Cache.Value<ReplCache.Value<V>>> entry: map.entrySet()) {
                if(count++ >= row) {
                    K key=entry.getKey();
                    Cache.Value<ReplCache.Value<V>> val=entry.getValue();
                    ReplCache.Value<V> tmp=val.getValue();
                    switch(col) {
                        case 0:  return key;
                        case 1:
                            V value=tmp.getVal();
                            return value instanceof byte[]? ((byte[])value).length + " bytes" : value;
                        case 2:  return tmp.getReplicationCount();
                        case 3:  return val.getTimeout();
                        default: return "n/a";
                    }
                }
            }
            throw new IllegalArgumentException("row=" + row + ", col=" + col);

        }

        public void changed() {
            fireTableDataChanged();
            num_elements.setText(cache.getL2Cache().getSize() + " elements");
        }
    }
    

}
