package com.horstmann.labrat;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Path;

public class CallMethodTask extends Task
{
   public void execute() throws BuildException
   {
      try
      {
         // TODO: ideally, we want this in a timed thread
         String[] pathElements = classpath.list();
         URL[] urls = new URL[pathElements.length];
         for (int i = 0; i < pathElements.length; i++)
         {
            urls[i] = new File(pathElements[i]).toURL();
         }
         ClassLoader loader = new URLClassLoader(urls);
         Class cl = loader.loadClass(classname);
         int i = 1;
         while (true)
         {
            // we must have a return value; parameters are optional            
            String expectedReturnValue = getReturnValue(i);
            if (expectedReturnValue == null) return;
            
            String methodName = getMethodName(i);
            Method method = null;
            for (Method m : cl.getMethods())
            {
               // TODO: check for duplicate methods
               if (m.getName().equals(methodName))
                  method = m;
            }
            // TODO: What if more complex method format (with params types)
            if (method == null) 
               throw new BuildException("method " + classname + "." + methodName + " not found.");
            
            
            List<String> paramValues = getParameterValues(i);
            
            Object obj = null;
            if ((method.getModifiers() & Modifier.STATIC) == 0)
               obj = cl.newInstance();
            
            Class[] paramTypes = method.getParameterTypes();
            Object[] args = new Object[paramValues.size()];
            for (int j = 0; j < paramValues.size(); j++)
               args[j] = convert(paramValues.get(j), paramTypes[j]);
            
            Object actualRet = method.invoke(obj, args);
            Object expectedRet = convert(expectedReturnValue, method.getReturnType());
            
            StringBuilder builder = new StringBuilder();
            // TODO: HTML Escapes
            builder.append(rowStart);
            builder.append(methodName);
            builder.append(cellEnd);           
            builder.append(cellStart);
            for (int k = 0; k < paramValues.size(); k++)
            {
               if (k > 0) builder.append(',');
               builder.append(paramValues.get(k));
            }
            
            boolean pass = actualRet == expectedRet || actualRet != null &&
               actualRet.equals(expectedRet);
            
            builder.append(cellEnd);
            builder.append(cellStart);
            builder.append("" + actualRet);
            builder.append(cellEnd);
            builder.append(cellStart);
            builder.append(expectedReturnValue);
            builder.append(cellEnd);
            builder.append(cellStart);
            builder.append(pass ? passString : failString);            
            builder.append(cellEnd);
            builder.append(rowEnd);
            
            log(builder.toString());
            
            // TODO: Set some property with the HTML?
            
            i++;
         }         
      }
      catch (Exception ex)
      {
         if (ex instanceof BuildException)
            throw (BuildException) ex;
         else
            throw new BuildException(ex);
      }
   }
   
   private Object convert(String value, Class cl)
   {
      if (cl == int.class) cl = Integer.class;
      else if (cl == byte.class) cl = Byte.class;
      else if (cl == short.class) cl = Short.class;
      else if (cl == long.class) cl = Long.class;
      else if (cl == float.class) cl = Float.class;
      else if (cl == double.class) cl = Double.class;
      else if (cl == boolean.class) cl = Boolean.class;
      else if (cl == char.class) 
      {
         return new Character(value.charAt(0));
      }
      
      try
      {
         Constructor<?> cons = cl.getConstructor(new Class[] { String.class });
         return cons.newInstance(value);
      }
      catch (Exception ex)
      {
         throw new BuildException(ex);
      }
      
      // TODO: Other construction, e.g. static constants
   }
   
   private String getMethodName(int i)
   {
      String name = getTestProperties().get("test." + i + ".method");
      if (name != null) return name;
      return getTestProperties().get("test.method");
      // TODO: Just return "test" + i as default?
   }
   
   private List<String> getParameterValues(int i)
   {
      List<String> values = new ArrayList<String>();
      int j = 1;
      String value;
      while ((value = getTestProperties().get("test." + i + ".param." + j)) != null)
      {
         values.add(value);
         j++;
      }
      return values;
   }
   
   private String getReturnValue(int i)
   {
      return getTestProperties().get("test." + i + ".return");
   }
   
   @SuppressWarnings("unchecked")
   private Map<String, String> getTestProperties()
   {
      return (Map<String, String>) getProject().getProperties(); 
   }

   public void setClassname(String classname)
   {
      this.classname = classname;
   }

   public void setClasspath(Path classpath)
   {
      this.classpath = classpath;
   }

   public void setCellEnd(String cellEnd)
   {
      this.cellEnd = cellEnd;
   }

   public void setCellStart(String cellStart)
   {
      this.cellStart = cellStart;
   }

   public void setFailString(String failString)
   {
      this.failString = failString;
   }

   public void setPassString(String passString)
   {
      this.passString = passString;
   }

   public void setRowEnd(String rowEnd)
   {
      this.rowEnd = rowEnd;
   }

   public void setRowStart(String rowStart)
   {
      this.rowStart = rowStart;
   }
   
   private Path classpath;
   private String classname;
   
   private String rowStart = "";
   private String rowEnd = "";
   private String cellStart = "";
   private String cellEnd = "|";
   
   private String passString = "pass";
   private String failString = "fail";
}
