package com.siberika.idea.pascal.ide.actions;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.SmartList;
import com.intellij.util.containers.SmartHashSet;
import com.siberika.idea.pascal.lang.psi.PasBlockGlobal;
import com.siberika.idea.pascal.lang.psi.PasEntityScope;
import com.siberika.idea.pascal.lang.psi.PasExportedRoutine;
import com.siberika.idea.pascal.lang.psi.PasFunctionDirective;
import com.siberika.idea.pascal.lang.psi.PasGenericTypeIdent;
import com.siberika.idea.pascal.lang.psi.PasRoutineImplDecl;
import com.siberika.idea.pascal.lang.psi.PasUsesClause;
import com.siberika.idea.pascal.lang.psi.PascalNamedElement;
import com.siberika.idea.pascal.lang.psi.PascalStructType;
import com.siberika.idea.pascal.lang.psi.impl.PasField;
import com.siberika.idea.pascal.lang.psi.impl.PasModuleImpl;
import com.siberika.idea.pascal.lang.psi.impl.PasRoutineImplDeclImpl;
import com.siberika.idea.pascal.lang.psi.impl.PascalModule;
import com.siberika.idea.pascal.lang.psi.impl.PascalModuleImpl;
import com.siberika.idea.pascal.lang.psi.impl.PascalRoutineImpl;
import com.siberika.idea.pascal.util.Filter;
import com.siberika.idea.pascal.util.PosUtil;
import com.siberika.idea.pascal.util.PsiUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
* Author: George Bakhtadze
* Date: 31/07/2015
*/
public class SectionToggle {
public static void getStructTarget(Collection<PsiElement> targets, PsiElement element) {
PascalStructType struct = PsiUtil.getStructByElement(element);
if (struct != null) {
Container cont = calcPrefix(new Container(struct));
retrieveFirstImplementations(targets, cont);
}
}
public static PsiElement getUsesTarget(PasUsesClause usesClause) {
if (usesClause != null) {
PsiElement impl = PsiUtil.getModuleImplementationSection(usesClause.getContainingFile());
PsiElement intf = PsiUtil.getModuleInterfaceSection(usesClause.getContainingFile());
if ((impl != null) && (intf != null)) {
if (PsiUtil.isParentOf(usesClause, impl)) {
return PsiTreeUtil.findChildOfType(intf, PasUsesClause.class);
} else if (PsiUtil.isParentOf(usesClause, intf)) {
return PsiTreeUtil.findChildOfType(impl, PasUsesClause.class);
}
}
}
return null;
}
// Non-strict
public static PsiElement getRoutineTarget(PascalRoutineImpl routine) {
Container cont = calcPrefix(new Container(routine));
if (routine instanceof PasExportedRoutine) {
return retrieveImplementation(cont, false);
} else if (routine instanceof PasRoutineImplDecl) {
return retrieveDeclaration(cont, false);
}
return null;
}
// Strict
public static PsiElement getImplementationOrDeclaration(PascalRoutineImpl routine) {
Container cont = calcPrefix(new Container(routine));
if (routine instanceof PasExportedRoutine) {
return retrieveImplementation(cont, true);
} else if (routine instanceof PasRoutineImplDeclImpl) {
PsiElement decl = retrieveDeclaration(cont, true);
if (decl != null) {
return decl;
} else {
return getRoutineForwardDeclaration((PasRoutineImplDeclImpl) routine);
}
}
return null;
}
@Nullable
public static PsiElement retrieveImplementation(PascalRoutineImpl routine, boolean strict) {
return retrieveImplementation(calcPrefix(new Container(routine)), strict);
}
@Nullable
private static PsiElement retrieveImplementation(Container container, boolean strict) {
if (null == container) {
return null;
}
PasField field = null;
if (container.scope instanceof PasModuleImpl) {
field = ((PasModuleImpl) container.scope).getPrivateField(container.prefix + PsiUtil.getFieldName(container.element));
field = checkRoutineField(field);
if (null == field && (!strict || !isOverloaded((PasExportedRoutine) container.element))) { // Try to find implementation w/o parameters
field = checkRoutineField(((PasModuleImpl) container.scope).getPrivateField(container.prefix + container.element.getName()));
if (strict && (field != null) && hasParametersOrReturnType((PascalRoutineImpl) field.getElement())) { // Only empty parameters list and return type allowed in strict mode
field = null;
}
}
}
return field != null ? field.getElement() : null;
}
public static boolean hasParametersOrReturnType(PascalRoutineImpl routine) {
return PsiUtil.hasParameters(routine) || routine.getFunctionTypeStr().length() > 0;
}
private static boolean isOverloaded(PasExportedRoutine routine) {
for (PasFunctionDirective directive : routine.getFunctionDirectiveList()) {
if (directive.getText().toUpperCase().startsWith("OVERLOAD")) {
return true;
}
}
return false;
}
private static PasField checkRoutineField(PasField field) {
return (field == null) || (field.fieldType == PasField.FieldType.ROUTINE) ? field : null;
}
private static void retrieveFirstImplementations(Collection<PsiElement> targets, Container container) {
if (null == container) {
return;
}
if (container.scope instanceof PasModuleImpl) {
String prefix = (container.prefix + container.element.getName()).toUpperCase();
Set<PasField> res = new TreeSet<PasField>(new Comparator<PasField>() {
@Override
public int compare(PasField o1, PasField o2) {
if ((null == o1.getElement()) || (null == o2.getElement())) {
return 0;
}
return o1.getElement().getTextOffset() - o2.getElement().getTextOffset();
}
});
for (PasField field : container.scope.getAllFields()) {
if ((field.fieldType == PasField.FieldType.ROUTINE) && (field.name.toUpperCase().startsWith(prefix))) {
res.add(field);
}
}
if (!res.isEmpty()) {
targets.add(res.iterator().next().getElement());
}
}
}
@Nullable
public static PsiElement retrieveDeclaration(PascalRoutineImpl routine, boolean strict) {
if (!PsiUtil.isNotNestedRoutine(routine)) { // Filter out nested routines
return null;
}
return retrieveDeclaration(calcPrefix(new Container(routine)), strict);
}
@Nullable
// In strict mode only correct declaration-implementation pairs will be found
private static PsiElement retrieveDeclaration(Container container, boolean strict) {
if (null == container) {
return null;
}
PasField field = null;
PasEntityScope scope = container.element.getContainingScope();
if (scope != null) {
String ns = container.element.getNamespace();
String name = PsiUtil.getFieldName(container.element).substring(StringUtils.isEmpty(ns) ? 0 : ns.length() + 1);
field = retrieveField(scope, name);
if (null == field) { // Try to find any routine with that name, ignoring parameters
field = retrieveField(scope, name.substring(0, name.indexOf('(')));
field = checkRoutineField(field);
if ((field != null) && strict &&
(isOverloaded((PasExportedRoutine) field.getElement()) || hasParametersOrReturnType((PascalRoutineImpl) container.element))) {
field = null; // Overloaded routines must repeat parameters
}
}
}
return field != null ? field.getElement() : null;
}
private static PasField retrieveField(PasEntityScope scope, String name) {
if (scope instanceof PasModuleImpl) {
return checkRoutineField(((PasModuleImpl) scope).getPublicField(name));
} else {
return checkRoutineField(scope.getField(name));
}
}
public static String getPrefix(PasEntityScope scope) {
return calcPrefix(new Container(scope)).prefix;
}
private static Container calcPrefix(Container current) {
while ((current.scope != null) && !(current.scope instanceof PascalModuleImpl)) {
current.scope = findOwner(current.scope);
if (current.scope instanceof PascalStructType) {
PsiElement nameEl = current.scope.getNameIdentifier();
if (nameEl instanceof PasGenericTypeIdent) {
current.prefix = ((PasGenericTypeIdent) nameEl).getRefNamedIdent().getName() + "." + current.prefix;
} else {
current.prefix = current.scope.getName() + "." + current.prefix;
}
} else if (current.scope instanceof PascalRoutineImpl) {
current.element = current.scope;
}
}
return current;
}
private static PasEntityScope findOwner(PasEntityScope scope) {
return scope.getContainingScope();
}
public static int getModuleMainDeclSectionOffset(PsiFile section) {
PasBlockGlobal block = PsiTreeUtil.findChildOfType(section, PasBlockGlobal.class);
if (block != null) {
List<PasRoutineImplDecl> impls = block.getRoutineImplDeclList();
if (!impls.isEmpty()) {
return impls.get(impls.size() - 1).getTextRange().getEndOffset();
}
return block.getBlockBody().getTextOffset();
}
return -1;
}
@Nullable
public static PsiElement getRoutineForwardDeclaration(@NotNull PasRoutineImplDeclImpl routine) {
PasEntityScope parent = routine.getContainingScope();
PasField field = null;
if (parent instanceof PascalModule) {
field = ((PascalModule) parent).getPrivateField(PsiUtil.getFieldName(routine));
} else if (parent instanceof PascalRoutineImpl) {
field = parent.getField(PsiUtil.getFieldName(routine));
}
return field != null ? field.getElement() : null;
}
private static class Container {
String prefix = "";
PasEntityScope element;
PasEntityScope scope;
public Container(PasEntityScope element) {
this.element = element;
this.scope = element;
}
}
public static int findImplPos(final PascalRoutineImpl routine) {
// collect all routine/method declarations in module/structure
// remember index of the given routine declaration
int res = -1;
int ind = -1;
PasEntityScope scope = routine.getContainingScope();
if (scope != null) {
List<PascalRoutineImpl> decls = collectFields(getDeclFields(scope), PasField.FieldType.ROUTINE, null);
for (int i = 0; i < decls.size(); i++) {
if (decls.get(i).isEquivalentTo(routine)) {
ind = i;
}
}
// starting from the index search for implementations
for (int i = ind - 1; (i >= 0) && (res < 0); i--) {
PsiElement impl = retrieveImplementation(calcPrefix(new Container(decls.get(i))), false);
res = impl != null ? impl.getTextRange().getEndOffset() : -1;
}
for (int i = ind + 1; (i < decls.size()) && (res < 0); i++) {
PsiElement impl = retrieveImplementation(calcPrefix(new Container(decls.get(i))), false);
if (impl != null) {
res = impl.getTextRange().getStartOffset();
}
}
}
return res;
}
@SuppressWarnings("unchecked")
public static <T extends PsiElement> List<T> collectFields(@NotNull Collection<PasField> fields, PasField.FieldType type, Filter<PasField> filter) {
List<T> result = new SmartList<T>();
Set<T> resultSet = new SmartHashSet<T>();
for (PasField field : fields) {
PascalNamedElement el = field.getElement();
//noinspection SuspiciousMethodCalls
if (((null == type) || (field.fieldType == type)) && !resultSet.contains(el) && ((null == filter) || filter.allow(field))) {
result.add((T) el);
resultSet.add((T) el);
}
}
return result;
}
@NotNull
public static Collection<PasField> getDeclFields(PasEntityScope scope) {
if (scope instanceof PascalModule) {
return ((PascalModule) scope).getPubicFields();
} else if (scope != null) {
return scope.getAllFields();
} else {
return Collections.emptyList();
}
}
// Returns suggested position of declaration in interface/structure of the specified implementation of routine/method
public static int findIntfPos(final PascalRoutineImpl routine) {
// collect all routine implementations in module with the same prefix
// remember index of the given routine implementation
int res = -1;
int ind = -1;
int member = -1; // To be used if right place will not be found
final PasEntityScope scope = routine.getContainingScope();
Container cont = calcPrefix(new Container(routine));
Collection<PasField> fields;
if (cont.scope instanceof PascalModule) {
fields = ((PascalModule) cont.scope).getPrivateFields();
} else {
fields = cont.scope.getAllFields();
}
List<PascalRoutineImpl> impls = collectFields(fields, PasField.FieldType.ROUTINE, new Filter<PasField>() {
@Override
public boolean allow(PasField value) {
return (value.getElement() instanceof PascalRoutineImpl) && (((PascalRoutineImpl) value.getElement()).getContainingScope() == scope);
}
});
for (int i = 0; i < impls.size(); i++) {
if (impls.get(i).isEquivalentTo(routine)) {
ind = i;
}
}
for (PasField field : getDeclFields(scope)) {
if (field.getElement() != null) {
if (field.fieldType == PasField.FieldType.PROPERTY) {
member = field.getElement().getTextRange().getStartOffset();
}
}
}
// starting from the index search for declarations
for (int i = ind - 1; (i >= 0) && (res < 0); i--) {
PsiElement decl = retrieveDeclaration(calcPrefix(new Container(impls.get(i))), false);
res = decl != null ? decl.getTextRange().getEndOffset() : -1;
}
for (int i = ind + 1; (i < impls.size()) && (res < 0); i++) {
PsiElement decl = retrieveDeclaration(calcPrefix(new Container(impls.get(i))), false);
if (decl != null) {
res = decl.getTextRange().getStartOffset();
}
}
res = res < 0 ? member : res;
if (res < 0) { // other declarations not found
if (scope instanceof PascalStructType) {
res = PosUtil.findPosInStruct((PascalStructType) scope, PasField.FieldType.ROUTINE, PasField.Visibility.PRIVATE.ordinal()).first;
} else {
PsiElement pos = PsiUtil.getModuleInterfaceSection(routine.getContainingFile());
if (null != pos) { // to the end of interface section
res = pos.getTextRange().getEndOffset();
} else { // program or library. Should not go here.
res = getModuleMainDeclSectionOffset(routine.getContainingFile());
}
}
}
return res;
}
}