//===- InputCmd.cpp -------------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "mcld/Script/InputCmd.h"

#include "mcld/LD/Archive.h"
#include "mcld/LD/ArchiveReader.h"
#include "mcld/LD/DynObjReader.h"
#include "mcld/LD/ObjectReader.h"
#include "mcld/MC/Attribute.h"
#include "mcld/MC/InputBuilder.h"
#include "mcld/Script/InputToken.h"
#include "mcld/Script/StringList.h"
#include "mcld/Support/MsgHandling.h"
#include "mcld/Support/Path.h"
#include "mcld/Support/raw_ostream.h"
#include "mcld/InputTree.h"
#include "mcld/LinkerScript.h"
#include "mcld/LinkerConfig.h"
#include "mcld/Module.h"

#include <llvm/Support/Casting.h>

#include <cassert>
#include <iostream>

namespace mcld {

//===----------------------------------------------------------------------===//
// InputCmd
//===----------------------------------------------------------------------===//
InputCmd::InputCmd(StringList& pStringList,
                   InputTree& pInputTree,
                   InputBuilder& pBuilder,
                   ObjectReader& pObjectReader,
                   ArchiveReader& pArchiveReader,
                   DynObjReader& pDynObjReader,
                   const LinkerConfig& pConfig)
    : ScriptCommand(ScriptCommand::INPUT),
      m_StringList(pStringList),
      m_InputTree(pInputTree),
      m_Builder(pBuilder),
      m_ObjectReader(pObjectReader),
      m_ArchiveReader(pArchiveReader),
      m_DynObjReader(pDynObjReader),
      m_Config(pConfig) {
}

InputCmd::~InputCmd() {
}

void InputCmd::dump() const {
  mcld::outs() << "INPUT ( ";
  bool prev = false, cur = false;
  for (StringList::const_iterator it = m_StringList.begin(),
                                  ie = m_StringList.end();
       it != ie;
       ++it) {
    assert((*it)->kind() == StrToken::Input);
    InputToken* input = llvm::cast<InputToken>(*it);
    cur = input->asNeeded();
    if (!prev && cur)
      mcld::outs() << "AS_NEEDED ( ";
    else if (prev && !cur)
      mcld::outs() << " )";

    if (input->type() == InputToken::NameSpec)
      mcld::outs() << "-l";
    mcld::outs() << input->name() << " ";

    prev = cur;
  }

  if (!m_StringList.empty() && prev)
    mcld::outs() << " )";

  mcld::outs() << " )\n";
}

void InputCmd::activate(Module& pModule) {
  LinkerScript& script = pModule.getScript();
  // construct the INPUT tree
  m_Builder.setCurrentTree(m_InputTree);

  bool is_begin_marked = false;
  InputTree::iterator input_begin;

  for (StringList::const_iterator it = m_StringList.begin(),
                                  ie = m_StringList.end();
       it != ie;
       ++it) {
    assert((*it)->kind() == StrToken::Input);
    InputToken* token = llvm::cast<InputToken>(*it);
    if (token->asNeeded())
      m_Builder.getAttributes().setAsNeeded();
    else
      m_Builder.getAttributes().unsetAsNeeded();

    switch (token->type()) {
      case InputToken::File: {
        sys::fs::Path path;

        // 1. Looking for file in the sysroot prefix, if a sysroot prefix is
        // configured and the filename starts with '/'
        if (script.hasSysroot() &&
            (token->name().size() > 0 && token->name()[0] == '/')) {
          path = script.sysroot();
          path.append(token->name());
        } else {
          // 2. Try to open the file in CWD
          path.assign(token->name());
          if (!sys::fs::exists(path)) {
            // 3. Search through the library search path
            sys::fs::Path* p =
                script.directories().find(token->name(), Input::Script);
            if (p != NULL)
              path = *p;
          }
        }

        if (!sys::fs::exists(path))
          fatal(diag::err_cannot_open_input) << path.filename() << path;

        m_Builder.createNode<InputTree::Positional>(
            path.filename().native(), path, Input::Unknown);
        break;
      }
      case InputToken::NameSpec: {
        const sys::fs::Path* path = NULL;
        // find out the real path of the namespec.
        if (m_Builder.getConstraint().isSharedSystem()) {
          // In the system with shared object support, we can find both archive
          // and shared object.
          if (m_Builder.getAttributes().isStatic()) {
            // with --static, we must search an archive.
            path = script.directories().find(token->name(), Input::Archive);
          } else {
            // otherwise, with --Bdynamic, we can find either an archive or a
            // shared object.
            path = script.directories().find(token->name(), Input::DynObj);
          }
        } else {
          // In the system without shared object support, only look for an
          // archive
          path = script.directories().find(token->name(), Input::Archive);
        }

        if (path == NULL)
          fatal(diag::err_cannot_find_namespec) << token->name();

        m_Builder.createNode<InputTree::Positional>(
            token->name(), *path, Input::Unknown);
        break;
      }
      default:
        assert(0 && "Invalid script token in INPUT!");
        break;
    }  // end of switch

    InputTree::iterator input = m_Builder.getCurrentNode();
    if (!is_begin_marked) {
      input_begin = input;
      is_begin_marked = true;
    }
    assert(*input != NULL);
    if (!m_Builder.setMemory(**input,
                             FileHandle::OpenMode(FileHandle::ReadOnly),
                             FileHandle::Permission(FileHandle::System))) {
      error(diag::err_cannot_open_input) << (*input)->name()
                                         << (*input)->path();
    }
    m_Builder.setContext(**input);
  }

  for (InputTree::iterator input = input_begin, ie = m_InputTree.end();
       input != ie;
       ++input) {
    bool doContinue = false;
    if (m_ObjectReader.isMyFormat(**input, doContinue)) {
      (*input)->setType(Input::Object);
      m_ObjectReader.readHeader(**input);
      m_ObjectReader.readSections(**input);
      m_ObjectReader.readSymbols(**input);
      pModule.getObjectList().push_back(*input);
    } else if (doContinue && m_DynObjReader.isMyFormat(**input, doContinue)) {
      (*input)->setType(Input::DynObj);
      m_DynObjReader.readHeader(**input);
      m_DynObjReader.readSymbols(**input);
      pModule.getLibraryList().push_back(*input);
    } else if (doContinue && m_ArchiveReader.isMyFormat(**input, doContinue)) {
      (*input)->setType(Input::Archive);
      if (m_Config.options().isInExcludeLIBS(**input)) {
        (*input)->setNoExport();
      }
      Archive archive(**input, m_Builder);
      m_ArchiveReader.readArchive(m_Config, archive);
      if (archive.numOfObjectMember() > 0) {
        m_InputTree.merge<InputTree::Inclusive>(input, archive.inputs());
      }
    } else {
      if (m_Config.options().warnMismatch())
        warning(diag::warn_unrecognized_input_file)
            << (*input)->path() << m_Config.targets().triple().str();
    }
  }
}

}  // namespace mcld