/*******************************************************************************
 * Copyright (c) 2004, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.tools.internal;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;

import org.eclipse.jdt.core.dom.*;
import org.eclipse.jdt.core.dom.Modifier;

public class ReflectMethod extends ReflectItem implements JNIMethod {
	Method method;
	ReflectType returnType, returnType64;
	ReflectType[] paramTypes, paramTypes64;
	ReflectClass declaringClass;
	Boolean unique;
	
public ReflectMethod(ReflectClass declaringClass, Method method, String source, CompilationUnit unit) {
	this.method = method;
	this.declaringClass = declaringClass;	
	Class<?> returnType = method.getReturnType();
	Class<?>[] paramTypes = method.getParameterTypes();
	this.returnType = new ReflectType(returnType);
	this.returnType64 = this.returnType;
	this.paramTypes = new ReflectType[paramTypes.length];
	this.paramTypes64 = new ReflectType[paramTypes.length];
	for (int i = 0; i < this.paramTypes.length; i++) {
		this.paramTypes[i] = this.paramTypes64[i] = new ReflectType(paramTypes[i]);
	}
	boolean changes = false;
	if ((method.getModifiers() & Modifier.NATIVE) != 0) {
		changes = canChange64(returnType);
		if (!changes) {
			for (int i = 0; i < paramTypes.length && !changes; i++) {
				changes |= canChange64(paramTypes[i]);
			}
		}
	}
	if (changes && new File(declaringClass.sourcePath).exists()) {
		String name = method.getName();
		TypeDeclaration type = (TypeDeclaration)unit.types().get(0);
		MethodDeclaration decl = null;
		MethodDeclaration[] methods = type.getMethods();
		for (int i = 0; i < methods.length && decl == null; i++) {
			MethodDeclaration node = methods[i];
			if (node.getName().getIdentifier().equals(name)) {
				if (!declaringClass.getSimpleName(returnType).equals(node.getReturnType2().toString())) continue;
				List<?> parameters = node.parameters();
				if (parameters.size() != paramTypes.length) continue;
				decl = node;
				for (int j = 0; j < paramTypes.length; j++) {
					if (!declaringClass.getSimpleName(paramTypes[j]).equals(((SingleVariableDeclaration)parameters.get(j)).getType().toString())) {
						decl = null;
						break;
					}
				}
			}
		}
		for (int i = 0; i < paramTypes.length; i++) {
			if (canChange64(paramTypes[i])) {
				Class<?> clazz = paramTypes[i];
				SingleVariableDeclaration node = (SingleVariableDeclaration)decl.parameters().get(i);
				String s = source.substring(node.getStartPosition(), node.getStartPosition() + node.getLength());
				if (clazz == int.class && s.indexOf("int /*long*/") != -1) this.paramTypes64[i] = new ReflectType(long.class);
				else if (clazz == int[].class && (s.indexOf("int /*long*/") != -1 || s.indexOf("int[] /*long[]*/") != -1)) this.paramTypes64[i] = new ReflectType(long[].class);
				else if (clazz == float.class && s.indexOf("float /*double*/") != -1) this.paramTypes64[i] = new ReflectType(double.class);
				else if (clazz == float[].class && (s.indexOf("float /*double*/") != -1|| s.indexOf("float[] /*double[]*/") != -1)) this.paramTypes64[i] = new ReflectType(double[].class);
				else if (clazz == long.class && s.indexOf("long /*int*/") != -1) this.paramTypes[i] = new ReflectType(int.class);
				else if (clazz == long[].class && (s.indexOf("long /*int*/") != -1|| s.indexOf("long[] /*int[]*/") != -1)) this.paramTypes[i] = new ReflectType(int[].class);
				else if (clazz == double.class && s.indexOf("double /*float*/") != -1) this.paramTypes[i] = new ReflectType(float.class);
				else if (clazz == double[].class && (s.indexOf("double /*float*/") != -1|| s.indexOf("double[] /*float[]*/") != -1)) this.paramTypes[i] = new ReflectType(float[].class);
			}
		}
		if (canChange64(returnType)) {
			Class<?> clazz = returnType;
			ASTNode node = decl.getReturnType2();
			String s = source.substring(node.getStartPosition(), decl.getName().getStartPosition());
			if (clazz == int.class && s.indexOf("int /*long*/") != -1) this.returnType64 = new ReflectType(long.class);
			else if (clazz == int[].class && (s.indexOf("int /*long*/") != -1 || s.indexOf("int[] /*long[]*/") != -1)) this.returnType64 = new ReflectType(long[].class);
			else if (clazz == float.class && s.indexOf("float /*double*/") != -1) this.returnType64 = new ReflectType(double.class);
			else if (clazz == float[].class && (s.indexOf("float /*double*/") != -1|| s.indexOf("float[] /*double[]*/") != -1)) this.returnType64 = new ReflectType(double[].class);
			else if (clazz == long.class && s.indexOf("long /*int*/") != -1) this.returnType = new ReflectType(int.class);
			else if (clazz == long[].class && (s.indexOf("long /*int*/") != -1|| s.indexOf("long[] /*int[]*/") != -1)) this.returnType = new ReflectType(int[].class);
			else if (clazz == double.class && s.indexOf("double /*float*/") != -1) this.returnType = new ReflectType(float.class);
			else if (clazz == double[].class && (s.indexOf("double /*float*/") != -1|| s.indexOf("double[] /*float[]*/") != -1)) this.returnType = new ReflectType(float[].class);
		}		
	}
}

@Override
public int hashCode() {
	return method.hashCode();
}

@Override
public boolean equals(Object obj) {
	if (!(obj instanceof ReflectMethod)) return false;
	return ((ReflectMethod)obj).method.equals(method);
}

@Override
public JNIClass getDeclaringClass() {
	return declaringClass;
}

@Override
public int getModifiers() {
	return method.getModifiers();
}

@Override
public String getName() {
	return method.getName();
}

@Override
public boolean isNativeUnique() {
	if (unique != null) return unique.booleanValue();
	boolean result = true;
	String name = getName();
	JNIMethod[] methods = declaringClass.getDeclaredMethods();
	for (int i = 0; i < methods.length; i++) {
		JNIMethod mth = methods[i];
		if ((mth.getModifiers() & Modifier.NATIVE) != 0 &&
			this != mth && !this.equals(mth) &&
			name.equals(mth.getName()))
			{
				result = false;
				break;
			}
	}
	unique = Boolean.valueOf(result);
	return result;
}

@Override
public JNIType[] getParameterTypes() {
	return paramTypes;
}

@Override
public JNIType[] getParameterTypes64() {
	return paramTypes64;
}

@Override
public JNIParameter[] getParameters() {
	Class<?>[] paramTypes = method.getParameterTypes();
	ReflectParameter[] result = new ReflectParameter[paramTypes.length];
	for (int i = 0; i < paramTypes.length; i++) {
		result[i] = new ReflectParameter(this, i);
	}
	return result;
}

@Override
public JNIType getReturnType() {
	return returnType;
}

@Override
public JNIType getReturnType64() {
	return returnType64;
}

@Override
public String getAccessor() {
	return (String)getParam("accessor");
}

@Override
public String getExclude() {
	return (String)getParam("exclude");
}

@Override
public String getMetaData() {
	String className = getDeclaringClass().getSimpleName();
	String key = className + "_" + JNIGenerator.getFunctionName(this);
	MetaData metaData = declaringClass.metaData;
	String value = metaData.getMetaData(key, null);
	if (value == null) {
		key = className + "_" + method.getName();
		value = metaData.getMetaData(key, null);
	}
	/*
	* Support for 64 bit port.
	*/
	if (value == null) {
		JNIType[] paramTypes = getParameterTypes();
		if (convertTo32Bit(paramTypes, true)) {
			key = className + "_" + JNIGenerator.getFunctionName(this, paramTypes);
			value = metaData.getMetaData(key, null);
		}
		if (value == null) {
			paramTypes = getParameterTypes();
			if (convertTo32Bit(paramTypes, false)) {
				key = className + "_" + JNIGenerator.getFunctionName(this, paramTypes);
				value = metaData.getMetaData(key, null);
			}
		}
	}
	/*
	* Support for lock.
	*/
	if (value == null && method.getName().startsWith("_")) {
		key = className + "_" + JNIGenerator.getFunctionName(this).substring(2);
		value = metaData.getMetaData(key, null);
		if (value == null) {
			key = className + "_" + method.getName().substring(1);
			value = metaData.getMetaData(key, null);
		}
	}
	if (value == null) value = "";	
	return value;
}

@Override
public void setAccessor(String str) { 
	setParam("accessor", str);
}

@Override
public void setExclude(String str) { 
	setParam("exclude", str);
}

@Override
public void setMetaData(String value) {
	String key;
	String className = declaringClass.getSimpleName();
	if (isNativeUnique()) {
		key = className + "_" + method.getName ();
	} else {
		key = className + "_" + JNIGenerator.getFunctionName(this);
	}
	declaringClass.metaData.setMetaData(key, value);
}

@Override
public String toString() {
	return method.toString();
}

}
