// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#include "rustllvm.h"

#include "llvm/Object/Archive.h"

#if LLVM_VERSION_MINOR >= 7
#include "llvm/Object/ArchiveWriter.h"
#endif

using namespace llvm;
using namespace llvm::object;

struct LLVMRustArchiveMember {
  const char *filename;
  const char *name;
  Archive::Child child;

  LLVMRustArchiveMember(): filename(NULL), name(NULL), child(NULL, NULL) {}
  ~LLVMRustArchiveMember() {}
};

#if LLVM_VERSION_MINOR >= 6
typedef OwningBinary<Archive> RustArchive;
#define GET_ARCHIVE(a) ((a)->getBinary())
#else
typedef Archive RustArchive;
#define GET_ARCHIVE(a) (a)
#endif

extern "C" void*
LLVMRustOpenArchive(char *path) {
    ErrorOr<std::unique_ptr<MemoryBuffer>> buf_or = MemoryBuffer::getFile(path,
                                                                          -1,
                                                                          false);
    if (!buf_or) {
        LLVMRustSetLastError(buf_or.getError().message().c_str());
        return nullptr;
    }

#if LLVM_VERSION_MINOR >= 6
    ErrorOr<std::unique_ptr<Archive>> archive_or =
        Archive::create(buf_or.get()->getMemBufferRef());

    if (!archive_or) {
        LLVMRustSetLastError(archive_or.getError().message().c_str());
        return nullptr;
    }

    OwningBinary<Archive> *ret = new OwningBinary<Archive>(
            std::move(archive_or.get()), std::move(buf_or.get()));
#else
    std::error_code err;
    Archive *ret = new Archive(std::move(buf_or.get()), err);
    if (err) {
        LLVMRustSetLastError(err.message().c_str());
        return nullptr;
    }
#endif

    return ret;
}

extern "C" void
LLVMRustDestroyArchive(RustArchive *ar) {
    delete ar;
}

struct RustArchiveIterator {
    Archive::child_iterator cur;
    Archive::child_iterator end;
};

extern "C" RustArchiveIterator*
LLVMRustArchiveIteratorNew(RustArchive *ra) {
    Archive *ar = GET_ARCHIVE(ra);
    RustArchiveIterator *rai = new RustArchiveIterator();
    rai->cur = ar->child_begin();
    rai->end = ar->child_end();
    return rai;
}

extern "C" const Archive::Child*
LLVMRustArchiveIteratorNext(RustArchiveIterator *rai) {
    if (rai->cur == rai->end)
        return NULL;
    const Archive::Child *cur = rai->cur.operator->();
    Archive::Child *ret = new Archive::Child(*cur);
    ++rai->cur;
    return ret;
}

extern "C" void
LLVMRustArchiveChildFree(Archive::Child *child) {
    delete child;
}

extern "C" void
LLVMRustArchiveIteratorFree(RustArchiveIterator *rai) {
    delete rai;
}

extern "C" const char*
LLVMRustArchiveChildName(const Archive::Child *child, size_t *size) {
    ErrorOr<StringRef> name_or_err = child->getName();
    if (name_or_err.getError())
        return NULL;
    StringRef name = name_or_err.get();
    *size = name.size();
    return name.data();
}

extern "C" const char*
LLVMRustArchiveChildData(Archive::Child *child, size_t *size) {
    StringRef buf;
#if LLVM_VERSION_MINOR >= 7
    ErrorOr<StringRef> buf_or_err = child->getBuffer();
    if (buf_or_err.getError()) {
      LLVMRustSetLastError(buf_or_err.getError().message().c_str());
      return NULL;
    }
    buf = buf_or_err.get();
#else
    buf = child->getBuffer();
#endif
    *size = buf.size();
    return buf.data();
}

extern "C" LLVMRustArchiveMember*
LLVMRustArchiveMemberNew(char *Filename, char *Name, Archive::Child *child) {
    LLVMRustArchiveMember *Member = new LLVMRustArchiveMember;
    Member->filename = Filename;
    Member->name = Name;
    if (child)
        Member->child = *child;
    return Member;
}

extern "C" void
LLVMRustArchiveMemberFree(LLVMRustArchiveMember *Member) {
    delete Member;
}

extern "C" int
LLVMRustWriteArchive(char *Dst,
                     size_t NumMembers,
                     const LLVMRustArchiveMember **NewMembers,
                     bool WriteSymbtab,
                     Archive::Kind Kind) {
#if LLVM_VERSION_MINOR >= 7
  std::vector<NewArchiveIterator> Members;

  for (size_t i = 0; i < NumMembers; i++) {
    auto Member = NewMembers[i];
    assert(Member->name);
    if (Member->filename) {
#if LLVM_VERSION_MINOR >= 8
      Members.push_back(NewArchiveIterator(Member->filename));
#else
      Members.push_back(NewArchiveIterator(Member->filename, Member->name));
#endif
    } else {
      Members.push_back(NewArchiveIterator(Member->child, Member->name));
    }
  }
#if LLVM_VERSION_MINOR >= 8
  auto pair = writeArchive(Dst, Members, WriteSymbtab, Kind, true, false);
#else
  auto pair = writeArchive(Dst, Members, WriteSymbtab, Kind, true);
#endif
  if (!pair.second)
    return 0;
  LLVMRustSetLastError(pair.second.message().c_str());
#else
  LLVMRustSetLastError("writing archives not supported with this LLVM version");
#endif
  return -1;
}
