package org.web3j.codegen;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.web3j.protocol.ObjectMapperFactory;
import org.web3j.protocol.core.methods.response.AbiDefinition;
import org.web3j.utils.Files;
import org.web3j.utils.Strings;

import static org.web3j.codegen.Console.exitError;
import static org.web3j.utils.Collection.tail;

/**
 * Java wrapper source code generator for Solidity ABI format.
 */
public class SolidityFunctionWrapperGenerator extends FunctionWrapperGenerator {

	private static final String USAGE = "solidity generate " + "[--javaTypes|--solidityTypes] " + "<input binary file>.bin <input abi file>.abi "
			+ "-p|--package <base package name> " + "-o|--output <destination base directory>";

	private final String binaryFileLocation;
	private final String absFileLocation;

	private SolidityFunctionWrapperGenerator(String binaryFileLocation, String absFileLocation, String destinationDirLocation, String basePackageName,
			boolean useJavaNativeTypes) {

		super(destinationDirLocation, basePackageName, useJavaNativeTypes);
		this.binaryFileLocation = binaryFileLocation;
		this.absFileLocation = absFileLocation;
	}

	public static void run(String[] args) throws Exception {
		if (args.length < 1 || !args[0].equals("generate")) {
			exitError(USAGE);
		} else {
			main(tail(args));
		}
	}

	public static void main(String[] args) throws Exception {

		String[] fullArgs;
		if (args.length == 6) {
			fullArgs = new String[args.length + 1];
			fullArgs[0] = JAVA_TYPES_ARG;
			System.arraycopy(args, 0, fullArgs, 1, args.length);
		} else {
			fullArgs = args;
		}

		if (fullArgs.length != 7) {
			exitError(USAGE);
		}

		boolean useJavaNativeTypes = useJavaNativeTypes(fullArgs[0], USAGE);

		String binaryFileLocation = parsePositionalArg(fullArgs, 1);
		String absFileLocation = parsePositionalArg(fullArgs, 2);
		String destinationDirLocation = parseParameterArgument(fullArgs, "-o", "--outputDir");
		String basePackageName = parseParameterArgument(fullArgs, "-p", "--package");

		if (binaryFileLocation.equals("") || absFileLocation.equals("") || destinationDirLocation.equals("") || basePackageName.equals("")) {
			exitError(USAGE);
		}

		new SolidityFunctionWrapperGenerator(binaryFileLocation, absFileLocation, destinationDirLocation, basePackageName, useJavaNativeTypes)
				.generate();
	}

	static List<AbiDefinition> loadContractDefinition(File absFile) throws IOException {
		ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
		AbiDefinition[] abiDefinition = objectMapper.readValue(absFile, AbiDefinition[].class);
		return Arrays.asList(abiDefinition);
	}

	private void generate() throws IOException, ClassNotFoundException {

		File binaryFile = new File(binaryFileLocation);
		if (!binaryFile.exists()) {
			exitError("Invalid input binary file specified: " + binaryFileLocation);
		}

		byte[] bytes = Files.readBytes(new File(binaryFile.toURI()));
		String binary = new String(bytes);

		File absFile = new File(absFileLocation);
		if (!absFile.exists() || !absFile.canRead()) {
			exitError("Invalid input ABI file specified: " + absFileLocation);
		}
		String fileName = absFile.getName();
		String contractName = getFileNameNoExtension(fileName);
		bytes = Files.readBytes(new File(absFile.toURI()));
		String abi = new String(bytes);

		List<AbiDefinition> functionDefinitions = loadContractDefinition(absFile);

		if (functionDefinitions.isEmpty()) {
			exitError("Unable to parse input ABI file");
		} else {
			String className = Strings.capitaliseFirstLetter(contractName);
			System.out.printf("Generating " + basePackageName + "." + className + " ... ");
			new SolidityFunctionWrapper(useJavaNativeTypes).generateJavaFiles(contractName, binary, abi, destinationDirLocation.toString(),
					basePackageName);
			System.out.println("File written to " + destinationDirLocation.toString() + "\n");
		}
	}

}