/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package memcacheapp;

import java.util.*;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import net.spy.memcached.*;
import net.spy.memcached.auth.*;
import org.apache.commons.codec.digest.DigestUtils;

/**
 *
 * @author sc-md
 */
public class MemcacheApp {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        /*
        Constructors
        ------------
        MemcachedClient(InetSocketAddress... ia) 
            Get a memcache client operating on the specified memcached locations.
            Pass comma separated list of addresses of memcache servers forming cluster.
            Locations provided as comma separated list of IP addresses forming memcached cluster
            new MemcachedClient(new java.net.InetSocketAddress("127.0.0.1", 11211))
         */
        System.out.println("Testing MemcachedClient(InetSocketAddress... ia)");
        MemcacheText("127.0.0.1", 11211);
        MemcacheText_Explicit("127.0.0.1", 11211);
        MemcacheObject("127.0.0.1", 11211);

        /*
        MemcachedClient(ConnectionFactory cf, List<InetSocketAddress> addrs) 
            Get a memcache client over the specified memcached locations.
            The ConnectionFactory is used for creating instances of MemcachedConnection and allows
            finer control of the configuration of those connections
            Pass list of addresses of memcache servers forming cluster.
         */
        System.out.println("Testing MemcachedClient(ConnectionFactory cf, List<InetSocketAddress> addrs)");
        MemcacheBinary("127.0.0.1", 11211);
        //MemcacheWithAuthorisation("User", "Password");
        MemcacheCluster(new String[]{"localhost:11211"});

        /*
        MemcachedClient(List<InetSocketAddress> addrs) 
            Get a memcache client over the specified memcached locations.
            Pass list of addresses of memcache servers forming cluster.
         */
        System.out.println("Testing MemcachedClient(List<InetSocketAddress> addrs)");
        MemcacheTextCluster(new String[]{"localhost:11211"});

        System.out.println("Asynchronous read operation");
        AsyncRead(new String[]{"localhost:11211"});

        // Example creating key for a URL
        // The key prefix is anything you like and is just used to
        // give some indication of the kind of information the
        // key represents. Seems to be standard practice
        String key = getKey("URL", "http://www.google.co.uk/books");
        System.out.printf("Url key %s\n", key);

        // Example creating key for SQL query
        String sql = "SELECT * FROM user WHERE user_id = ?";
        int userId = 22;
        String sqlKeyPrefix = String.format("SQL:%d:", userId);
        key = getKey(sqlKeyPrefix, sql);
        System.out.printf("SQL key %s\n", key);
        
        // For multiple SQL queries
        // The more processing that you can turn into a single memcached request, 
        // the better. Often you can replace several SQL queries with a single, 
        // fast, memcached lookup.
        String sql1 = "SELECT * FROM user WHERE user_id = ?";
        String sql2 = "SELECT * FROM user_preferences WHERE user_id = ?";
        key = getKey(sqlKeyPrefix, String.format("%s&%s", sql1, sql2));
        System.out.printf("SQL key %s\n", key);
        
        // You can also precompute and store HTML page fragments (partial HTML)
        // to save any time required to build them and the to access time.
        String username = "Fred";
        String partialHTML = String.format("<p>Nello %s</p>", username);
        String fragKeyPrefix = String.format("FRAG_HELLO:%d:", userId);
        key = getKey(fragKeyPrefix, partialHTML);
        System.out.printf("HTML Fragment key %s\n", key);

        BasicMemcacehSQL(new String[]{"localhost:11211"}, userId, sql); // Cache miss
        BasicMemcacehSQL(new String[]{"localhost:11211"}, userId, sql); // Cache hit
        
        
        CheckAndSet("127.0.0.1", 11211);
    }

    private static String getKey(String keyPrefix, String value) {
        return keyPrefix + DigestUtils.sha256Hex(value);
    }

    private static void MemcacheText(String ipAddress, int port) {
        try {
            // Default connection uses text protocol
            MemcachedClient client = new MemcachedClient(new java.net.InetSocketAddress(ipAddress, port));

            // Cache value "Istanbul" with key "city" and store entry for 20s
            // It is removed when this time expires.
            // Set expiry time to 0 to stop it expiring, although it will
            // still be replaced if memcache runs out of resources when
            // new values are stored.
            client.set("city", 20, "Istanbul");
            System.out.println((String) client.get("city"));
            client.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void MemcacheText_Explicit(String ipAddress, int port) {
        try {
            // Explicitly use text protocol
            ConnectionFactory factory = new ConnectionFactoryBuilder()
                    .setProtocol(ConnectionFactoryBuilder.Protocol.TEXT)
                    .build();

            MemcachedClient client = new MemcachedClient(factory,
                    AddrUtil.getAddresses(String.format("%s:%d", ipAddress, port)));

            client.set("city", 20, "Istanbul");
            System.out.println((String) client.get("city"));
            client.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void MemcacheBinary(String ipAddress, int port) {

        try {
            ConnectionFactory factory = new BinaryConnectionFactory();

            MemcachedClient client = new MemcachedClient(factory,
                    AddrUtil.getAddresses(String.format("%s:%d", ipAddress, port)));

            client.set("foo", 0, "bar");
            String value = (String) client.get("foo");
            System.out.println(value);
            client.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void MemcacheWithAuthorisation(String username, String password) {
        try {
            // Authorisation 
            AuthDescriptor ad = new AuthDescriptor(new String[]{"PLAIN"},
                    new PlainCallbackHandler(username, password));

            ConnectionFactory factory = new ConnectionFactoryBuilder()
                    .setProtocol(ConnectionFactoryBuilder.Protocol.BINARY)
                    .setAuthDescriptor(ad) // Authorisation required
                    .build();

            MemcachedClient client = new MemcachedClient(factory,
                    AddrUtil.getAddresses("localhost:11211"));
            client.set("foo", 0, "bar");
            String value = (String) client.get("foo");
            System.out.println(value);
            client.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void MemcacheObject(String ipAddress, int port) {
        try {
            // Use Binary protocol
            ConnectionFactory factory = new BinaryConnectionFactory();
            MemcachedClient client = new MemcachedClient(factory, AddrUtil.getAddresses(String.format("%s:%d", ipAddress, port)));
            Employee sample = new Employee("Ihab", 26);
            client.set("engineer", 20, sample);
            System.out.println((Employee) client.get("engineer"));
            client.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void MemcacheCluster(String[] hostNames) {
        try {
            final List<String> hostNamesList = Arrays.asList(hostNames);
            ConnectionFactory factory = new BinaryConnectionFactory();

            MemcachedClient client = new MemcachedClient(factory,
                    AddrUtil.getAddresses(hostNamesList));

            client.set("message", 0, "hello world");
            String value = (String) client.get("message");
            System.out.println(value);
            client.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void MemcacheTextCluster(String[] hostNames) {
        try {
            final List<String> hostNamesList = Arrays.asList(hostNames);
            MemcachedClient client = new MemcachedClient(AddrUtil.getAddresses(hostNamesList));

            client.set("message", 0, "hello world");
            String value = (String) client.get("message");
            System.out.println(value);
            client.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void AsyncRead(String[] hostNames) {
        try {
            final List<String> hostNamesList = Arrays.asList(hostNames);
            ConnectionFactory factory = new BinaryConnectionFactory();

            MemcachedClient client = new MemcachedClient(factory,
                    AddrUtil.getAddresses(hostNamesList));

            Employee sample = new Employee("Ihab", 26);
            client.set("engineer", 20, sample);

            Future<Object> promise = client.asyncGet("engineer");
            try {
                Employee employee = (Employee) promise.get(10, TimeUnit.SECONDS);
                System.out.println(employee);
            } catch (InterruptedException eInt) {
                eInt.printStackTrace();
            } catch (TimeoutException eTimeout) {
                promise.cancel(false);
            } catch (ExecutionException e) {
                e.printStackTrace();
            }

            client.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void BasicMemcacehSQL(String[] hostNames, int userId, String sql) {
        try {
            final List<String> hostNamesList = Arrays.asList(hostNames);
            ConnectionFactory factory = new BinaryConnectionFactory();

            MemcachedClient client = new MemcachedClient(factory,
                    AddrUtil.getAddresses(hostNamesList));

            String key = getKey(String.format("SQL:%d:", userId), sql);

            Future<Object> promise = client.asyncGet(key);
            try {
                ArrayList<Employee> cachedData = (ArrayList<Employee>) promise.get(10, TimeUnit.SECONDS);

                if (cachedData == null) // Cache Miss
                {
                    System.out.printf("Cache miss, %s [%s]\n", sql, key);
                    // Execute SQL query
                    // "SELECT * FROM user WHERE user_id = ?"
                    // FAKE Read from Database
                    ArrayList<Employee> dbData = new ArrayList<Employee>();
                    dbData.add(new Employee("Fred", 22));
                    dbData.add(new Employee("Bob", 26));

                    // Cache the data read from the database
                    client.set(key, 5, dbData);     // 0 = do not expire the cache entry, set to 5ms here for testing
                    client.flush();
                    
                    System.out.println("SQL SELECT Data");
                    for (Employee emp : dbData) {
                        System.out.println(emp);
                    }
                    
                } else {
                    System.out.println("Cache hit");
                    for (Employee emp : cachedData) {
                        System.out.println(emp);
                    }
                }

            } catch (InterruptedException eInt) {
                eInt.printStackTrace();
            } catch (TimeoutException eTimeout) {
                promise.cancel(false);
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            //System.out.println(client.get(key) != null);      
            client.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static void CheckAndSet(String ipAddress, int port)
    {
        try {
            // Use Binary protocol
            ConnectionFactory factory = new BinaryConnectionFactory();
            MemcachedClient client = new MemcachedClient(factory, AddrUtil.getAddresses(String.format("%s:%d", ipAddress, port)));
            String key = "x";
            int expiry = 20;
            boolean done = false;
            long casToken;
            CASResponse casResponse;
            done = client.set(key, expiry, 22).isDone();
            System.out.printf("Value set ready for test is %b %d\n", done, client.get(key));

            // Lock the cache entry so we can change it, and get CAS token
            // so we can change it.
            casToken = client.gets(key).getCas();
            System.out.printf("CAS token is %d\n", casToken);
            
            // Set the new data having locked the value in the cache
            casResponse = client.cas(key, casToken, expiry, 33);
            System.out.printf("CAS Response %b\n", casResponse == CASResponse.OK);
            
            // Check the new value has been set.
            System.out.printf("New value is %d\n", client.get(key));
            
            client.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }     
    }

}
