package tora.plugin;

import gw.config.CommonServices;
import gw.fs.IDirectory;
import gw.fs.IFile;
import gw.lang.reflect.IType;
import gw.lang.reflect.RefreshKind;
import gw.lang.reflect.TypeLoaderBase;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.module.IModule;
import gw.util.Pair;
import gw.util.StreamUtil;
import gw.util.concurrent.LockingLazyVar;
import tora.parser.Parser;
import tora.parser.Tokenizer;
import tora.parser.tree.ProgramNode;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class JavascriptPlugin extends TypeLoaderBase
{

  private static final String JS_EXTENSION = ".js";
  private Set<String> _namespaces;

  private final LockingLazyVar<Map<String, IFile>> _jsSources = new LockingLazyVar<Map<String, IFile>>() {
    @Override
    protected Map<String, IFile> init() {
      Map<String, IFile> result = new HashMap<>();
      for(Pair<String, IFile> p : findAllFilesByExtension( JS_EXTENSION)) {
        IFile file = p.getSecond();
        String fileName = p.getFirst();
        String fqn = fileName.substring(0, fileName.length() - JS_EXTENSION.length()).replace('/', '.');
        result.put(fqn, file);
      }
      return result;
    }
  };

  public List<Pair<String, IFile>> findAllFilesByExtension(String extension) {
    List<Pair<String, IFile>> results = new ArrayList<>();

    for (IDirectory sourceEntry : _module.getSourcePath()) {
      if (sourceEntry.exists()) {
        String prefix = sourceEntry.getName().equals(IModule.CONFIG_RESOURCE_PREFIX) ? IModule.CONFIG_RESOURCE_PREFIX : "";
        addAllLocalResourceFilesByExtensionInternal(prefix, sourceEntry, extension, results);
      }
    }
    return results;
  }

  private void addAllLocalResourceFilesByExtensionInternal(String relativePath, IDirectory dir, String extension, List<Pair<String, IFile>> results) {
    List<IDirectory> excludedPath = Arrays.asList( _module.getFileRepository().getExcludedPath());
    if ( excludedPath.contains( dir )) {
      return;
    }
    if (!CommonServices.getPlatformHelper().isPathIgnored( relativePath)) {
      for (IFile file : dir.listFiles()) {
        if (file.getName().endsWith(extension)) {
          String path = appendResourceNameToPath(relativePath, file.getName());
          results.add(new Pair<String, IFile>(path, file));
        }
      }
      for (IDirectory subdir : dir.listDirs()) {
        String path = appendResourceNameToPath(relativePath, subdir.getName());
        addAllLocalResourceFilesByExtensionInternal(path, subdir, extension, results);
      }
    }
  }

  private static String appendResourceNameToPath( String relativePath, String resourceName )
  {
    String path;
    if( relativePath.length() > 0 )
    {
      path = relativePath + '/' + resourceName;
    }
    else
    {
      path = resourceName;
    }
    return path;
  }


  public JavascriptPlugin( IModule currentModule )
  {
    super(currentModule);
  }

  @Override
  public IType getType( String name )
  {
    IFile iFile = _jsSources.get().get( name );

    if( iFile != null )
    {
      try
      {
        Parser parser = new Parser( new Tokenizer( StreamUtil.getContent( new InputStreamReader( iFile.openInputStream() ) ) ) );
        ProgramNode programNode = parser.parse();
        if( parser.isES6Class() )
        {
          return new JavascriptClassType( this, name, iFile, programNode );
        } else  {
          return new JavascriptProgramType(this, name, iFile, programNode);
        }
      } catch (IOException e) {

      }
    }

    return null;
  }


  @Override
  public Set<? extends CharSequence> getAllNamespaces()
  {
    if( _namespaces == null ) {
      try {
        _namespaces = TypeSystem.getNamespacesFromTypeNames( getAllTypeNames(), new HashSet<String>() );
      }
      catch( NullPointerException e ) {
        //!! hack to get past dependency issue with tests
        return Collections.emptySet();
      }
    }
    return _namespaces;
  }

  @Override
  public List<String> getHandledPrefixes()
  {
    return Collections.emptyList();
  }

  @Override
  public boolean handlesNonPrefixLoads()
  {
    return true;
  }

  @Override
  public void refreshedNamespace( String s, IDirectory iDirectory, RefreshKind refreshKind )
  {
      //TODO cgross - implement
  }

  @Override
  public boolean hasNamespace( String s )
  {
    return getAllNamespaces().contains(s);
  }

  @Override
  public Set<String> computeTypeNames()
  {
    return _jsSources.get().keySet();
  }
}

@@ -159,16 +159,55 @@
 shKind;%0A
+import gw.lang.reflect.RefreshRequest;%0A
 import g
@@ -4446,17 +4446,25 @@
  String 
-s
+namespace
 , IDirec
@@ -4496,57 +4496,600 @@
 ind 
-refreshKind )%0A  %7B%0A      //TODO cgross - implement
+kind )%0A  %7B%0A    clear();%0A    if( _namespaces != null ) %7B%0A      if( kind == RefreshKind.CREATION ) %7B%0A        _namespaces.add( namespace );%0A      %7D%0A      else if( kind == RefreshKind.DELETION ) %7B%0A        _namespaces.remove( namespace );%0A      %7D%0A    %7D%0A  %7D%0A%0A  @Override%0A  protected void refreshedImpl() %7B%0A    clear();%0A  %7D%0A%0A  @Override%0A  public RefreshKind refreshedFile( IFile file, String%5B%5D types, RefreshKind kind ) %7B%0A    clear();%0A    return kind;%0A  %7D%0A%0A  @Override%0A  protected void refreshedTypesImpl( RefreshRequest request ) %7B%0A    clear();%0A  %7D%0A%0A  private void clear() %7B%0A    _jsSources.clear();
 %0A  %7D
@@ -5140,17 +5140,25 @@
  String 
-s
+namespace
  )%0A  %7B%0A 
@@ -5195,17 +5195,27 @@
 ontains(
-s
+ namespace 
 );%0A  %7D%0A%0A

handle refresh