/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, 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.system.server.profileservice.repository;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.jboss.profileservice.spi.ProfileKey;
import org.jboss.profileservice.spi.metadata.ProfileMetaData;
import org.jboss.profileservice.spi.metadata.SubProfileMetaData;
import org.jboss.system.server.profile.repository.metadata.FilteredProfileMetaData;
import org.jboss.system.server.profile.repository.metadata.HotDeploymentProfileMetaData;
import org.jboss.system.server.profile.repository.metadata.ProfilesMetaData;
import org.jboss.virtual.VFS;
import org.jboss.virtual.VirtualFile;
import org.jboss.xb.binding.JBossXBException;
import org.jboss.xb.binding.Unmarshaller;
import org.jboss.xb.binding.UnmarshallerFactory;
import org.jboss.xb.binding.sunday.unmarshalling.DefaultSchemaResolver;

/**
 * A profile factory based which parses the xml ProfileMetaData files.
 * 
 * @author <a href="mailto:emuckenh@redhat.com">Emanuel Muckenhuber</a>
 * @version $Revision: 87871 $
 */
public class XmlProfileFactory extends AbstractBootstrapProfileFactory
{
   /** The profiles directory name. */
   public final static String PROFILES_SUFFIX = ".profile";
   
   /** The attachment store uri. */
   private File attachmentStoreRoot;
   
   /** Profile directories */
   private Collection<VirtualFile> profileDirectories;
   
   /** The unmarshaller. */
   private Unmarshaller unmarshaller;
   
   /** The parsed file list */
   private Collection<ProfileKey> processedProfileKeys = new ArrayList<ProfileKey>();

   /** The default schema resolver. */
   private static final DefaultSchemaResolver resolver = new DefaultSchemaResolver();
   
   static
   {
      // Add schema bindings
      resolver.addClassBinding("urn:jboss:profileservice:profiles:1.0", ProfilesMetaData.class);
      resolver.addClassBinding("urn:jboss:profileservice:profile:filtered:1.0", FilteredProfileMetaData.class);
      resolver.addClassBinding("urn:jboss:profileservice:profile:hotdeployment:1.0", HotDeploymentProfileMetaData.class);
   }
   
   public XmlProfileFactory(URI[] profileDirectories) throws Exception
   {
      if(profileDirectories == null)
         throw new IllegalArgumentException("Null directories");
      this.profileDirectories = new ArrayList<VirtualFile>();
      for(URI uri : profileDirectories)
      {
         VirtualFile vf = VFS.getRoot(uri);
         if(vf == null)
            throw new IllegalArgumentException("Could not find uri: " + vf);
         if(vf.isLeaf())
            throw new IllegalArgumentException("Not a directory: " + vf);
         this.profileDirectories.add(vf);
      }
   }
   
   public Collection<VirtualFile> getProfileDirectories()
   {
      return profileDirectories;
   }
   
   public void setProfileDirectories(Collection<VirtualFile> profileDirectories)
   {
      this.profileDirectories = profileDirectories;
   }   
   
   public File getAttachmentStoreRoot()
   {
      return attachmentStoreRoot;
   }

   public void setAttachmentStoreRoot(File attachmentStoreRoot)
   {
      this.attachmentStoreRoot = attachmentStoreRoot;
   }
   
   /**
    * Create the profile meta data.
    * 
    * @param rootKey the key of the profile/
    * @param the url pointing to the profile file.
    * @throws Exception
    */
   @Override
   protected void createProfileMetaData(ProfileKey rootKey, URL url) throws Exception
   {
      // Create the unmarshaller
      this.unmarshaller = UnmarshallerFactory.newInstance().newUnmarshaller();

      // Get the root profile virtual file
      VirtualFile vf = getProfileFile(rootKey, url);
      // Parse
      ProfilesMetaData rootProfiles = parse(vf);
      // Process
      processProfilesMetaData(rootProfiles);
   }
   
   /**
    * process the profile meta data.
    * 
    * @param metaData the profile meta data.
    * @throws Exception
    */
   protected void processProfileMetaData(ProfileMetaData metaData) throws Exception
   {
      // Create profileKey
      ProfileKey key = createProfileKey(metaData);      
      processProfileMetaData(key, metaData);
   }
   
   /**
    * process the profile meta data
    * 
    * @param key the profile key.
    * @param metaData the meta data.
    * 
    * @throws Exception
    */
   protected void processProfileMetaData(ProfileKey key, ProfileMetaData metaData) throws Exception
   {      
      // Don't process the same key twice
      if(this.processedProfileKeys.contains(key))
         return;

      // Add profile meta data
      addProfile(key, metaData);
      
      // Process sub profiles
      processSubProfiles(key, metaData);
   }
   
   /**
    * process the sub profiles.
    * 
    * @param key the profile key.
    * @param metaData the profile meta data
    * @throws Exception
    */
   protected void processSubProfiles(ProfileKey key, ProfileMetaData metaData) throws Exception
   {
      if(metaData.getSubprofiles() != null && metaData.getSubprofiles().isEmpty() == false)
      {
         for(SubProfileMetaData subProfile : metaData.getSubprofiles())
         {
            ProfileKey subProfileKey = createProfileKey(subProfile);
            processSubProfileMetaData(subProfileKey, subProfile);
         }
      }
   }
   
   /**
    * process the sub profile.
    * 
    * @param key the sub profile key.
    * @param metaData the sub profile meta data.
    * @throws Exception
    */
   protected void processSubProfileMetaData(ProfileKey key, SubProfileMetaData metaData) throws Exception
   {
      // get file
      VirtualFile vf = getProfileFile(key, null);
      // parse
      ProfilesMetaData profiles = parse(vf);
      // TODO maybe override the profiles key (as it should be the same - filename and profiles name
      // processProfilesMetaData(key, profiles);
      processProfilesMetaData(profiles);
   }
   
   /**
    * process the profiles meta data.
    * 
    * @param key the profiles key.
    * @param metaData the profiles meta data.
    * @throws Exception
    */
   protected void processProfilesMetaData(ProfileKey key, ProfilesMetaData metaData) throws Exception
   {
      if(metaData.getProfiles() != null && metaData.getProfiles().isEmpty() == false)
      {
         for(ProfileMetaData profile : metaData.getProfiles())
         {
            processProfileMetaData(profile);
         }
      }
      // Add the profiles for later resolution
      addProfiles(key, metaData.getProfiles());      
   }
   
   /**
    * Process the <profiles> meta data.
    * This will add dependencies based on the ordering of the xml.
    * 
    * @param profilesMetaData the profiles meta data
    */
   protected void processProfilesMetaData(ProfilesMetaData profilesMetaData) throws Exception
   {
      // The <profiles> key
      ProfileKey profilesKey = createProfileKey(profilesMetaData);
      processProfilesMetaData(profilesKey, profilesMetaData);
   }

   /**
    * get the virtual file of the profile.
    * 
    * @param key the profile key.
    * @param url the url pointing to the file
    * @return a resolved name if the url is null.
    * 
    * @throws Exception
    */
   protected VirtualFile getProfileFile(ProfileKey key, URL url) throws Exception
   {
      if(url != null)
         return VFS.getRoot(url);
      else
         return resolveFile(key.getName());
   }
   
   /**
    * Try to resolve the profile name.
    * 
    * @param name the profile name.
    * @return the virtual file
    * @throws Exception
    */
   protected VirtualFile resolveFile(String name) throws Exception
   {
      // ProfileKey.getName() + .profile
      if(name.endsWith(PROFILES_SUFFIX) == false)
         name = name + PROFILES_SUFFIX;
         
      VirtualFile vf = null;
      for(VirtualFile dir : profileDirectories)
      {
         vf = dir.getChild(name);
         if(vf != null)
            break;
      }
      if(vf == null)
         throw new FileNotFoundException("Could not find profile configuration file for: " + name);
      return vf;
   }
   
   /**
    * Parse a profile file.
    * 
    * @param vf the virtual file.
    * @return the <profiles> meta data
    * @throws JBossXBException
    * @throws IOException
    */
   protected ProfilesMetaData parse(VirtualFile vf) throws JBossXBException, IOException
   {
      if(log.isTraceEnabled())
         log.trace("parsing file: " + vf.getPathName());
      return (ProfilesMetaData) unmarshaller.unmarshal(vf.openStream(), resolver);
   }
   
   @Override
   protected void addProfile(ProfileKey key, ProfileMetaData metaData)
   {
      // Add to processed keys
      processedProfileKeys.add(key);
      // Add to parent
      super.addProfile(key, metaData);
   }
   
   @Override
   protected void addProfiles(ProfileKey key, List<ProfileMetaData> metaData)
   {
      // Add to processed keys
      processedProfileKeys.add(key);
      // Add to pared
      super.addProfiles(key, metaData);
   }
}

