/*
 * JBoss, a division of Red Hat
 * Copyright 2012, Red Hat Middleware, LLC, and individual
 * contributors as indicated by the @authors tag. See the
 * copyright.txt in the distribution for a full listing of
 * individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.gatein.sso.plugin;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;

import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

/**
 * Object, which runs on SSO server side (CAS, OpenAM) and is used to help with connection to GateIn via REST
 *
 * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
 */
public class RestCallbackCaller
{
   private static final Log log = ExoLogger.getLogger(RestCallbackCaller.class);

   private static final String ENCODING_CHARSET = "UTF-8";

   // If true, we use "https". If false, we use "http"
   private final String protocol;

   // Host (Something like "localhost")
   private final String host;

   // Port (Something like "8080")
   private final String port;

   // Something like "portal"
   private final String pathContext;

   // If true we use "POST" method. If false we use "GET" method
   private final boolean isPostHttpMethod;

   public RestCallbackCaller(String protocol,
                             String host,
                             String port,
                             String pathContext,
                             String httpMethod)
   {
      if (host == null || port == null || pathContext == null)
      {
         throw new IllegalArgumentException("Host, port and context are mandatory, but some of them is not available in configuration. host="
               + host + ", port=" + port + ", context=" + pathContext);
      }

      this.host = host;
      this.port = port;
      this.pathContext = pathContext;

      this.isPostHttpMethod = isPostHttpMethod(httpMethod);

      // Default to http if not provided
      if (protocol == null)
      {
         protocol = "http";
      }
      this.protocol = protocol;


      log.info("RestCallbackCaller initialized: " + this);
   }

    /**
     * @param callbackURL expected in format like "http://www.sp.com:8080/portal"
     * @param httpMethod
     */
   public RestCallbackCaller(String callbackURL, String httpMethod)
   {
       try
       {
           URL url = new URL(callbackURL);
           this.protocol = url.getProtocol();
           this.host = url.getHost();

           int port = url.getPort();
           if (port == -1) {
               port = 80;
           }
           this.port = String.valueOf(port);

           String path = url.getPath();
           if (path.startsWith("/")) {
               path = path.substring(1);
           }
           if (path.endsWith("/")) {
               path = path.substring(0, path.length() - 1);
           }
           this.pathContext = path;

           this.isPostHttpMethod = isPostHttpMethod(httpMethod);
       }
       catch (MalformedURLException mfe)
       {
           throw new IllegalArgumentException("Bad URL format: " + callbackURL, mfe);
       }

       log.debug("RestCallbackCaller initialized: " + this);
   }

   private boolean isPostHttpMethod(String httpMethod)
   {
       boolean isPostHttpMethod;

       // Default to GET method if not provided
       if (httpMethod == null || "GET".equalsIgnoreCase(httpMethod))
       {
           isPostHttpMethod = false;
       }
       else if ("POST".equalsIgnoreCase(httpMethod))
       {
           isPostHttpMethod = true;
       }
       else
       {
           throw new IllegalArgumentException("Illegal httpMethod: " + httpMethod + ". Only GET or POST are allowed");
       }
       return isPostHttpMethod;
   }

   public boolean executeRemoteCall(String username, String password) throws Exception
   {
      try
      {
         HttpResponseContext httpResponse = sendPortalCallbackRequest(username, password);

         int status = httpResponse.getResponseCode();
         String response = httpResponse.getResponse();

         switch (status)
         {
            case 200:
               if (response.equals(Boolean.TRUE.toString()))
               {
                  if (log.isTraceEnabled())
                  {
                     log.trace("User " + username + " authenticated successfully via Rest callback!");
                  }
                  return true;
               }
               break;
         }

         log.debug("Authentication failed for user " + username + ". HTTP status: " + status + ", HTTP response: " + response);
         return false;
      }
      catch (Exception e)
      {
         log.warn("Can't authenticate because of error: " + e.getMessage());
         e.printStackTrace();
         throw e;
      }
   }

   private HttpResponseContext sendPortalCallbackRequest(String username, String password) throws IOException
   {
      String requestURL = null;
      String queryString = null;

      if (isPostHttpMethod)
      {
         StringBuilder builder = new StringBuilder(this.protocol).append("://");
         builder.append(this.host).append(":").append(this.port);

         // Don't append portal context for now. We need the request to be served by rest.war application on portal side
         // because here we can't call request.getParameter("something") before request is processed by RestServlet
         // builder.append("/").append(this.pathContext)
         builder.append("/rest/sso/authcallback/postauth/");

         requestURL = builder.toString();

         queryString = new StringBuilder("username=")
                 .append(URLEncoder.encode(username, ENCODING_CHARSET))
                 .append("&password=")
                 .append(URLEncoder.encode(password, ENCODING_CHARSET))
                 .toString();
      }
      else
      {
         StringBuilder builder = new StringBuilder(this.protocol).append("://");
         builder.append(this.host).append(":").append(this.port).append("/")
               .append(this.pathContext).append("/rest/sso/authcallback/auth/")
               .append(URLEncoder.encode(username, ENCODING_CHARSET))
               .append("/")
               .append(URLEncoder.encode(password, ENCODING_CHARSET));
         requestURL =  builder.toString();
      }

      if (log.isTraceEnabled())
      {
         log.trace("Rest callback URL: " + requestURL + ", query string: " + queryString + ", isPostMethod: " + isPostHttpMethod);
      }

      return sendHttpRequest(requestURL, queryString);
   }

    private HttpResponseContext sendHttpRequest(String url, String urlParameters) throws IOException
    {
        Reader reader = null;
        DataOutputStream wr = null;
        StringBuilder result = new StringBuilder();

        try
        {
            HttpURLConnection connection;

            if (isPostHttpMethod)
            {
                URL tempURL = new URL(url);
                connection = (HttpURLConnection)tempURL.openConnection();
                connection.setRequestMethod("POST");
                connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                connection.setRequestProperty("Content-Length", "" + Integer.toString(urlParameters.getBytes().length));
            }
            else
            {
                if (urlParameters != null)
                {
                   url = url + "?" + urlParameters;
                }

                URL tempURL = new URL(url);
                connection = (HttpURLConnection)tempURL.openConnection();
            }

            connection.setUseCaches (false);
            connection.setDoInput(true);

            if (isPostHttpMethod)
            {
                connection.setDoOutput(true);

                //Send request
                wr = new DataOutputStream(connection.getOutputStream ());
                wr.writeBytes(urlParameters);
                wr.flush();
            }

            int statusCode = connection.getResponseCode();

            try
            {
                reader = new InputStreamReader(connection.getInputStream());
            }
            catch (IOException ioe)
            {
                reader = new InputStreamReader(connection.getErrorStream());
            }

            char[] buffer = new char[50];
            int nrOfChars;
            while ((nrOfChars = reader.read(buffer)) != -1)
            {
                result.append(buffer, 0, nrOfChars);
            }

            String response = result.toString();
            return new HttpResponseContext(statusCode, response);
        }
        finally
        {
            if (reader != null)
            {
                reader.close();
            }
            if (wr != null) {
                wr.close();
            }
        }
    }


   @Override
   public String toString()
   {
      StringBuilder builder = new StringBuilder("RestCallbackCaller [ protocol=").append(protocol)
            .append(", host=").append(host)
            .append(", port=").append(port)
            .append(", pathContext=").append(pathContext)
            .append(", isPostMethod=").append(isPostHttpMethod)
            .append(" ]");

      return builder.toString();
   }
}
