/*
 * JBoss, Home of Professional Open Source
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * 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.jboss.deployers.plugins.managed;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jboss.deployers.spi.deployer.managed.ManagedDeploymentCreator;
import org.jboss.deployers.spi.management.KnownDeploymentTypes;
import org.jboss.deployers.structure.spi.DeploymentUnit;
import org.jboss.logging.Logger;
import org.jboss.managed.api.ManagedDeployment;
import org.jboss.managed.api.ManagedObject;
import org.jboss.managed.api.annotation.ManagementConstants;
import org.jboss.managed.api.annotation.ManagementDeployment;
import org.jboss.managed.plugins.ManagedDeploymentImpl;

/**
 * A ManagedDeploymentCreator that looks for {@linkplain KnownDeploymentTypes}
 * attachments and maps those to the {@linkplain ManagedDeployment#getTypes()}
 * set.
 * 
 * This also does attachment type mapping based on the registered attachment
 * type class to deployment type mapping.
 * @see {@link #addAttachmentType(Class, String)}
 * 
 * @author Scott.Stark@jboss.org
 * @version $Revision: 90179 $
 */
public class TypedManagedDeploymentCreator
   implements ManagedDeploymentCreator
{
   private static Logger log = Logger.getLogger(TypedManagedDeploymentCreator.class);
   /** A mapping from a deployment attachment type to the {@link ManagedDeployment#addType(String)} type */
   private Map<Class, String> attachmentToTypeMap = new ConcurrentHashMap<Class, String>();
   private Map<Class, List<TypeVersion>> attachmentToTypeVersionsMap = new ConcurrentHashMap<Class, List<TypeVersion>>();

   /**
    * 
    */
   public ManagedDeployment build(DeploymentUnit unit,
         Map<String, ManagedObject> unitMOs,
         ManagedDeployment parent)
   {
      ManagementDeployment mdAnnotation = null;
      HashMap<String, ManagedObject> validUnitMOs = new HashMap<String, ManagedObject>();
      for(String name : unitMOs.keySet())
      {
         // TODO: why should there be null ManagedObjects?
         ManagedObject mo = unitMOs.get(name);
         if(mo == null)
            continue;
         validUnitMOs.put(name, mo);
         Map<String, Annotation> annotations = mo.getAnnotations();
         if(annotations != null && mdAnnotation == null)
            mdAnnotation = (ManagementDeployment) annotations.get(ManagementDeployment.class.getName());
      }
      String simpleName = unit.getSimpleName();
      String[] types = {};
      if(mdAnnotation != null)
      {
         String mdaSimpleName = mdAnnotation.simpleName();
         if(mdaSimpleName.length() > 0 && ManagementConstants.GENERATED.equals(mdaSimpleName) == false)
            simpleName = mdAnnotation.simpleName();
         types = mdAnnotation.types();
      }
      
      ManagedDeployment md = new ManagedDeploymentImpl(unit.getName(), simpleName, parent, validUnitMOs);
      if(types.length > 0)
      {
         for(String type : types)
            addType(md, type);
      }

      // Check for KnownDeploymentTypes attachment(s)
      Set<? extends KnownDeploymentTypes> knownTypes = unit.getAllMetaData(KnownDeploymentTypes.class);
      if(knownTypes != null)
      {
         for(KnownDeploymentTypes type : knownTypes)
         {
            addType(md, type.getType());
         }
      }

      // Check for attachment to type mappings
      for(Class attachmentType : attachmentToTypeMap.keySet())
      {
         if(unit.isAttachmentPresent(attachmentType))
         {
            String type = attachmentToTypeMap.get(attachmentType);
            addType(md, type);
         }
      }

      //
      for(Class attachmentType : attachmentToTypeVersionsMap.keySet())
      {
         if(unit.isAttachmentPresent(attachmentType))
         {
            List<TypeVersion> versions = attachmentToTypeVersionsMap.get(attachmentType);
            Object attachment = unit.getAttachment(attachmentType);
            if(attachment == null)
               continue;

            for(TypeVersion tv : versions)
            {
               String version = getVersion(attachment, tv.versionGetter);
               if(version == null)
                  continue;

               Matcher m = tv.versionPattern.matcher(version);
               if(m.matches())
                  addType(md, tv.type);
            }
         }
      }
      return md;
   }

   /**
    * 
    * @param attachmentType
    * @param deploymentType
    */
   public void addAttachmentType(Class attachmentType, String deploymentType)
   {
      log.debug("addAttachmentType, "+attachmentType+":"+deploymentType);
      attachmentToTypeMap.put(attachmentType, deploymentType);
   }
   public void addVersionedAttachmentType(Class attachmentType, String deploymentType,
         String versionPattern)
   {
      addVersionedAttachmentType(attachmentType, deploymentType, versionPattern, "getVersion");
   }
   public void addVersionedAttachmentType(Class attachmentType, String deploymentType,
         String versionPattern, String versionGetterName)
   {
      Pattern p = Pattern.compile(versionPattern);
      log.debug("addVersionedAttachmentType, "+attachmentType+":"+deploymentType
            +", version: "+versionPattern);
      TypeVersion tv = new TypeVersion(deploymentType, p, versionGetterName);
      List<TypeVersion> tvlist = attachmentToTypeVersionsMap.get(attachmentType);
      if(tvlist == null)
      {
         tvlist = new ArrayList<TypeVersion>();
         attachmentToTypeVersionsMap.put(attachmentType, tvlist);         
      }
      tvlist.add(tv);
   }

   public void removeAttachmentType(Class attachmentType)
   {
      log.debug("removeAttachmentType, "+attachmentType);
      attachmentToTypeMap.remove(attachmentType);
   }
   public void removeVersionedAttachmentType(Class attachmentType, String deploymentType)
   {
      log.debug("removeVersionedAttachmentType, "+attachmentType);
      List<TypeVersion> tvlist = attachmentToTypeVersionsMap.get(attachmentType);
      if(tvlist != null)
      {
         Iterator<TypeVersion> iter = tvlist.iterator();
         while(iter.hasNext())
         {
            TypeVersion tv = iter.next();
            if(deploymentType.equals(tv.type))
               iter.remove();
         }
         attachmentToTypeVersionsMap.put(attachmentType, tvlist);         
         if(tvlist.size() == 0)
            attachmentToTypeVersionsMap.remove(attachmentType);      
      }
   }

   /**
    * Look for a String getterName method using reflection
    * @param attachment
    * @param getterName - the method name to call to get the version
    * @return the attachment version if found, null otherwise
    */
   private String getVersion(Object attachment, String getterName)
   {
      String version = null;
      try
      {
         Class[] parameterTypes = {};
         Method getVersion = attachment.getClass().getMethod(getterName, parameterTypes);
         Object[] args = {};
         version = (String) getVersion.invoke(attachment, args);
      }
      catch(Throwable t)
      {
         log.debug("Failed to get version:"+t);
      }
      return version;
   }

   private void addType(ManagedDeployment md, String type)
   {
      if(md.getTypes() == null)
         md.setTypes(new HashSet<String>());
      md.addType(type);
   }

   static class TypeVersion
   {
      private String type;
      private Pattern versionPattern;
      private String versionGetter;

      public TypeVersion(String type, Pattern versionPattern, String versionGetter)
      {
         super();
         this.type = type;
         this.versionPattern = versionPattern;
         this.versionGetter = versionGetter;
      }
   }
   public static void main(String[] args)
   {
      Pattern p = Pattern.compile("3.*");
      Matcher m = p.matcher("3.0");
      System.out.println("3.0 matches: "+m.matches());
      m = p.matcher("3.1");
      System.out.println("3.1 matches: "+m.matches());
      m = p.matcher("3.x");
      System.out.println("3.x matches: "+m.matches());
      m = p.matcher("2.1");
      System.out.println("2.1 matches: "+m.matches());

      Pattern p2x = Pattern.compile("[1-2].*");
      m = p2x.matcher("3.1");
      System.out.println("3.1 is 2x: "+m.matches());
      m = p2x.matcher("1.1");
      System.out.println("1.1 is 2x: "+m.matches());
      m = p2x.matcher("2.1");
      System.out.println("2.1 is 2x: "+m.matches());
   }
}
