package org.testng.eclipse.util;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import com.google.common.collect.Lists;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageDeclaration;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.testng.eclipse.launch.components.Filters.ITypeFilter;
import org.testng.eclipse.refactoring.FindTestsRunnableContext;
import org.testng.eclipse.ui.conversion.JUnitConverterQuickAssistProcessor;
import org.testng.eclipse.ui.conversion.JUnitVisitor;

public class Utils {
  /**
   * A filter that will keep types that need to be converted to TestNG.
   */
  public static final ITypeFilter CONVERSION_FILTER = new ITypeFilter() {

    public boolean accept(IType type) {
      IResource resource = type.getResource();
      ICompilationUnit cu = JDTUtil.getJavaElement((IFile) resource);
      CompilationUnit astRoot = JUnitConverterQuickAssistProcessor.createCompilationUnit(cu);
      JUnitVisitor visitor = new JUnitVisitor();
      astRoot.accept(visitor);

      return visitor.needsConversion();
    }

  };

  public static class JavaElement {
    public IJavaProject m_project;
    public IPackageFragmentRoot packageFragmentRoot;
    public IPackageFragment packageFragment;
    public ICompilationUnit compilationUnit;
    public String sourceFolder;

    public JavaElement() {
    }

    public String getPath() {
      String result = null;
      if (compilationUnit != null) {
        result = resourceToPath(compilationUnit);
      } else if (packageFragmentRoot != null) {
        result = resourceToPath(packageFragmentRoot);
      } else if (packageFragment != null) {
        result = resourceToPath(packageFragment);
      } else {
        result = resourceToPath(getProject());
      }
      return result;
    }

    public IJavaProject getProject() {
      if (m_project != null) return m_project;
      else if (packageFragmentRoot != null) return packageFragmentRoot.getJavaProject();
      else if (packageFragment != null) return packageFragment.getJavaProject();
      else if (compilationUnit != null) return compilationUnit.getJavaProject();
      else throw new AssertionError("Couldn't find a project");
    }

    private String resourceToPath(IJavaElement element) {
      return ((IResource) element.getAdapter(IResource.class)).getFullPath().toOSString();
    }

    public String getPackageName() {
      String result = null;
      if (packageFragment != null) {
        result = packageFragment.getElementName();
      } else if (compilationUnit != null) {
        try {
          IPackageDeclaration[] pkg = compilationUnit.getPackageDeclarations();
          result = pkg.length > 0 ? pkg[0].getElementName() : null;
        } catch (JavaModelException e) {
          // ignore
        }
      }

      return result;
    }

    public String getClassName() {
      String result = null;
      if (compilationUnit != null) {
        result = compilationUnit.getElementName();
        if (result.endsWith(".java")) {
          result = result.substring(0, result.length() - ".java".length());
        }
      }
      return result;
    }

    public IResource getResource() {
      if (compilationUnit != null) {
        return (IResource) compilationUnit.getAdapter(IResource.class);
      } else if (packageFragment != null) {
        return (IResource) packageFragment.getAdapter(IResource.class);
      } else if (m_project != null) {
        return (IResource) m_project.getAdapter(IResource.class);
      } else {
        return null;
      }
    }


  }

  /**
   * @return all the ITypes included in the current selection.
   */
  public static List<IType> findSelectedTypes(IWorkbenchPage page, ITypeFilter filter) {
    return findTypes(Utils.getSelectedJavaElements(page), filter);
  }

  public static List<IType> findTypes(List<JavaElement> elements, ITypeFilter filter) {
    List<IType> result = Lists.newArrayList();
    if (filter == null) {
      filter = new ITypeFilter() {
        public boolean accept(IType type) {
          return true;
        }
      };
    };

    for (JavaElement pp : elements) {
      if (pp.compilationUnit != null) {
        try {
          for (IType t : pp.compilationUnit.getAllTypes()) {
            if (filter.accept(t)) {
              result.add(t);
            }
          }
        } catch (JavaModelException e) {
          e.printStackTrace();
        }
      } else {
        IPackageFragmentRoot pfr = pp.packageFragmentRoot;
        IPackageFragment pf = pp.packageFragment;
        try {
          IRunnableContext context = new FindTestsRunnableContext();
          if (pf != null) {
            result.addAll(Arrays.asList(
                TestSearchEngine.findTests(context, new Object[] { pf }, filter)));
          } else if (pfr != null) {
            result.addAll(Arrays.asList(
                TestSearchEngine.findTests(context, new Object[] { pfr }, filter)));
          } else {
            result.addAll(Arrays.asList(
                TestSearchEngine.findTests(context, new Object[] { pp.getProject() }, filter)));
          }
        }
        catch(InvocationTargetException ex) {
          ex.printStackTrace();
        }
        catch(InterruptedException ex) {
          // ignore
        }
      }
    }

    return result;
  }

  /**
   * Known limitation of this method: if the selection is happening in the Navigator,
   * the selected tree item will contain a path that I'm not bothering turning into
   * Java elements: instead, I just return the entire project. Therefore, right clicking
   * on a file in the Navigator and selecting "Convert to TestNG" will cause the refactoring
   * to apply to the entire project.
   *
   * TODO: handle the Navigator as well as the Package Explorer.
   *
   * @param page
   * @return
   */
  public static List<JavaElement> getSelectedJavaElements() {
    return getSelectedJavaElements(
        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage());
  }

  public static List<JavaElement> getSelectedJavaElements(IWorkbenchPage page) {
    List<JavaElement> result = Lists.newArrayList();
    ISelection selection = page.getSelection();

    if (selection instanceof TreeSelection) {
      //
      // If we have a selection, extract the Java information from it
      //
      TreeSelection sel = (TreeSelection) selection;
      for (Iterator it = sel.iterator(); it.hasNext();) {
        result.add(convertToJavaElement(it.next()));
      }
    } else {
      //
      // No selection, extract the Java information from the current editor, if applicable
      //
      IEditorReference[] editors = page.getEditorReferences();
//        workbench.getActiveWorkbenchWindow().getActivePage().getEditorReferences();
      for (IEditorReference ref : editors) {
        IEditorPart editor = ref.getEditor(false);
        if (editor != null) {
          ITypeRoot root = JavaUI.getEditorInputTypeRoot(editor.getEditorInput());
          if (root != null && root.getElementType() == IJavaElement.COMPILATION_UNIT) {
            result.add(convertToJavaElement(root));
          }
        }
      }

    }

    return result;
  }

  private static JavaElement convertToJavaElement(Object element) {
    JavaElement result = new JavaElement();
    if (element instanceof IFile) {
      IJavaElement je = JavaCore.create((IFile) element);
      if (je instanceof ICompilationUnit) {
        result.compilationUnit = (ICompilationUnit) je;
      }
    }
    else if (element instanceof ICompilationUnit) {
      result.compilationUnit = (ICompilationUnit) element;
    } else if (element instanceof IPackageFragment) {
      result.packageFragment = (IPackageFragment) element;
    } else if (element instanceof IPackageFragmentRoot) {
      result.packageFragmentRoot = (IPackageFragmentRoot) element;
    } else if (element instanceof IJavaProject) {
      result.m_project = (IJavaProject) element;
    } else if (element instanceof IProject) {
      result.m_project = JavaCore.create((IProject) element);
    }

    // If we have a project, initialize the source folder too
    IResource resource = result.getResource();
    if (resource != null) {
      // By default, the target directory is the same as the class file
      result.sourceFolder = resource.getFullPath().removeLastSegments(1).toOSString();

      // Try to find a better target directory for the test class we're about to create
      for (IClasspathEntry entry : Utils.getSourceFolders(result.getProject())) {
        String source = entry.getPath().toOSString();
        if (source.endsWith("src/test/java")) {
          result.sourceFolder = source;
          break;
        } else if (source.contains("test")) {
          result.sourceFolder = source;
          break;
        }
        else if (resource.getFullPath().toString().startsWith(source)) {
          result.sourceFolder = source;
          break;
        }
      }
      if (result.sourceFolder.endsWith("src/main/java")) {
        result.sourceFolder = result.sourceFolder.replace("main", "test");
      }
    }

    return result;
  }

  /**
   * @return the source folders for this Java project.
   */
  public static List<IClasspathEntry> getSourceFolders(IJavaProject jp) {
    List<IClasspathEntry> result = Lists.newArrayList();
    try {
      for (IClasspathEntry entry : jp.getRawClasspath()) {
        if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
          result.add(entry);
        }
      }
    } catch (JavaModelException e) {
      e.printStackTrace();
    }
    return result;
  }

  /**
   * Open the given file in the editor.
   */
  public static void openFile(Shell shell, final IFile javaFile, IProgressMonitor monitor) {
    monitor.setTaskName("Opening file for editing...");
  	shell.getDisplay().asyncExec(new Runnable() {
      public void run() {
  			IWorkbenchPage page =
  				PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
  			try {
  				IDE.openEditor(page, javaFile, true);
  			} catch (PartInitException e) {
  			}
  		}
  	});
  	monitor.worked(1);
  }

}
