//===--- HostInfo.cpp - Host specific information -----------------------*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "clang/Driver/HostInfo.h"

#include "clang/Driver/Arg.h"
#include "clang/Driver/ArgList.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/DriverDiagnostic.h"
#include "clang/Driver/Option.h"
#include "clang/Driver/Options.h"

#include "llvm/ADT/StringMap.h"
#include "llvm/Support/Compiler.h"

#include "ToolChains.h"

#include <cassert>
 
using namespace clang::driver;

HostInfo::HostInfo(const Driver &D, const char *_Arch, const char *_Platform,
                   const char *_OS) 
  : TheDriver(D), Arch(_Arch), Platform(_Platform), OS(_OS) 
{
  
}

HostInfo::~HostInfo() {
}

namespace {

// Darwin Host Info

/// DarwinHostInfo - Darwin host information implementation.
class DarwinHostInfo : public HostInfo {
  /// Darwin version of host.
  unsigned DarwinVersion[3];

  /// GCC version to use on this host.
  unsigned GCCVersion[3];

  /// Cache of tool chains we have created.
  mutable llvm::StringMap<ToolChain *> ToolChains;

public:
  DarwinHostInfo(const Driver &D, const char *Arch, 
                 const char *Platform, const char *OS);
  ~DarwinHostInfo();

  virtual bool useDriverDriver() const;

  virtual types::ID lookupTypeForExtension(const char *Ext) const {
    types::ID Ty = types::lookupTypeForExtension(Ext);

    // Darwin always preprocesses assembly files (unless -x is used
    // explicitly).
    if (Ty == types::TY_PP_Asm)
      return types::TY_Asm;

    return Ty;
  }

  virtual ToolChain *getToolChain(const ArgList &Args, 
                                  const char *ArchName) const;
};

DarwinHostInfo::DarwinHostInfo(const Driver &D, const char *_Arch, 
                               const char *_Platform, const char *_OS) 
  : HostInfo(D, _Arch, _Platform, _OS) {
  
  assert((getArchName() == "i386" || getArchName() == "x86_64" || 
          getArchName() == "powerpc" || getArchName() == "powerpc64") &&
         "Unknown Darwin arch.");

  assert(memcmp(&getOSName()[0], "darwin", 6) == 0 &&
         "Unknown Darwin platform.");
  const char *Release = &getOSName()[6];
  bool HadExtra;
  if (!Driver::GetReleaseVersion(Release, DarwinVersion[0], DarwinVersion[1], 
                                 DarwinVersion[2], HadExtra)) {
    D.Diag(clang::diag::err_drv_invalid_darwin_version)
      << Release;
  }
  
  // We can only call 4.2.1 for now.
  GCCVersion[0] = 4;
  GCCVersion[1] = 2;
  GCCVersion[2] = 1;
}

DarwinHostInfo::~DarwinHostInfo() {
  for (llvm::StringMap<ToolChain*>::iterator
         it = ToolChains.begin(), ie = ToolChains.end(); it != ie; ++it)
    delete it->second;
}

bool DarwinHostInfo::useDriverDriver() const { 
  return true;
}

ToolChain *DarwinHostInfo::getToolChain(const ArgList &Args, 
                                        const char *ArchName) const {
  if (!ArchName) {
    ArchName = getArchName().c_str();

    // If no arch name is specified, infer it from the host and
    // -m32/-m64.
    if (Arg *A = Args.getLastArg(options::OPT_m32, options::OPT_m64)) {
      if (getArchName() == "i386" || getArchName() == "x86_64") {
        ArchName = 
          (A->getOption().getId() == options::OPT_m32) ? "i386" : "x86_64";
      } else if (getArchName() == "powerpc" || getArchName() == "powerpc64") {
        ArchName = (A->getOption().getId() == options::OPT_m32) ? "powerpc" : 
          "powerpc64";
      }
    } 
  } else {
    // Normalize arch name; we shouldn't be doing this here.
    if (strcmp(ArchName, "ppc") == 0)
      ArchName = "powerpc";
    else if (strcmp(ArchName, "ppc64") == 0)
      ArchName = "powerpc64";
  }

  ToolChain *&TC = ToolChains[ArchName];
  if (!TC) {
    if (strcmp(ArchName, "i386") == 0 || strcmp(ArchName, "x86_64") == 0)
      TC = new toolchains::Darwin_X86(*this, ArchName, 
                                      getPlatformName().c_str(), 
                                      getOSName().c_str(),
                                      DarwinVersion,
                                      GCCVersion);
    else
      TC = new toolchains::Darwin_GCC(*this, ArchName, 
                                      getPlatformName().c_str(), 
                                      getOSName().c_str());
  }

  return TC;
}

// Unknown Host Info

/// UnknownHostInfo - Generic host information to use for unknown
/// hosts.
class UnknownHostInfo : public HostInfo {
  /// Cache of tool chains we have created.
  mutable llvm::StringMap<ToolChain*> ToolChains;

public:
  UnknownHostInfo(const Driver &D, const char *Arch, 
                  const char *Platform, const char *OS);
  ~UnknownHostInfo();

  virtual bool useDriverDriver() const;

  virtual types::ID lookupTypeForExtension(const char *Ext) const {
    return types::lookupTypeForExtension(Ext);
  }

  virtual ToolChain *getToolChain(const ArgList &Args, 
                                  const char *ArchName) const;
};

UnknownHostInfo::UnknownHostInfo(const Driver &D, const char *Arch, 
                                 const char *Platform, const char *OS) 
  : HostInfo(D, Arch, Platform, OS) {
}

UnknownHostInfo::~UnknownHostInfo() {
  for (llvm::StringMap<ToolChain*>::iterator
         it = ToolChains.begin(), ie = ToolChains.end(); it != ie; ++it)
    delete it->second;
}

bool UnknownHostInfo::useDriverDriver() const { 
  return false;
}

ToolChain *UnknownHostInfo::getToolChain(const ArgList &Args, 
                                         const char *ArchName) const {
  assert(!ArchName && 
         "Unexpected arch name on platform without driver driver support.");
  
  // Automatically handle some instances of -m32/-m64 we know about.
  ArchName = getArchName().c_str();
  if (Arg *A = Args.getLastArg(options::OPT_m32, options::OPT_m64)) {
    if (getArchName() == "i386" || getArchName() == "x86_64") {
      ArchName = 
        (A->getOption().getId() == options::OPT_m32) ? "i386" : "x86_64";
    } else if (getArchName() == "powerpc" || getArchName() == "powerpc64") {
      ArchName = 
        (A->getOption().getId() == options::OPT_m32) ? "powerpc" : "powerpc64";
    }
  } 
  
  ToolChain *&TC = ToolChains[ArchName];
  if (!TC)
    TC = new toolchains::Generic_GCC(*this, ArchName, 
                                     getPlatformName().c_str(), 
                                     getOSName().c_str());

  return TC;
}

// FreeBSD Host Info

/// FreeBSDHostInfo -  FreeBSD host information implementation.
class FreeBSDHostInfo : public HostInfo {
  /// Cache of tool chains we have created.
  mutable llvm::StringMap<ToolChain*> ToolChains;

public:
  FreeBSDHostInfo(const Driver &D, const char *Arch, 
                  const char *Platform, const char *OS);
  ~FreeBSDHostInfo();

  virtual bool useDriverDriver() const;

  virtual types::ID lookupTypeForExtension(const char *Ext) const {
    return types::lookupTypeForExtension(Ext);
  }

  virtual ToolChain *getToolChain(const ArgList &Args, 
                                  const char *ArchName) const;
};

FreeBSDHostInfo::FreeBSDHostInfo(const Driver &D, const char *Arch, 
                                 const char *Platform, const char *OS) 
  : HostInfo(D, Arch, Platform, OS) {
}

FreeBSDHostInfo::~FreeBSDHostInfo() {
  for (llvm::StringMap<ToolChain*>::iterator
         it = ToolChains.begin(), ie = ToolChains.end(); it != ie; ++it)
    delete it->second;
}

bool FreeBSDHostInfo::useDriverDriver() const { 
  return false;
}

ToolChain *FreeBSDHostInfo::getToolChain(const ArgList &Args, 
                                         const char *ArchName) const {
  bool Lib32 = false;

  assert(!ArchName && 
         "Unexpected arch name on platform without driver driver support.");
  
  // On x86_64 we need to be able to compile 32-bits binaries as well.
  // Compiling 64-bit binaries on i386 is not supported. We don't have a
  // lib64.
  ArchName = getArchName().c_str();
  if (Args.hasArg(options::OPT_m32) && getArchName() == "x86_64") {
    ArchName = "i386";
    Lib32 = true;
  } 
  
  ToolChain *&TC = ToolChains[ArchName];
  if (!TC)
    TC = new toolchains::FreeBSD(*this, ArchName, 
                                 getPlatformName().c_str(), 
                                 getOSName().c_str(), Lib32);

  return TC;
}

}

const HostInfo *clang::driver::createDarwinHostInfo(const Driver &D,
                                                    const char *Arch, 
                                                    const char *Platform, 
                                                    const char *OS) {
  return new DarwinHostInfo(D, Arch, Platform, OS);
}

const HostInfo *clang::driver::createFreeBSDHostInfo(const Driver &D,
                                                     const char *Arch, 
                                                     const char *Platform, 
                                                     const char *OS) {
  return new FreeBSDHostInfo(D, Arch, Platform, OS);
}

const HostInfo *clang::driver::createUnknownHostInfo(const Driver &D,
                                                     const char *Arch, 
                                                     const char *Platform, 
                                                     const char *OS) {
  return new UnknownHostInfo(D, Arch, Platform, OS);
}
