/**
 * Copyright (c) 2006 - 2010 Smaxe Ltd (www.smaxe.com).
 * All rights reserved.
 */

import com.smaxe.me.core.LinkedHashtable;
import com.smaxe.me.io.Externalizable;
import com.smaxe.me.io.ObjectInput;
import com.smaxe.me.io.ObjectOutput;
import com.smaxe.me.uv.ObjectEncoding;
import com.smaxe.me.uv.Responder;
import com.smaxe.me.uv.amf.ClassObject;
import com.smaxe.me.uv.amf.IObjectCreator;
import com.smaxe.me.uv.amf.support.ObjectCreator;
import com.smaxe.me.uv.amf3.ArrayCollection;
import com.smaxe.me.uv.client.License;
import com.smaxe.me.uv.remoting.ds.AmfConnection;
import com.smaxe.me.uv.remoting.ds.RemotingResponder;
import com.smaxe.me.uv.remoting.ds.messages.AbstractMessage;
import com.smaxe.me.uv.remoting.ds.messages.RemotingMessage;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;

/**
 * <code>ExMeAmfConnection</code> - {@link AmfConnection} usage example.
 * <p> Note:
 * <br> The example is based on Samples applications provided with BlazeDS server.
 * 
 * @author Andrei Sochirca
 */
public final class ExMeAmfConnection extends Object
{
    /**
     * Entry point.
     * 
     * @param args
     * @throws Exception if an exception occurred
     */
    public static void main(final String[] args) throws Exception
    {
        // NOTE:
        // you can get Evaluation Key at:
        // http://www.smaxe.com/order.jsf#request_evaluation_key
        // or buy at:
        // http://www.smaxe.com/order.jsf
        License.setKey("SET-YOUR-KEY");
        
        final String url = "http://localhost:8400/samples/messagebroker/amf";
        
        
        Hashtable configuration = new Hashtable();
        
//        configuration.put(AmfConnection.Configuration.USE_HTTP_CONNECTION, new Boolean(false));
        configuration.put(AmfConnection.Configuration.OBJECT_CREATOR, new CustomObjectCreator());
        
        AmfConnection connection = new AmfConnection(configuration);
        
        connection.objectEncoding(ObjectEncoding.AMF3);
        
        connection.addEventListener(new AmfConnection.IListener()
        {
            public void onIOError(AmfConnection connection, final String message)
            {
                System.out.println("AmfConnection$IListener#onIOError: " + connection.uri() + " " + message);
            }
            
            public void onConnect(AmfConnection connection, final String id)
            {
                System.out.println("AmfConnection$IListener#onConnect: " + connection.uri() + " " + id);
            }
            
            public void onDisconnect(AmfConnection connection)
            {
                disconnected = true;
                
                System.out.println("AmfConnection$IListener#onDisconnect: " + connection.uri());
            }
        });
        
        connection.connect(url, new Object[] {});
        
        // wait till connected
        while (!connection.connected() && !disconnected)
        {
            try
            {
                Thread.sleep(100);
            }
            catch (Exception e) {/*ignore*/}
        }
        
        if (!disconnected)
        {
            connection.call("my-amf" /*endpoint*/, "runtime-employee-ro" /*destination*/,
                    "getEmployees", new Responder()
            {
                public void onResult(final Object result)
                {
                    System.out.println("getEmployees() result: " + result);
                    
                    if (result instanceof ArrayCollection)
                    {
                        Enumeration e = ((ArrayCollection) result).array.elements();
                        
                        while (e.hasMoreElements())
                        {
                            final Object o = e.nextElement();
                            
                            System.out.println(o);
                        }
                    }
                }
                
                public void onStatus(final Hashtable status)
                {
                    System.out.println("getEmployees() status: " + status);
                }
            }, new Object[] {});
            
            
            // other way to call remote method is using RemotingMessage instance
            
            RemotingMessage call = new RemotingMessage();
            
            call.messageId = AmfConnection.createUUID();
            call.destination = "runtime-employee-ro";
            call.operation = "getEmployees";
            call.body = new Object[0]; // getEmployees() arguments
            
            call.headers.put(RemotingMessage.ENDPOINT_HEADER, "my-amf");
            // NOTE:
            // call.headers.put(RemotingMessage.FLEX_CLIENT_ID_HEADER, <id>);
            // this ID is added by AmfConnection class before sending to the server 
            
            connection.sendMessage(call, new RemotingResponder()
            {
                public void onMessage(final AbstractMessage message)
                {
                    System.out.println("getEmployees() message: " + message);
                }
                
                // Responder implementation
                
                public void onResult(final Object result)
                {
                    System.out.println("getEmployees() result: " + result);
                    
                    if (result instanceof ArrayCollection)
                    {
                        Enumeration e = ((ArrayCollection) result).array.elements();
                        
                        while (e.hasMoreElements())
                        {
                            final Object o = e.nextElement();
                            
                            System.out.println(o);
                        }
                    }
                }
                
                public void onStatus(final Hashtable status)
                {
                    System.out.println("getEmployees() status: " + status);
                }
            });
        }
        
        try
        {
            Thread.sleep(2 * 1000);
        }
        catch (Exception e) {/*ignore*/}
        
        connection.close();
    }
    
    private static boolean disconnected = false;
    
    /**
     * <code>Employee</code> - employee class.
     */
    public static class Employee extends Object
    {
        // fields
        /**
         * employee id.
         */
        public int employeeId;
        /**
         * first name.
         */
        public String firstName;
        /**
         * last name.
         */
        public String lastName;
        /**
         * company.
         */
        public Company company;
        /**
         * title.
         */
        public String title;
        /**
         * phone number.
         */
        public String phone;
        /**
         * email address.
         */
        public String email;
        
        /**
         * Constructor.
         */
        public Employee()
        {
        }
        
        @Override
        public String toString()
        {
            return "Employee [" +
                "employeeId=" + employeeId + ", " +
                "firstName=" + firstName + ", " +
                "lastName=" + lastName + ", " +
                "company=" + company + ", " +
                "title=" + title + ", " +
                "phone=" + phone + ", " +
                "email=" + email + 
                "]";
        }
    }
    
    /**
     * <code>Company</code> - company class.
     */
    public static class Company extends Object
    {
        // fields
        /**
         * company id.
         */
        public int companyId;
        /**
         * company name.
         */
        public String name;
        /**
         * industry.
         */
        public String industry;
        /**
         * state.
         */
        public String state;
        /**
         * city.
         */
        public String city;
        /**
         * zip code.
         */
        public String zip;
        /**
         * address.
         */
        public String address;
        
        /**
         * Constructor.
         */
        public Company()
        {
        }
        
        @Override
        public String toString()
        {
            return "Company [" +
                "companyId=" + companyId + ", " +
                "name=" + name + ", " +
                "industry=" + industry + ", " +
                "state=" + state + ", " +
                "city=" + city + ", " +
                "zip=" + zip + ", " +
                "address=" + address + 
                "]";
        }
    }
    
    /**
     * <code>Money</code> - money.
     */
    public static class Money extends Object implements Externalizable
    {
        // fields
        private String currency;
        private long amount;
        
        /**
         * Constructor.
         */
        public Money()
        {
        }
        
        // Externalizable implementation
        
        public void readExternal(ObjectInput in) throws IOException
        {
            currency = in.readUTF();
            amount = in.readLong();
        }
        
        public void writeExternal(ObjectOutput out) throws IOException
        {
            out.writeUTF(currency);
            out.writeLong(amount);
        }
    }
    
    /**
     * <code>CustomObjectCreator</code> - custom {@link IObjectCreator} implementation.
     */
    public static class CustomObjectCreator extends Object implements IObjectCreator
    {
        // fields
        private IObjectCreator defaultObjectCreator = new ObjectCreator();
        
        /**
         * Constructor.
         */
        public CustomObjectCreator()
        {
        }
        
        // IObjectCreator implementation
        
        public ClassObject toClassObject(Object o)
        {
            if (o instanceof Employee)
            {
                final Employee employee = (Employee) o;
                
                LinkedHashtable h = new LinkedHashtable();
                
                h.put("employeeId", new Integer(employee.employeeId));
                h.put("firstName", employee.firstName);
                h.put("lastName", employee.lastName);
                h.put("company", employee.company);
                h.put("title", employee.title);
                h.put("phone", employee.phone);
                h.put("email", employee.email);
                
                return new ClassObject("flex.samples.crm.employee.Employee", h);
            }
            else
            if (o instanceof Company)
            {
                final Company company = (Company) o;
                
                LinkedHashtable h = new LinkedHashtable();
                
                h.put("companyId", new Integer(company.companyId));
                h.put("name", company.name);
                h.put("industry", company.industry);
                h.put("state", company.state);
                h.put("city", company.city);
                h.put("zip", company.zip);
                h.put("address", company.address);
                
                return new ClassObject("flex.samples.crm.company.Company", h);
            }
            else
            {
                return defaultObjectCreator.toClassObject(o);
            }
        }
        
        public Object toObject(ClassObject co)
        {
            if ("flex.samples.crm.employee.Employee".equals(co.className))
            {
                Employee employee = new Employee();
                
                employee.employeeId = ((Integer) co.propertyValues.get("employeeId")).intValue();
                employee.firstName = (String) co.propertyValues.get("firstName");
                employee.lastName = (String) co.propertyValues.get("lastName");
                employee.company = (Company) co.propertyValues.get("company");
                employee.title = (String) co.propertyValues.get("title");
                employee.phone = (String) co.propertyValues.get("phone");
                employee.email = (String) co.propertyValues.get("email");
                
                return employee;
            }
            else
            if ("flex.samples.crm.company.Company".equals(co.className))
            {
                Company company = new Company();
                
                company.companyId = ((Integer) co.propertyValues.get("companyId")).intValue();
                company.name = (String) co.propertyValues.get("name");
                company.industry = (String) co.propertyValues.get("industry");
                company.state = (String) co.propertyValues.get("state");
                company.city = (String) co.propertyValues.get("city");
                company.zip = (String) co.propertyValues.get("zip");
                company.address = (String) co.propertyValues.get("address");
                
                return company;
            }
            else
            {
                return defaultObjectCreator.toObject(co);
            }
        }
        
        public String getClassName(Externalizable e)
        {
            if (e instanceof Money)
            {
                return "Money";
            }
            else
            {
                return defaultObjectCreator.getClassName(e);
            }
        }
        
        public Externalizable getExternalizable(String className)
        {
            if ("Money".equals(className))
            {
                return new Money();
            }
            else
            {
                return defaultObjectCreator.getExternalizable(className);
            }
        }
    }
}