/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2011, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file 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.jboss.as.test.integration.security.common;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.client.OperationBuilder;
import org.jboss.as.test.integration.security.loginmodules.common.Coding;
import org.jboss.dmr.ModelNode;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.util.Base64;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
import static org.jboss.as.security.Constants.SECURITY_DOMAIN;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * @author Jan Lanik
 *         <p/>
 *         Common utilities for login modules tests.
 */
public class Utils {

   public static String hash(String target, String algorithm, Coding coding) {
      MessageDigest md = null;
      try {
         md = MessageDigest.getInstance(algorithm);
      } catch (Exception e) {
         e.printStackTrace();
      }
      byte[] bytes = target.getBytes();
      byte[] byteHash = md.digest(bytes);

      String encodedHash = null;

      switch (coding) {
         case BASE_64:
            encodedHash = Base64.encodeBytes(byteHash);
            break;
         case HEX:
            encodedHash = toHex(byteHash);
            break;
         default:
            throw new IllegalArgumentException("Unsuported coding:" + coding.name());
      }

      return encodedHash;
   }


   public static String toHex(byte[] bytes) {
      StringBuffer sb = new StringBuffer(bytes.length * 2);
      for (int i = 0; i < bytes.length; i++) {
         byte b = bytes[i];
         // top 4 bits
         char c = (char) ((b >> 4) & 0xf);
         if (c > 9)
            c = (char) ((c - 10) + 'a');
         else
            c = (char) (c + '0');
         sb.append(c);
         // bottom 4 bits
         c = (char) (b & 0xf);
         if (c > 9)
            c = (char) ((c - 10) + 'a');
         else
            c = (char) (c + '0');
         sb.append(c);
      }
      return sb.toString();
   }

   public static URL getResource(String name) {
      ClassLoader tccl = Thread.currentThread().getContextClassLoader();
      return tccl.getResource(name);
   }


   public static synchronized void logToFile(String what, String where) {
      try {
         File file = new File(where);
         if (!file.exists()) {
            file.createNewFile();
         }
         OutputStream os = new FileOutputStream(file, true);
         Writer writer = new OutputStreamWriter(os);
         writer.write(what);
         if (!what.endsWith("\n")) {
            writer.write("\n");
         }
         writer.close();
         os.close();

      } catch (IOException ex) {
         throw new RuntimeException(ex);
      }
   }

   private static final long STOP_DELAY_DEFAULT = 0;

   /**
    *  stops execution of the program indefinitely
    *  useful in testsuite debugging
    */
   public static void stop(){
      stop(STOP_DELAY_DEFAULT);
   }
   
   /**
   stop test execution for a given time interval
   useful for debugging  
   @param delay interval (milliseconds), if delay<=0, interval is considered to be infinite (Long.MAX_VALUE)
    */
   public static void stop(long delay) {
      long currentTime = System.currentTimeMillis();
      long remainingTime = 0<delay ? currentTime + delay - System.currentTimeMillis() : Long.MAX_VALUE;
      while (remainingTime > 0) {
         try {
            Thread.sleep(remainingTime);
         } catch (InterruptedException ex) {
            remainingTime = currentTime + delay - System.currentTimeMillis();
            continue;
         }
      }
   }

   public static void applyUpdates(final List<ModelNode> updates, final ModelControllerClient client) throws Exception {
      for (ModelNode update : updates) {
         applyUpdate(update, client);
      }
   }

   public static void applyUpdate(ModelNode update, final ModelControllerClient client) throws Exception {
      ModelNode result = client.execute(new OperationBuilder(update).build());
      if (result.hasDefined("outcome") && "success".equals(result.get("outcome").asString())) {
         if (result.hasDefined("result")) {
            //System.out.println(result.get("result"));
         }
      } else if (result.hasDefined("failure-description")) {
         throw new RuntimeException(result.get("failure-description").toString());
      } else {
         throw new RuntimeException("Operation not successful; outcome = " + result.get("outcome"));
      }
   }

   public static void removeSecurityDomain(final ModelControllerClient client, final String domainName) throws Exception {
      final List<ModelNode> updates = new ArrayList<ModelNode>();
      ModelNode op = new ModelNode();
      op.get(OP).set(REMOVE);
      op.get(OP_ADDR).add(SUBSYSTEM, "security");
      op.get(OP_ADDR).add(SECURITY_DOMAIN, domainName);
      updates.add(op);

      applyUpdates(updates, client);
   }

   public static String getContent(HttpResponse response) throws IOException {
      InputStreamReader reader = new InputStreamReader(response.getEntity().getContent());
      StringBuilder content = new StringBuilder();
      int c;
      while (-1 != (c = reader.read())) {
         content.append((char) c);
      }
      reader.close();
      return content.toString();
   }

   public static HttpResponse authAndGetResponse(String URL, String user, String pass) throws Exception {
      DefaultHttpClient httpclient = new DefaultHttpClient();
      HttpResponse response;
      HttpGet httpget = new HttpGet(URL);

      response = httpclient.execute(httpget);

      HttpEntity entity = response.getEntity();
      if (entity != null)
         EntityUtils.consume(entity);

      // We should get the Login Page
      StatusLine statusLine = response.getStatusLine();
      System.out.println("Login form get: " + statusLine);
      assertEquals(200, statusLine.getStatusCode());

      System.out.println("Initial set of cookies:");
      List<Cookie> cookies = httpclient.getCookieStore().getCookies();
      if (cookies.isEmpty()) {
         System.out.println("None");
      } else {
         for (int i = 0; i < cookies.size(); i++) {
            System.out.println("- " + cookies.get(i).toString());
         }
      }

      // We should now login with the user name and password
      HttpPost httpost = new HttpPost(URL + "/j_security_check");

      List<NameValuePair> nvps = new ArrayList<NameValuePair>();
      nvps.add(new BasicNameValuePair("j_username", user));
      nvps.add(new BasicNameValuePair("j_password", pass));

      httpost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));

      response = httpclient.execute(httpost);


      int statusCode = response.getStatusLine().getStatusCode();

      assertTrue((302 == statusCode) || (200 == statusCode));
      // Post authentication - if succesfull, we have a 302 and have to redirect
      if (302 == statusCode) {
         entity = response.getEntity();
         if (entity != null) {
            EntityUtils.consume(entity);
         }
         Header locationHeader = response.getFirstHeader("Location");
         String location = locationHeader.getValue();
         HttpGet httpGet = new HttpGet(location);
         response = httpclient.execute(httpGet);
      }

      return response;
   }

   public static void makeCall(String URL, String user, String pass, int expectedStatusCode) throws Exception {
      DefaultHttpClient httpclient = new DefaultHttpClient();
      try {
         HttpGet httpget = new HttpGet(URL);

         HttpResponse response = httpclient.execute(httpget);

         HttpEntity entity = response.getEntity();
         if (entity != null)
            EntityUtils.consume(entity);

         // We should get the Login Page
         StatusLine statusLine = response.getStatusLine();
         System.out.println("Login form get: " + statusLine);
         assertEquals(200, statusLine.getStatusCode());

         System.out.println("Initial set of cookies:");
         List<Cookie> cookies = httpclient.getCookieStore().getCookies();
         if (cookies.isEmpty()) {
            System.out.println("None");
         } else {
            for (int i = 0; i < cookies.size(); i++) {
               System.out.println("- " + cookies.get(i).toString());
            }
         }

         // We should now login with the user name and password
         HttpPost httpost = new HttpPost(URL + "/j_security_check");

         List<NameValuePair> nvps = new ArrayList<NameValuePair>();
         nvps.add(new BasicNameValuePair("j_username", user));
         nvps.add(new BasicNameValuePair("j_password", pass));

         httpost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));

         response = httpclient.execute(httpost);
         entity = response.getEntity();
         if (entity != null)
            EntityUtils.consume(entity);

         statusLine = response.getStatusLine();

         // Post authentication - we have a 302
         assertEquals(302, statusLine.getStatusCode());
         Header locationHeader = response.getFirstHeader("Location");
         String location = locationHeader.getValue();

         HttpGet httpGet = new HttpGet(location);
         response = httpclient.execute(httpGet);

         entity = response.getEntity();
         if (entity != null)
            EntityUtils.consume(entity);

         System.out.println("Post logon cookies:");
         cookies = httpclient.getCookieStore().getCookies();
         if (cookies.isEmpty()) {
            System.out.println("None");
         } else {
            for (int i = 0; i < cookies.size(); i++) {
               System.out.println("- " + cookies.get(i).toString());
            }
         }

         // Either the authentication passed or failed based on the expected status code
         statusLine = response.getStatusLine();
         assertEquals(expectedStatusCode, statusLine.getStatusCode());
      } finally {
         // When HttpClient instance is no longer needed,
         // shut down the connection manager to ensure
         // immediate deallocation of all system resources
         httpclient.getConnectionManager().shutdown();
      }
   }


   public static void setPropertiesFile(Map<String, String> props, URL propFile) {
      File userPropsFile = new File(propFile.getFile());
      try {
         Writer writer = new FileWriter(userPropsFile);
         for (Map.Entry<String, String> entry : props.entrySet()) {
            writer.write(entry.getKey() + "=" + entry.getValue() + "\n");
         }
         writer.close();
      } catch (IOException e) {
         throw new RuntimeException(e);
      }

   }

   public static File createTempPropFile(Map<String, String> props) {
      try {
         File propsFile = File.createTempFile("props", ".properties");
         propsFile.deleteOnExit();
         Writer writer = new FileWriter(propsFile);
         for (Map.Entry<String, String> entry : props.entrySet()) {
            writer.write(entry.getKey() + "=" + entry.getValue() + "\n");
         }
         writer.close();
         return propsFile;
      } catch (IOException e) {
         throw new RuntimeException("Temporary file could not be created or writen to!", e);
      }
   }
   
   public static void saveWar(WebArchive war, String address){
      war.as(ZipExporter.class).exportTo(new File(address), true);
   }

}
