/*
 * Copyright 2014 Guidewire Software, Inc.
 */

package gw.internal.gosu.parser;

import gw.fs.IDirectory;
import gw.fs.IFile;
import gw.fs.IFileUtil;
import gw.internal.gosu.parser.FileSystemGosuClassRepository.ClassFileInfo;
import gw.lang.parser.IFileRepositoryBasedType;
import gw.lang.reflect.IType;
import gw.lang.reflect.ITypeLoader;
import gw.lang.reflect.gs.IFileSystemGosuClassRepository;
import gw.lang.reflect.gs.TypeName;
import gw.lang.reflect.module.IModule;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 */
class PackageToClassPathEntryTreeMap
{
  private String _strFullPackageName;
  private String _strRelativePackageName;
  private Map<String, PackageToClassPathEntryTreeMap> _children = new HashMap<String, PackageToClassPathEntryTreeMap>();
  private List<IFileSystemGosuClassRepository.ClassPathEntry> _classPathEntries = new ArrayList<IFileSystemGosuClassRepository.ClassPathEntry>();
  private PackageToClassPathEntryTreeMap _parent;
  private IModule _module;

  PackageToClassPathEntryTreeMap( PackageToClassPathEntryTreeMap parent, String packageName, IModule module )
  {
    _parent = parent;
    _module = module;
    _strRelativePackageName = packageName;
    if( parent != null && !parent._strFullPackageName.isEmpty() )
    {
      _strFullPackageName = parent._strFullPackageName + '.' + packageName;
    }
    else
    {
      _strFullPackageName = packageName;
    }
  }

  public void addClassPathEntry( IFileSystemGosuClassRepository.ClassPathEntry entry )
  {
    if( !_classPathEntries.contains( entry ) )
    {
      _classPathEntries.add( entry );
    }
  }

  public PackageToClassPathEntryTreeMap createChildForDir( IFileSystemGosuClassRepository.ClassPathEntry entry, String packageName )
  {
    PackageToClassPathEntryTreeMap packageTree = _children.get( packageName );
    if( packageTree == null )
    {
      packageTree = new PackageToClassPathEntryTreeMap( this, packageName, _module );
      _children.put( packageName, packageTree );
    }
    if(entry != null) {
      packageTree.addClassPathEntry( entry );
    }
    return packageTree;
  }

  public PackageToClassPathEntryTreeMap getChild( String relativePackage )
  {
    return _children.get( relativePackage );
  }

  public FileSystemGosuClassRepository.ClassFileInfo resolveToClassFileInfo(String strQualifiedClassName, String[] extensions) {
    if (strQualifiedClassName.length() <= _strFullPackageName.length()) {
      return null;
    }
    String remainingPart = _strFullPackageName.isEmpty()
                           ? strQualifiedClassName
                           : strQualifiedClassName.substring(_strFullPackageName.length() + 1);
    int dotIndex = remainingPart.indexOf('.');
    String fileName = remainingPart.substring(0, dotIndex == -1 ? remainingPart.length() : dotIndex);
    for (IFileSystemGosuClassRepository.ClassPathEntry classPathEntry : _classPathEntries) {
      FileSystemGosuClassRepository.ClassFileInfo info =
              getClassFileInfo(classPathEntry, fileName, dotIndex, remainingPart, extensions);
      if (info != null) {
        return info;
      }
    }
    return null;
  }

  public URL resolveToResource( String resourceName )
  {
    if( resourceName.length() <= _strFullPackageName.length() )
    {
      return null;
    }

    String remainingPart = resourceName.substring( _strFullPackageName.length() + 1 );
    for( IFileSystemGosuClassRepository.ClassPathEntry classPathEntry : _classPathEntries )
    {
      URL url = getResource( classPathEntry, remainingPart );
      if( url != null )
      {
        return url;
      }
    }
    return null;
  }

  private FileSystemGosuClassRepository.ClassFileInfo getClassFileInfo(
          IFileSystemGosuClassRepository.ClassPathEntry classPathEntry,
          String fileName, int dotIndex,
          String remainingPart,
          String[] extensions )
  {
    ClassFileInfo file = getFile( classPathEntry, fileName, extensions );
    if( file != null && dotIndex != -1 )
    {
      IType outerMostClass;
      try
      {
        outerMostClass = TypeLoaderAccess.instance().getIntrinsicTypeByFullName( _strFullPackageName + "." + fileName );
      }
      catch( ClassNotFoundException e )
      {
        throw new RuntimeException( e );
      }
      List<String> innerClassParts = splitInnerClassPartsIntoList( dotIndex, remainingPart );
      if (!(outerMostClass instanceof IFileRepositoryBasedType)) {
        throw new RuntimeException("Type not a IFileRepositoryBasedType: " + outerMostClass);
      }
      return new FileSystemGosuClassRepository.ClassFileInfo( ((IFileRepositoryBasedType)outerMostClass).getSourceFileHandle(),
                                                                  ((IFileRepositoryBasedType) outerMostClass).getClassType(),
                                                                  _strFullPackageName + "." + fileName,
                                                                  innerClassParts,
                                                                  classPathEntry.isTestResource() );
    }
    else
    {
      return file;
    }
  }

  public static List<String> splitInnerClassPartsIntoList( int dotIndex, String remainingPart )
  {
    String[] parts = remainingPart.substring( dotIndex + 1 ).split("\\.");
    for( int i = 0; i < parts.length; i++ )
    {
      parts[i] = parts[i].intern();
    }
    return Arrays.asList( parts );
  }

  private URL getResource( IFileSystemGosuClassRepository.ClassPathEntry root, String strFileName )
  {
    try
    {
      IFile possibleFile = getFile(root, strFileName);
      return possibleFile == null ? null : possibleFile.toURI().toURL();
    }  catch (IOException ex) {
        throw new RuntimeException(ex);
    }
  }

  private IFile getFile( IFileSystemGosuClassRepository.ClassPathEntry root, String strFileName )
  {
    IDirectory dir = getDir( root );
    return getFileMatchCase( dir, strFileName );
  }

  private ClassFileInfo getFile(
          IFileSystemGosuClassRepository.ClassPathEntry root,
          String strFileName, String[] extensions )
  {
    IDirectory dir = getDir( root );
    for( String ext : extensions )
    {
      IFile file = getFileMatchCase( dir, strFileName + ext );
      if( file != null )
      {
        return new FileSystemGosuClassRepository.ClassFileInfo( root, file, root.isTestResource() );
      }
    }
    return null;
  }

  private IFile getFileMatchCase( IDirectory dir, String strFileName ) {
    if (dir.hasChildFile(strFileName)) {
      IFile file = dir.file( strFileName );
      if (IFileUtil.getBaseName( strFileName ).equals( file.getBaseName() )) {
        return file;
      } else {
        return null;
      }
    } else {
      return null;
    }
  }

  private IDirectory getDir(IFileSystemGosuClassRepository.ClassPathEntry root) {
    return _strFullPackageName.isEmpty()
            ? root.getPath()
            : root.getPath().dir(_strFullPackageName.replace('.', File.separatorChar));
  }

  public void delete( IDirectory dir ) {
    if( _classPathEntries.size() == 1 ) {
      _parent.removeChild(this);
    }
    else {
      for( IFileSystemGosuClassRepository.ClassPathEntry entry: _classPathEntries ) {
        if( dir.equals( entry.getPath() ) || dir.isDescendantOf( entry.getPath() ) ) {
          _classPathEntries.remove( entry );
          break;
        }
      }
    }
  }

  private void removeChild(PackageToClassPathEntryTreeMap pkg) {
    _children.remove(pkg._strRelativePackageName);
  }

  public PackageToClassPathEntryTreeMap getParent() {
    return _parent;
  }

  public int getSourceRootCount() {
    return _classPathEntries.size();
  }

  @Override
  public String toString() {
    return _strFullPackageName;
  }

  public Set<TypeName> getTypeNames(Set<String> extensions, ITypeLoader loader) {
    Set<TypeName> names = new HashSet<TypeName>();
    for (PackageToClassPathEntryTreeMap child : _children.values()) {
      String name = child._strFullPackageName;
      name = name.substring(name.lastIndexOf('.') + 1);
      names.add(new TypeName(name, loader, TypeName.Kind.NAMESPACE, TypeName.Visibility.PUBLIC));
    }
    for (IFileSystemGosuClassRepository.ClassPathEntry classPathEntry : _classPathEntries) {
      IDirectory entryPath = classPathEntry.getPath().dir(_strFullPackageName.replace('.', '/'));
      List<? extends IFile> files = entryPath.listFiles();
      for (IFile file : files) {
        String extension = "." + file.getExtension();
        if (extensions.contains(extension)) {
          String fqn = entryPath.relativePath(file).replace('/', '.');
          fqn = fqn.substring(0, fqn.lastIndexOf('.'));
          names.add(new TypeName(_strFullPackageName + "." + fqn, loader, TypeName.Kind.TYPE, TypeName.Visibility.PUBLIC));
        }
      }
    }
    return names;
  }
}
