#!/usr/bin/env node

import { NameResolver } from '@0x/sol-resolver';
import { PackageJSON } from '@0x/types';
import { logUtils } from '@0x/utils';
import { CompilerOptions } from 'ethereum-types';
import * as fs from 'fs';
import * as _ from 'lodash';
import * as path from 'path';
import * as prettier from 'prettier';
import toSnakeCase = require('to-snake-case');

const SOLIDITY_EXTENSION = '.sol';
const DEFAULT_ARTIFACTS_DIR = 'artifacts';
const DEFAULT_CONTRACTS_DIR = 'contracts';
const DEFAULT_WRAPPERS_DIR = 'generated-wrappers';
const AUTO_GENERATED_BANNER = `/*
* -----------------------------------------------------------------------------
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/`;
const AUTO_GENERATED_BANNER_FOR_LISTS = `This list is auto-generated by contracts-gen. Don't edit manually.`;

(async () => {
    const packageDir = process.cwd();
    const compilerJSON = readJSONFile<CompilerOptions>('compiler.json');
    const contracts = compilerJSON.contracts;
    const contractsDir = compilerJSON.contractsDir || DEFAULT_CONTRACTS_DIR;
    const artifactsDir = compilerJSON.artifactsDir || DEFAULT_ARTIFACTS_DIR;
    const wrappersDir = DEFAULT_WRAPPERS_DIR;
    if (!_.isArray(contracts)) {
        throw new Error('Unable to run the generator bacause contracts key in compiler.json is not of type array');
    }
    const prettierConfig = await prettier.resolveConfig(packageDir);
    generateCompilerJSONContractsList(contracts, contractsDir, prettierConfig);
    generateArtifactsTs(contracts, artifactsDir, prettierConfig);
    generateWrappersTs(contracts, wrappersDir, prettierConfig);
    generateTsConfigJSONFilesList(contracts, artifactsDir, prettierConfig);
    generatePackageJSONABIConfig(contracts, artifactsDir, prettierConfig);
    process.exit(0);
})().catch(err => {
    logUtils.log(err);
    process.exit(1);
});

function generateCompilerJSONContractsList(
    contracts: string[],
    contractsDir: string,
    prettierConfig: prettier.Options | null,
): void {
    const COMPILER_JSON_FILE_PATH = 'compiler.json';
    const compilerJSON = readJSONFile<CompilerOptions>(COMPILER_JSON_FILE_PATH);
    compilerJSON.contracts = _.map(contracts, contract => {
        if (contract.endsWith(SOLIDITY_EXTENSION)) {
            // If it's already a relative path - NO-OP.
            return contract;
        } else {
            // If it's just a contract name - resolve it and rewrite.
            return new NameResolver(contractsDir).resolve(contract).path;
        }
    });
    compilerJSON.contracts = _.sortBy(compilerJSON.contracts);
    const compilerJSONString = JSON.stringify(compilerJSON);
    const formattedCompilerJSON = prettier.format(compilerJSONString, {
        ...prettierConfig,
        filepath: COMPILER_JSON_FILE_PATH,
    });
    fs.writeFileSync(COMPILER_JSON_FILE_PATH, formattedCompilerJSON);
}

function generateArtifactsTs(contracts: string[], artifactsDir: string, prettierConfig: prettier.Options | null): void {
    const imports = _.map(contracts, contract => {
        const contractName = path.basename(contract, SOLIDITY_EXTENSION);
        const importPath = path.join('..', artifactsDir, `${contractName}.json`);
        return `import * as ${contractName} from '${importPath}';`;
    });
    const sortedImports = _.sortBy(imports);
    const artifacts = _.map(contracts, contract => {
        const contractName = path.basename(contract, SOLIDITY_EXTENSION);
        if (contractName === 'ZRXToken') {
            // HACK(albrow): "as any" hack still required here because ZRXToken does not
            // conform to the v2 artifact type.
            return `${contractName}: (${contractName} as any) as ContractArtifact,`;
        } else {
            return `${contractName}: ${contractName} as ContractArtifact,`;
        }
    });
    const artifactsTs = `
    ${AUTO_GENERATED_BANNER}
    import { ContractArtifact } from 'ethereum-types';

    ${sortedImports.join('\n')}
    export const artifacts = {${artifacts.join('\n')}};
    `;
    const ARTIFACTS_TS_FILE_PATH = 'src/artifacts.ts';
    const formattedArtifactsTs = prettier.format(artifactsTs, { ...prettierConfig, filepath: ARTIFACTS_TS_FILE_PATH });
    fs.writeFileSync(ARTIFACTS_TS_FILE_PATH, formattedArtifactsTs);
}

function generateWrappersTs(contracts: string[], wrappersDir: string, prettierConfig: prettier.Options | null): void {
    const imports = _.map(contracts, contract => {
        const contractName = path.basename(contract, SOLIDITY_EXTENSION);
        const outputFileName = makeOutputFileName(contractName);
        const exportPath = path.join('..', wrappersDir, outputFileName);
        return `export * from '${exportPath}';`;
    });
    const sortedImports = _.sortBy(imports);
    const wrappersTs = `
    ${AUTO_GENERATED_BANNER}
    ${sortedImports.join('\n')}
    `;
    const WRAPPERS_TS_FILE_PATH = 'src/wrappers.ts';
    const formattedArtifactsTs = prettier.format(wrappersTs, { ...prettierConfig, filepath: WRAPPERS_TS_FILE_PATH });
    fs.writeFileSync(WRAPPERS_TS_FILE_PATH, formattedArtifactsTs);
}

function generateTsConfigJSONFilesList(
    contracts: string[],
    artifactsDir: string,
    prettierConfig: prettier.Options | null,
): void {
    const TS_CONFIG_FILE_PATH = 'tsconfig.json';
    const tsConfig = readJSONFile<any>(TS_CONFIG_FILE_PATH);
    tsConfig.files = _.map(contracts, contract => {
        const contractName = path.basename(contract, SOLIDITY_EXTENSION);
        const artifactPath = path.join(artifactsDir, `${contractName}.json`);
        return artifactPath;
    });
    tsConfig.files = _.sortBy(tsConfig.files);
    const tsConfigString = JSON.stringify(tsConfig);
    const formattedTsConfig = prettier.format(tsConfigString, { ...prettierConfig, filepath: TS_CONFIG_FILE_PATH });
    fs.writeFileSync(TS_CONFIG_FILE_PATH, formattedTsConfig);
}

function generatePackageJSONABIConfig(
    contracts: string[],
    artifactsDir: string,
    prettierConfig: prettier.Options | null,
): void {
    let packageJSON = readJSONFile<PackageJSON>('package.json');
    const contractNames = _.map(contracts, contract => {
        const contractName = path.basename(contract, SOLIDITY_EXTENSION);
        return contractName;
    });
    const sortedContractNames = _.sortBy(contractNames);
    packageJSON = {
        ...packageJSON,
        config: {
            ...packageJSON.config,
            'abis:comment': AUTO_GENERATED_BANNER_FOR_LISTS,
            abis: `${artifactsDir}/@(${sortedContractNames.join('|')}).json`,
        },
    };
    const PACKAGE_JSON_FILE_PATH = 'package.json';
    const packageJSONString = JSON.stringify(packageJSON);
    const formattedPackageJSON = prettier.format(packageJSONString, {
        ...prettierConfig,
        filepath: PACKAGE_JSON_FILE_PATH,
    });
    fs.writeFileSync(PACKAGE_JSON_FILE_PATH, formattedPackageJSON);
}

function makeOutputFileName(name: string): string {
    let fileName = toSnakeCase(name);
    // HACK: Snake case doesn't make a lot of sense for abbreviated names but we can't reliably detect abbreviations
    // so we special-case the abbreviations we use.
    fileName = fileName.replace('z_r_x', 'zrx').replace('e_r_c', 'erc');
    return fileName;
}

function readJSONFile<T>(filePath: string): T {
    const JSONString = fs.readFileSync(filePath, 'utf8');
    const parsed: T = JSON.parse(JSONString);
    return parsed;
}
