/*******************************************************************************
 * Copyright 2002-2015, OpenNebula Project (OpenNebula.org), C12G Labs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package org.opennebula.client;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;


/**
 * This class represents the connection with the core and handles the
 * xml-rpc calls.
 *
 */
public class Client{

    //--------------------------------------------------------------------------
    //  PUBLIC INTERFACE
    //--------------------------------------------------------------------------

    /**
     * Creates a new xml-rpc client with default options: the auth. file will be
     * assumed to be at $ONE_AUTH, and the endpoint will be set to $ONE_XMLRPC. <br/>
     * It is the equivalent of Client(null, null).
     *
     * @throws ClientConfigurationException
     *             if the default configuration options are invalid.
     */
    public Client() throws ClientConfigurationException
    {
        setOneAuth(null);
        setOneEndPoint(null);
    }

    /**
     * Creates a new xml-rpc client with specified options.
     *
     * @param secret
     *            A string containing the ONE user:password tuple. Can be null
     * @param endpoint
     *            Where the rpc server is listening, must be something like
     *            "http://localhost:2633/RPC2". Can be null
     * @throws ClientConfigurationException
     *             if the configuration options are invalid
     */
    public Client(String secret, String endpoint)
            throws ClientConfigurationException
    {
        setOneAuth(secret);
        setOneEndPoint(endpoint);
    }

    /**
     * Performs an XML-RPC call.
     *
     * @param action ONE action
     * @param args ONE arguments
     * @return The server's xml-rpc response encapsulated
     */
    public OneResponse call(String action, Object...args)
    {
        boolean success = false;
        String  msg = null;

        try
        {
            Object[] params = new Object[args.length + 1];

            params[0] = oneAuth;

            for(int i=0; i<args.length; i++)
                params[i+1] = args[i];

            Object[] result = (Object[]) client.execute("one."+action, params);

            success = (Boolean) result[0];

            // In some cases, the xml-rpc response only has a boolean
            // OUT parameter
            if(result.length > 1)
            {
                try
                {
                    msg = (String) result[1];
                }
                catch (ClassCastException e)
                {
                    // The result may be an Integer
                    msg = ((Integer) result[1]).toString();
                }
            }
        }
        catch (XmlRpcException e)
        {
            msg = e.getMessage();
        }

        return new OneResponse(success, msg);
    }

    /**
     * Calls OpenNebula and retrieves oned version
     *
     * @return The server's xml-rpc response encapsulated
     */
    public OneResponse get_version()
    {
        return call("system.version");
    }

    //--------------------------------------------------------------------------
    //  PRIVATE ATTRIBUTES AND METHODS
    //--------------------------------------------------------------------------

    private String       oneAuth;
    private String       oneEndPoint;

    private XmlRpcClient client;

    private void setOneAuth(String secret) throws ClientConfigurationException
    {
        oneAuth = secret;

        try
        {
            if(oneAuth == null)
            {
                String oneAuthEnv = System.getenv("ONE_AUTH");
                File   authFile;

                if ( oneAuthEnv != null && oneAuthEnv.length() != 0)
                {
                    authFile = new File(oneAuthEnv);
                }
                else
                {
                    authFile = new File(System.getenv("HOME")+"/.one/one_auth");
                }

                oneAuth =
                      (new BufferedReader(new FileReader(authFile))).readLine();
            }

            oneAuth = oneAuth.trim();
        }
        catch (FileNotFoundException e)
        {
            // This comes first, since it is a special case of IOException
            throw new ClientConfigurationException("ONE_AUTH file not present");
        }
        catch (IOException e)
        {
            // You could have the file but for some reason the program can not
            // read it
            throw new ClientConfigurationException("ONE_AUTH file unreadable");
        }
    }

    private void setOneEndPoint(String endpoint)
            throws ClientConfigurationException
    {
        oneEndPoint = "http://localhost:2633/RPC2";

        if(endpoint != null)
        {
            oneEndPoint = endpoint;
        }
        else
        {
            String oneXmlRpcEnv = System.getenv("ONE_XMLRPC");

            if ( oneXmlRpcEnv != null && oneXmlRpcEnv.length() != 0 )
            {
                oneEndPoint = oneXmlRpcEnv;
            }
        }

        XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();

        try
        {
            config.setServerURL(new URL(oneEndPoint));
        }
        catch (MalformedURLException e)
        {
            throw new ClientConfigurationException(
                    "The URL "+oneEndPoint+" is malformed.");
        }

        client = new XmlRpcClient();
        client.setConfig(config);
    }
}
