/*
 * Copyright 2011-2012, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <bcinfo/BitcodeTranslator.h>
#include <bcinfo/BitcodeWrapper.h>
#include <bcinfo/MetadataExtractor.h>

#include <llvm/ADT/StringRef.h>
#include <llvm/Bitcode/ReaderWriter.h>
#include <llvm/IR/AssemblyAnnotationWriter.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#include <llvm/Support/FileSystem.h>
#include <llvm/Support/ManagedStatic.h>
#include <llvm/Support/MemoryBuffer.h>
#include <llvm/Support/ToolOutputFile.h>

#include <ctype.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>

#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <unistd.h>

#include <string>
#include <vector>

// This file corresponds to the standalone bcinfo tool. It prints a variety of
// information about a supplied bitcode input file.

std::string inFile;
std::string outFile;
std::string infoFile;

extern int opterr;
extern int optind;

bool translateFlag = false;
bool infoFlag = false;
bool verbose = true;

static int parseOption(int argc, char** argv) {
  int c;
  while ((c = getopt(argc, argv, "itv")) != -1) {
    opterr = 0;

    switch(c) {
      case '?':
        // ignore any error
        break;

      case 't':
        translateFlag = true;
        break;

      case 'i':
        // Turn off verbose so that we only generate the .info file.
        infoFlag = true;
        verbose = false;
        break;

      case 'v':
        verbose = true;
        break;

      default:
        // Critical error occurs
        return 0;
        break;
    }
  }

  if(optind >= argc) {
    fprintf(stderr, "input file required\n");
    return 0;
  }

  inFile = argv[optind];

  int l = inFile.length();
  if (l > 3 && inFile[l-3] == '.' && inFile[l-2] == 'b' && inFile[l-1] == 'c') {
    outFile = std::string(inFile.begin(), inFile.end() - 3) + ".ll";
    infoFile = std::string(inFile.begin(), inFile.end() - 3) + ".bcinfo";
  } else {
    outFile = inFile + ".ll";
    infoFile = inFile + ".bcinfo";
  }
  return 1;
}


static void dumpReduceInfo(FILE *info, const char *Kind, const char *Name) {
  if (Name)
    fprintf(info, "  %s(%s)\n", Kind, Name);
}

static int dumpInfo(bcinfo::MetadataExtractor *ME) {
  if (!ME) {
    return 1;
  }

  FILE *info = fopen(infoFile.c_str(), "w");
  if (!info) {
    fprintf(stderr, "Could not open info file %s\n", infoFile.c_str());
    return 2;
  }

  fprintf(info, "exportVarCount: %zu\n", ME->getExportVarCount());
  const char **varNameList = ME->getExportVarNameList();
  for (size_t i = 0; i < ME->getExportVarCount(); i++) {
    fprintf(info, "%s\n", varNameList[i]);
  }

  fprintf(info, "exportFuncCount: %zu\n", ME->getExportFuncCount());
  const char **funcNameList = ME->getExportFuncNameList();
  for (size_t i = 0; i < ME->getExportFuncCount(); i++) {
    fprintf(info, "%s\n", funcNameList[i]);
  }

  fprintf(info, "exportForEachCount: %zu\n",
          ME->getExportForEachSignatureCount());
  const char **nameList = ME->getExportForEachNameList();
  const uint32_t *sigList = ME->getExportForEachSignatureList();
  const uint32_t *inputCountList = ME->getExportForEachInputCountList();
  for (size_t i = 0; i < ME->getExportForEachSignatureCount(); i++) {
    fprintf(info, "%u - %s - %u\n", sigList[i], nameList[i],
            inputCountList[i]);
  }

  fprintf(info, "exportReduceCount: %zu\n", ME->getExportReduceCount());
  const bcinfo::MetadataExtractor::Reduce *reduceList =
      ME->getExportReduceList();
  for (size_t i = 0; i < ME->getExportReduceCount(); i++) {
    const bcinfo::MetadataExtractor::Reduce &reduce = reduceList[i];
    fprintf(info, "%u - %s - %u - %u\n", reduce.mSignature, reduce.mReduceName,
            reduce.mInputCount, reduce.mAccumulatorDataSize);
    dumpReduceInfo(info, "initializer",  reduce.mInitializerName);
    dumpReduceInfo(info, "accumulator",  reduce.mAccumulatorName);
    dumpReduceInfo(info, "combiner",     reduce.mCombinerName);
    dumpReduceInfo(info, "outconverter", reduce.mOutConverterName);
    dumpReduceInfo(info, "halter",       reduce.mHalterName);
  }

  fprintf(info, "objectSlotCount: %zu\n", ME->getObjectSlotCount());
  const uint32_t *slotList = ME->getObjectSlotList();
  for (size_t i = 0; i < ME->getObjectSlotCount(); i++) {
    fprintf(info, "%u\n", slotList[i]);
  }

  fclose(info);
  return 0;
}


static void dumpMetadata(bcinfo::MetadataExtractor *ME) {
  if (!ME) {
    return;
  }

  printf("RSFloatPrecision: ");
  switch (ME->getRSFloatPrecision()) {
  case bcinfo::RS_FP_Full:
    printf("Full\n\n");
    break;
  case bcinfo::RS_FP_Relaxed:
    printf("Relaxed\n\n");
    break;
  default:
    printf("UNKNOWN\n\n");
    break;
  }

  printf("exportVarCount: %zu\n", ME->getExportVarCount());
  const char **varNameList = ME->getExportVarNameList();
  for (size_t i = 0; i < ME->getExportVarCount(); i++) {
    printf("var[%zu]: %s\n", i, varNameList[i]);
  }
  printf("\n");

  printf("exportFuncCount: %zu\n", ME->getExportFuncCount());
  const char **funcNameList = ME->getExportFuncNameList();
  for (size_t i = 0; i < ME->getExportFuncCount(); i++) {
    printf("func[%zu]: %s\n", i, funcNameList[i]);
  }
  printf("\n");

  printf("exportForEachSignatureCount: %zu\n",
         ME->getExportForEachSignatureCount());
  const char **nameList = ME->getExportForEachNameList();
  const uint32_t *sigList = ME->getExportForEachSignatureList();
  const uint32_t *inputCountList = ME->getExportForEachInputCountList();
  for (size_t i = 0; i < ME->getExportForEachSignatureCount(); i++) {
    printf("exportForEachSignatureList[%zu]: %s - 0x%08x - %u\n", i, nameList[i],
           sigList[i], inputCountList[i]);
  }
  printf("\n");

  printf("exportReduceCount: %zu\n", ME->getExportReduceCount());
  const bcinfo::MetadataExtractor::Reduce *reduceList = ME->getExportReduceList();
  for (size_t i = 0; i < ME->getExportReduceCount(); i++) {
    const bcinfo::MetadataExtractor::Reduce &reduce = reduceList[i];
    printf("exportReduceList[%zu]: %s - 0x%08x - %u - %u\n", i, reduce.mReduceName,
           reduce.mSignature, reduce.mInputCount, reduce.mAccumulatorDataSize);
    dumpReduceInfo(stdout, "initializer",  reduce.mInitializerName);
    dumpReduceInfo(stdout, "accumulator",  reduce.mAccumulatorName);
    dumpReduceInfo(stdout, "combiner",     reduce.mCombinerName);
    dumpReduceInfo(stdout, "outconverter", reduce.mOutConverterName);
    dumpReduceInfo(stdout, "halter",       reduce.mHalterName);
  }
  printf("\n");

  printf("pragmaCount: %zu\n", ME->getPragmaCount());
  const char **keyList = ME->getPragmaKeyList();
  const char **valueList = ME->getPragmaValueList();
  for (size_t i = 0; i < ME->getPragmaCount(); i++) {
    printf("pragma[%zu]: %s - %s\n", i, keyList[i], valueList[i]);
  }
  printf("\n");

  printf("objectSlotCount: %zu\n", ME->getObjectSlotCount());
  const uint32_t *slotList = ME->getObjectSlotList();
  for (size_t i = 0; i < ME->getObjectSlotCount(); i++) {
    printf("objectSlotList[%zu]: %u\n", i, slotList[i]);
  }
  printf("\n");

  return;
}


static size_t readBitcode(const char **bitcode) {
  if (!inFile.length()) {
    fprintf(stderr, "input file required\n");
    return 0;
  }

  struct stat statInFile;
  if (stat(inFile.c_str(), &statInFile) < 0) {
    fprintf(stderr, "Unable to stat input file: %s\n", strerror(errno));
    return 0;
  }

  if (!S_ISREG(statInFile.st_mode)) {
    fprintf(stderr, "Input file should be a regular file.\n");
    return 0;
  }

  FILE *in = fopen(inFile.c_str(), "r");
  if (!in) {
    fprintf(stderr, "Could not open input file %s\n", inFile.c_str());
    return 0;
  }

  size_t bitcodeSize = statInFile.st_size;

  *bitcode = (const char*) calloc(1, bitcodeSize + 1);
  size_t nread = fread((void*) *bitcode, 1, bitcodeSize, in);

  if (nread != bitcodeSize)
      fprintf(stderr, "Could not read all of file %s\n", inFile.c_str());

  fclose(in);
  return nread;
}


static void releaseBitcode(const char **bitcode) {
  if (bitcode && *bitcode) {
    free((void*) *bitcode);
    *bitcode = nullptr;
  }
  return;
}


int main(int argc, char** argv) {
  if(!parseOption(argc, argv)) {
    fprintf(stderr, "failed to parse option\n");
    return 1;
  }

  const char *bitcode = nullptr;
  size_t bitcodeSize = readBitcode(&bitcode);

  unsigned int version = 0;

  bcinfo::BitcodeWrapper bcWrapper((const char *)bitcode, bitcodeSize);
  if (bcWrapper.getBCFileType() == bcinfo::BC_WRAPPER) {
    version = bcWrapper.getTargetAPI();
    if (verbose) {
      printf("Found bitcodeWrapper\n");
    }
  } else if (translateFlag) {
    version = 12;
  }

  if (verbose) {
    printf("targetAPI: %u\n", version);
    printf("compilerVersion: %u\n", bcWrapper.getCompilerVersion());
    printf("optimizationLevel: %u\n\n", bcWrapper.getOptimizationLevel());
  }

  std::unique_ptr<bcinfo::BitcodeTranslator> BT;
  BT.reset(new bcinfo::BitcodeTranslator(bitcode, bitcodeSize, version));
  if (!BT->translate()) {
    fprintf(stderr, "failed to translate bitcode\n");
    return 3;
  }

  std::unique_ptr<bcinfo::MetadataExtractor> ME;
  ME.reset(new bcinfo::MetadataExtractor(BT->getTranslatedBitcode(),
                                         BT->getTranslatedBitcodeSize()));
  if (!ME->extract()) {
    fprintf(stderr, "failed to get metadata\n");
    return 4;
  }

  if (verbose) {
    dumpMetadata(ME.get());

    const char *translatedBitcode = BT->getTranslatedBitcode();
    size_t translatedBitcodeSize = BT->getTranslatedBitcodeSize();

    llvm::LLVMContext &ctx = llvm::getGlobalContext();
    llvm::llvm_shutdown_obj called_on_exit;

    std::unique_ptr<llvm::MemoryBuffer> mem;

    mem = llvm::MemoryBuffer::getMemBuffer(
        llvm::StringRef(translatedBitcode, translatedBitcodeSize),
        inFile.c_str(), false);

    std::unique_ptr<llvm::Module> module;
    llvm::ErrorOr<std::unique_ptr<llvm::Module> > moduleOrError =
        llvm::parseBitcodeFile(mem.get()->getMemBufferRef(), ctx);
    std::error_code ec = moduleOrError.getError();
    if (!ec) {
        module = std::move(moduleOrError.get());
        ec = module->materializeAll();
    }
    std::string errmsg;
    if (ec) {
      errmsg = ec.message();
      module.reset();
      if (errmsg.size()) {
        fprintf(stderr, "error: %s\n", errmsg.c_str());
      } else {
        fprintf(stderr, "error: failed to parse bitcode file\n");
      }
      return 5;
    }

    std::unique_ptr<llvm::tool_output_file> tof(
        new llvm::tool_output_file(outFile.c_str(), ec,
                                   llvm::sys::fs::F_None));
    std::unique_ptr<llvm::AssemblyAnnotationWriter> ann;
    module->print(tof->os(), ann.get());

    tof->keep();
  }

  if (infoFlag) {
    if (dumpInfo(ME.get()) != 0) {
      fprintf(stderr, "Error dumping info file\n");
      return 6;
    }
  }

  releaseBitcode(&bitcode);

  return 0;
}