/**
 * Copyright (c) 2005-2006 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 * 
 * Contributors: 
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.converter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage;
import org.eclipse.emf.codegen.ecore.genmodel.GenPackage;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.converter.util.ConverterUtil;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.plugin.EcorePlugin;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;

/**
 * @since 2.2.0
 */
public abstract class ModelConverter
{
  public static class EPackageConvertInfo
  {
    protected boolean convert = false;
    protected String convertData;
    
    public boolean isConvert()
    {
      return convert;
    }

    public void setConvert(boolean convert)
    {
      this.convert = convert;
    }
    
    public String getConvertData()
    {
      return convertData;
    }
    
    public void setConvertData(String convertData)
    {
      this.convertData = convertData;
    }
  }
  
  public static class ReferencedGenPackageConvertInfo
  {
    protected boolean validReference = true;
    
    public boolean isValidReference()
    {
      return validReference;
    }

    public void setValidReference(boolean validReference)
    {
      this.validReference = validReference;
    }
  }
  
  protected class ReferencedEPackageFilter
  {
    public List<GenPackage> computeValidReferencedGenPackages()
    {
      List<GenPackage> genPackages = new ConverterUtil.GenPackageList();
      for (GenPackage genPackage : getReferencedGenPackages())
      {
        EPackage ePackage = getReferredEPackage(genPackage);
        if (ePackage != null)
        {
          genPackages.add(genPackage);
        }
      }
      return genPackages;
      
    }
    
    public List<EPackage> filterReferencedEPackages(Collection<EPackage> ePackages, List<GenPackage> referencedGenPackages)
    {
      if (referencedGenPackages == null)
      {
        referencedGenPackages = getReferencedGenPackages();
      }
      
      if (ePackages.isEmpty())
      {
        return Collections.emptyList();
      }
      else if (referencedGenPackages.isEmpty())
      {
        return new ArrayList<EPackage>(ePackages);
      }
      else
      {
        List<EPackage> filteredEPackages = new ConverterUtil.EPackageList(ePackages);
        for (GenPackage genPackage : referencedGenPackages)
        {
          EPackage ePackage = getReferredEPackage(genPackage);
          if (ePackage != null)
          {
            filteredEPackages.remove(ePackage);
          }
        }
        return filteredEPackages;
      }
    }
    
    protected EPackage getReferredEPackage(GenPackage genPackage)
    {
      return isValidReference(genPackage) ?
        ModelConverter.this.getReferredEPackage(genPackage) :
        null;
    }
    
    protected boolean isValidReference(GenPackage genPackage)
    {
      return getReferenceGenPackageConvertInfo(genPackage).isValidReference();
    }
  }

  protected GenModel genModel;
  protected List<EPackage> ePackages;
  protected Map<EPackage, EPackageConvertInfo> ePackageToInfoMap;
  protected List<GenPackage> referencedGenPackages;
  protected Map<GenPackage, ReferencedGenPackageConvertInfo> referencedGenPackageToInfoMap;
  
  protected ResourceSet externalGenModelResourceSet;
  protected List<GenModel> externalGenModelList;  
  protected ReferencedEPackageFilter referencedEPackageFilter;
  protected ReferencedEPackageFilter referencedEPackageFilterToConvert;

  public void dispose()
  {
    clearEPackagesCollections();

    if (referencedGenPackages != null)
    {
      referencedGenPackages.clear();
      referencedGenPackages = null;
    }

    if (referencedGenPackageToInfoMap != null)
    {
      referencedGenPackageToInfoMap.clear();
      referencedGenPackageToInfoMap = null;
    }

    genModel = null;
    referencedEPackageFilter = null;
    referencedEPackageFilterToConvert = null;
  }
  
  public abstract String getID();
  
  protected String getConverterGenAnnotationSource()
  {
    return GenModelPackage.eNS_URI + getID();
  }  
  
  public GenModel getGenModel()
  {
    return genModel;
  }
  
  public List<EPackage> getEPackages()
  {
    if (ePackages == null)
    {
      ePackages = createEPackagesList();
    }
    return ePackages;
  }
  
  protected List<EPackage> createEPackagesList()
  {
    return new ConverterUtil.EPackageList();
  }
  
  public List<GenPackage> getReferencedGenPackages()
  {
    if (referencedGenPackages == null)
    {
      referencedGenPackages = createReferencedGenPackagesList();
    }
    return referencedGenPackages;
  }
  
  protected List<GenPackage> createReferencedGenPackagesList()
  {
    return new ConverterUtil.GenPackageList();
  }
  
  public void clearEPackagesCollections()
  {    
    if (ePackages != null)
    {
      ePackages.clear();
      ePackages = null;
    }
    
    if (ePackageToInfoMap != null)
    {
      ePackageToInfoMap.clear();
      ePackageToInfoMap = null;
    }  
    
    if (externalGenModelList != null)
    {
      externalGenModelList.clear();
      externalGenModelList = null;
    }    
  }
  
  public ResourceSet createResourceSet()
  {
    return ConverterUtil.createResourceSet();
  }
  
  protected Map<EPackage, EPackageConvertInfo> getEPackageToInfoMap()
  {
    if (ePackageToInfoMap == null)
    {
      ePackageToInfoMap = new HashMap<EPackage, EPackageConvertInfo>();
    }
    return ePackageToInfoMap;
  }

  public EPackageConvertInfo getEPackageConvertInfo(EPackage ePackage)
  {
    EPackageConvertInfo ePackageInfo = getEPackageToInfoMap().get(ePackage);
    if (ePackageInfo == null)
    {
      ePackageInfo = createEPackageInfo(ePackage);
      getEPackageToInfoMap().put(ePackage, ePackageInfo);
    }
    return ePackageInfo;
  }
  
  public void clearEPackageConvertInfoCache()
  {
    getEPackageToInfoMap().clear();
  }
  
  protected EPackageConvertInfo createEPackageInfo(EPackage ePackage)
  {
    return new EPackageConvertInfo();
  }
  
  protected Map<GenPackage, ReferencedGenPackageConvertInfo> getReferencedGenPackageToInfoMap()
  {
    if (referencedGenPackageToInfoMap == null)
    {
      referencedGenPackageToInfoMap = new HashMap<GenPackage, ReferencedGenPackageConvertInfo>();
    }
    return referencedGenPackageToInfoMap;
  }
 
  public ReferencedGenPackageConvertInfo getReferenceGenPackageConvertInfo(GenPackage genPackage)
  {
    ReferencedGenPackageConvertInfo genPackageConvertInfo = getReferencedGenPackageToInfoMap().get(genPackage);
    if (genPackageConvertInfo == null)
    {
      genPackageConvertInfo = createGenPackageConvertInfo(genPackage);
      getReferencedGenPackageToInfoMap().put(genPackage, genPackageConvertInfo);
    }
    return genPackageConvertInfo;
  }
  
  public void clearReferenceGenPackageConvertInfoCache()
  {
    getReferencedGenPackageToInfoMap().clear();
  }
  
  protected ReferencedGenPackageConvertInfo createGenPackageConvertInfo(GenPackage genPackage)
  {
    return new ReferencedGenPackageConvertInfo();
  }

  public List<EPackage> filterReferencedEPackages(Collection<EPackage> ePackages, List<GenPackage> referencedGenPackages)
  {
    if (referencedEPackageFilter == null)
    {
      referencedEPackageFilter = createReferencedEPackageFilter();
    }
    return referencedEPackageFilter.filterReferencedEPackages(ePackages, referencedGenPackages);
  }
  
  protected ReferencedEPackageFilter createReferencedEPackageFilter()
  {
    return new ReferencedEPackageFilter();
  }  

  protected List<EPackage> computeEPackagesToConvert()
  {
    List<EPackage> ePackages = new ConverterUtil.EPackageList();
    for (EPackage ePackage : getEPackages())
    {
      if (canConvert(ePackage))
      {
        ePackages.add(ePackage);
      }
    }
    return filterReferencedEPackagesToConvert(ePackages, null);
  }
    
  protected List<EPackage> filterReferencedEPackagesToConvert(Collection<EPackage> ePackages, List<GenPackage> referencedGenPackages)
  {
    if (referencedEPackageFilterToConvert == null)
    {
      referencedEPackageFilterToConvert = createReferencedEPackageFilterToConvert();
    }
    return referencedEPackageFilterToConvert.filterReferencedEPackages(ePackages, referencedGenPackages);
  }
  
  protected List<GenPackage> computeValidReferencedGenPackages()
  {
    if (referencedEPackageFilterToConvert == null)
    {
      referencedEPackageFilterToConvert = createReferencedEPackageFilterToConvert();
    }
    return referencedEPackageFilterToConvert.computeValidReferencedGenPackages();
  }  
  
  protected ReferencedEPackageFilter createReferencedEPackageFilterToConvert()
  {
    return new ReferencedEPackageFilter();
  }
  
  /**
   * Returns true if the EPackage can be converted.  This
   * method doesn't check for dependencies or anything, being focused
   * in testing whether all the required information to generate an 
   * EPackage was gathered. 
   */
  protected boolean canConvert(EPackage ePackage)
  {
    return getEPackageConvertInfo(ePackage).isConvert();
  }
   
  public EPackage getReferredEPackage(GenPackage genPackage)
  {
    String nsURI = genPackage.getEcorePackage().getNsURI();
    if (nsURI != null)
    {
      for (EPackage ePackage : getEPackages())
      {
        if (nsURI.equals(ePackage.getNsURI()))
        {
          return ePackage;
        }
      }
    }
    return null;
  }
  
  public List<GenModel> getExternalGenModels()
  {
    if (externalGenModelList == null)
    {
      externalGenModelList = new UniqueEList.FastCompare<GenModel>();
      if (externalGenModelResourceSet == null)
      {
        externalGenModelResourceSet = createExternalGenModelResourceSet();
      }
      Map<String, URI> ePackageToGenModelMap = EcorePlugin.getEPackageNsURIToGenModelLocationMap(true);
      for (TreeIterator<EPackage> i = 
             new EcoreUtil.ContentTreeIterator<EPackage>(getEPackages())
             {
               private static final long serialVersionUID = 1L;

               @Override
               protected Iterator<? extends EObject> getEObjectChildren(EObject eObject)
               {
                 return ((EPackage)eObject).getESubpackages().iterator();
               }
             };
           i.hasNext(); )
      {
        EPackage ePackage = i.next();
        URI genModelURI = ePackageToGenModelMap.get(ePackage.getNsURI());
        if (genModelURI != null)
        {
          try
          {
            Resource genModelResource = externalGenModelResourceSet.getResource(genModelURI, false);
            if (genModelResource == null)
            {
              genModelResource = externalGenModelResourceSet.getResource(genModelURI, true);

              // If we're loading an Xcore file, we need to be sure there is a resource to identify the project to initialize the Xcore's project provider.
              //
              Resource dummyResource = null;
              if ("xcore".equals(genModelURI.fileExtension()) && genModelURI.isPlatformPlugin())
              {
                dummyResource = externalGenModelResourceSet.createResource(getEPackages().get(0).eResource().getURI());
              }
              externalGenModelList.add((GenModel)EcoreUtil.getObjectByType(genModelResource.getContents(), GenModelPackage.Literals.GEN_MODEL));
              if (dummyResource != null)
              {
                externalGenModelResourceSet.getResources().remove(dummyResource);
              }
            }
          }
          catch (Exception exception)
          {
            ConverterPlugin.INSTANCE.log(exception);
          }
          i.prune();
        }
      }
    }
    return externalGenModelList;
  }
  
  protected ResourceSet createExternalGenModelResourceSet()
  {
    return createResourceSet();
  }  
  
  /**
   * This method was misnamed and will be removed in the next release.
   * @see #getGenModelSaveOptions()
   */
  @Deprecated
  protected Map<?, ?> getGenmodelSaveOptions()
  {
    Map<Object, Object> result = new HashMap<Object, Object>();
    result.put(Resource.OPTION_SAVE_ONLY_IF_CHANGED, Resource.OPTION_SAVE_ONLY_IF_CHANGED_MEMORY_BUFFER);
    result.put(Resource.OPTION_LINE_DELIMITER, Resource.OPTION_LINE_DELIMITER_UNSPECIFIED);
    return result;
  }

  protected Map<?, ?> getGenModelSaveOptions()
  {
    return getGenmodelSaveOptions();
  }
  
  /**
   * Changes the existing EPackage Infos so that no duplicated convert data 
   * is used.
   */
  public void makeEPackageConvertDataUnique()
  {
    if (!getEPackageToInfoMap().isEmpty())
    {
      Map<String, Integer> dataToCounter = new HashMap<String, Integer>();
      List<EPackage> ePackages = filterReferencedEPackages(getEPackageToInfoMap().keySet(), null);
      if (!ePackages.isEmpty())
      {
        List<EPackageConvertInfo> packageInfos = new ArrayList<EPackageConvertInfo>(ePackages.size());
        for (EPackage ePackage : ePackages)
        {
          if (ePackage.getESuperPackage() == null || !ePackages.contains(ePackage.getESuperPackage()))
          {
            EPackageConvertInfo packageInfo = getEPackageToInfoMap().get(ePackage);
            if (packageInfo.isConvert())
            {
              packageInfos.add(0, packageInfo);
            }
            else
            {
              packageInfos.add(packageInfo);
            }
            String data = packageInfo.getConvertData();
            if (data != null)
            {
              dataToCounter.put(data, null);
            }
          }
        }
        
        for (EPackageConvertInfo packageInfo : packageInfos)
        {        
          String data = packageInfo.getConvertData();
          if (data != null)
          {
            Integer counterObject = dataToCounter.get(data);
            if (counterObject != null)
            {
              int counter = counterObject;
              int index = data.lastIndexOf(".");
              StringBuffer newValue = null;
              do
              {            
                newValue = new StringBuffer(data).insert(index, counter++);
              }
              while (dataToCounter.containsKey(newValue.toString()));
              
              packageInfo.setConvertData(newValue.toString());
              counterObject = counter;
              dataToCounter.put(newValue.toString(), 1);
            }
            else
            {
              counterObject = 1;
            }        
            dataToCounter.put(data, counterObject);
          }
        }
      }
    }
  }  
}
