/**
 * Copyright (c) 2005-2009 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.test.tools;

import java.util.Collection;
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.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.codegen.util.CodeGenUtil;
import org.eclipse.emf.common.EMFPlugin;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ImportDeclaration;

/**
 * The ImportManager implementation from EMF 2.4.
 */
class OldImportManager
{
  protected SortedSet<String> imports = new TreeSet<String>();
  protected HashMap<String, String> shortNameToImportMap = new HashMap<String, String>();
  protected HashSet<String> javaLangImports = null;
  protected HashSet<String> importedPackages;

  public OldImportManager(String compilationUnitPackage)
  {
    importedPackages = new HashSet<String>();
    importedPackages.add(compilationUnitPackage);
  }

  public Collection<String> getImports()
  {
    return imports;
  }

  public String getImportedName(String qualifiedName)
  {
    String indices = "";
    int firstBracket = qualifiedName.indexOf("[");
    if (firstBracket != -1)
    {
      indices = qualifiedName.substring(firstBracket);
      qualifiedName = qualifiedName.substring(0, firstBracket);
    }

    String baseName = qualifiedName.substring(qualifiedName.lastIndexOf(".") + 1);

    String shortName = baseName;
    int firstDollar = shortName.indexOf("$");
    if (firstDollar != -1)
    {
      shortName = shortName.substring(0, firstDollar);
    }

    String registeredName = shortNameToImportMap.get(shortName);
    if (registeredName == null)
    {
      registeredName = "java.lang." + shortName;
      if (qualifiedName.equals(registeredName))
      {
        if (javaLangImports != null && javaLangImports.contains(shortName))
        {
          imports.add(qualifiedName);
        }
        return shortName + indices;
      }
      else
      {
        return qualifiedName + indices;
      }
    }
    else
    {
      if (qualifiedName.startsWith(registeredName))
      {
        if (qualifiedName.length () == registeredName.length())
        {
          return baseName.replace('$', '.') + indices;
        }
        else
        {
          char character = qualifiedName.charAt(registeredName.length());
          if (character == '.' || character == '$')
          {
            return baseName.replace('$', '.') + indices;
          }
        }
      }
      return qualifiedName.replace('$', '.') + indices;
    }
  }

  public void addImport(String packageName, String shortName)
  {
    int firstBracket = shortName.indexOf("[");
    if (firstBracket != -1) shortName = shortName.substring(0, firstBracket);
    basicAdd(packageName, shortName, packageName + "." + shortName);
  }

  public void addImport(String qualifiedName)
  {
    int firstBracket = qualifiedName.indexOf("[");
    if (firstBracket != -1) qualifiedName = qualifiedName.substring(0, firstBracket);

    int lastDot = qualifiedName.lastIndexOf(".");
    String shortName = qualifiedName.substring(lastDot + 1);
    int firstDollar = shortName.indexOf("$");
    if (firstDollar != -1)
    {
      shortName = shortName.substring(0, firstDollar);
    }

    String packageName = lastDot == -1 ? null : qualifiedName.substring(0, lastDot);
    basicAdd(packageName, shortName, qualifiedName);
  }

  public void addMasterImport(String packageName, String shortName)
  {
    shortNameToImportMap.put(shortName, packageName + "." + shortName);
  }

  public void addJavaLangImports(List<String> javaLangClassNames)
  {
    if (!javaLangClassNames.isEmpty())
    {
      javaLangImports = new HashSet<String>();
      javaLangImports.addAll(javaLangClassNames);
    }
  }

  public boolean hasImport(String shortName)
  {
    return shortNameToImportMap.containsKey(shortName);
  }

  public void addCompilationUnitImports(String compilationUnitContents)
  {
    if (EMFPlugin.IS_ECLIPSE_RUNNING)
    {
      EclipseHelper.addCompilationUnitImports(importedPackages, shortNameToImportMap, compilationUnitContents);
    }
    else
    {
      Pattern importPattern = Pattern.compile("import\\s+([^\\s;]*);\\s*", Pattern.MULTILINE | Pattern.DOTALL);
      Matcher matcher = importPattern.matcher(compilationUnitContents);
      while (matcher.find())
      {
        String qualifiedName = matcher.group(1);
        int lastDot = qualifiedName.lastIndexOf(".");
        String shortName = qualifiedName.substring(lastDot + 1);
        if (shortName.equals("*"))
        {
          String packageName = qualifiedName.substring(0, lastDot);
          importedPackages.add(packageName);
        }
        else
        {
          shortNameToImportMap.put(shortName, qualifiedName);
        }
      }
    }
  }

  private static class EclipseHelper
  {
    public static void addCompilationUnitImports
      (Set<String> importedPackages, Map<String, String> shortNameToImportMap, String compilationUnitContents)
    {
      ASTParser parser = CodeGenUtil.EclipseUtil.newASTParser(true);
      parser.setSource(compilationUnitContents.toCharArray());
      CompilationUnit compilationUnit = (CompilationUnit)parser.createAST(new NullProgressMonitor());
      for (Iterator<?> i = compilationUnit.imports().iterator(); i.hasNext();)
      {
        ImportDeclaration importDeclaration = (ImportDeclaration)i.next();
        String qualifiedName = importDeclaration.getName().getFullyQualifiedName();
        int lastDot = qualifiedName.lastIndexOf(".");
        String shortName = qualifiedName.substring(lastDot + 1);
        if (shortName.equals("*"))
        {
          String packageName = qualifiedName.substring(0, lastDot);
          importedPackages.add(packageName);
        }
        else
        {
          shortNameToImportMap.put(shortName, qualifiedName);
        }
      }
    }
  }

  public void addPseudoImport(String qualifiedName)
  {
    int lastDot = qualifiedName.lastIndexOf(".");
    String shortName = qualifiedName.substring(lastDot + 1);
    if (shortName.equals("*"))
    {
      String packageName = qualifiedName.substring(0, lastDot);
      importedPackages.add(packageName);
    }
    else
    {
      shortNameToImportMap.put(shortName, qualifiedName);
    }
  }

  private void basicAdd(String packageName, String shortName, String qualifiedName)
  {
    if (shortName.equals("*"))
    {
      importedPackages.add(packageName);
      imports.add(qualifiedName);
    }
    else if (!shortNameToImportMap.containsKey(shortName) && (!CodeGenUtil.isJavaDefaultType(shortName)))
    {
      shortNameToImportMap.put(shortName, qualifiedName);

      if (!importedPackages.contains(packageName))
      {
        imports.add(qualifiedName);
      }
    }
  }

  public String computeSortedImports()
  {
    String NL = System.getProperties().getProperty("line.separator");
    StringBuffer imports = new StringBuffer();

    String previousPackageName = null;
    for (String importName : getImports())
    {
      int index = importName.lastIndexOf(".");
      if (index != -1)
      {
        String packageName = importName.substring(0, index);
        if (previousPackageName != null && !previousPackageName.equals(packageName))
        {
          imports.append(NL);
        }
        previousPackageName = packageName;
      }
      imports.append(NL + "import " + importName + ";");
    }

    return imports.toString();
  }
}

/**
 * Extension of the old ImportManager implementation that adds new APIs moved from GenModel in EMF 2.5.
 * This class would exhibit exactly the same behavior as the new ImportManager, but for numerous bugs in the old implementation.
 */
public class ImportManager extends OldImportManager
{
  private String lineDelimiter;
  private StringBuilder importStringBuilder;
  private StringBuffer importStringBuffer;
  private int importInsertionPoint;

  public ImportManager(String compilationUnitPackage, String compilationUnitShortName)
  {
    this(compilationUnitPackage);
    addMasterImport(compilationUnitPackage, compilationUnitShortName);
  }

  public ImportManager(String compilationUnitPackage)
  {
    super(compilationUnitPackage);
  }

  public String getImportedName(String qualifiedName, boolean autoImport)
  {
    int index = qualifiedName.indexOf("<");
    if (index >= 0)
    {
      String baseName = qualifiedName.substring(0, index);
      StringBuilder result = new StringBuilder(getImportedName(baseName, autoImport));
      result.append("<");
      for (int start = ++index,  end = qualifiedName.lastIndexOf(">") +  1; index < end; ++index)
      {
        char character = qualifiedName.charAt(index);
        switch (character)
        {
          case ' ':
          case ',':
          case '<':
          case '>':
          case '&':
          {
            if (start != index)
            {
              String segment = qualifiedName.substring(start, index);
              result.append(getImportedName(segment, autoImport));
            }
            result.append(character);
            start = index + 1;
            break;
          }
          default:
          {
            break;
          }
        }
      }
      return result.toString();
    }

    index = qualifiedName.indexOf("$");
    String baseName = index == -1 ? qualifiedName : qualifiedName.substring(0, index);
    if (baseName.contains("."))
    {
      addImport(index == -1 ? qualifiedName : qualifiedName.substring(0, index));
      return super.getImportedName(qualifiedName);
    }
    else
    {
      return qualifiedName;
    }
  }

  public String getLineDelimiter()
  {
    return lineDelimiter == null ? System.getProperty("line.separator") : lineDelimiter;
  }

  public void setLineDelimiter(String lineDelimiter)
  {
    this.lineDelimiter = lineDelimiter;
  }

  public void markImportLocation(StringBuilder stringBuilder)
  {
    importStringBuffer = null;
    importStringBuilder = stringBuilder;
    importInsertionPoint = stringBuilder.length();
    addCompilationUnitImports(stringBuilder.toString());
  }

  public void markImportLocation(StringBuffer stringBuffer)
  {
    importStringBuilder = null;
    importStringBuffer = stringBuffer;
    importInsertionPoint = stringBuffer.length();
    addCompilationUnitImports(stringBuffer.toString());
  }

  public void emitSortedImports()
  {
    if (importStringBuilder != null)
    {
      importStringBuilder.insert(importInsertionPoint, computeSortedImports());
    }
    else if (importStringBuffer != null)
    {
      importStringBuffer.insert(importInsertionPoint, computeSortedImports());
    }
  }
}
