// clang does not seem to compile if RTTI is enabled, but
// boost fails if it is not (shared_ptr seems to want it).
// Boost 1.35.0 added this (underdocumented) define to allow
// shared_ptr to compile without RTTI
#define BOOST_NO_TYPEID

#include "SchemeLibrary.hpp"
#include "SexpWriter.hpp"

#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/Decl.h"
#include "clang/AST/StmtIterator.h"
#include "clang/AST/TranslationUnit.h"
#include "clang/AST/Type.h"

#include "llvm/Support/Streams.h"
#include "llvm/Support/Debug.h"
#include "llvm/System/Path.h"

#include <libguile.h>

#include <boost/format.hpp>

#include <sstream>
#include <cstdlib>
#include <algorithm>
#include <numeric>
#include <map>

using namespace boost;
using namespace clang;
using namespace llvm;

namespace
{
  std::string getSchemeDir()
  {
    char * rawEnv = std::getenv("SIGDUMP_SCHEME_PATH");    
    if(rawEnv) return rawEnv;
    
    // If the environment variable is not set, fall back to the
    // compile-time default
    return SCHEME_LIBDIR;
  }
  
  SCM getFunction(const std::string & funcname)
  {
    SCM sym = scm_c_lookup(funcname.c_str());
    return scm_variable_ref(sym);
  }


  size_t bitWidth(const BuiltinType * BT)
  {   
    switch(BT->getKind())
    {
    case BuiltinType::Char_U:
    case BuiltinType::UChar:
    case BuiltinType::Char_S:
    case BuiltinType::SChar:
      return 8;
      
    case BuiltinType::UShort:
    case BuiltinType::Short:
      return 16;
      
    case BuiltinType::UInt:
    case BuiltinType::ULong:
    case BuiltinType::Int:
    case BuiltinType::Long:
      return 32;
      
    case BuiltinType::ULongLong:
    case BuiltinType::LongLong:
      return 64;
    default:
      return -1;
    }
  }
  
  
  class SexpWriter : public ASTConsumer
  {
  public:
    SexpWriter(const std::string & infile,
               const std::string & outfile)
    {
      scm_init_guile();

      // Add our local module directory to the Guile load path (note the escaped %s)
      std::string libpath = (format("(set! %%load-path (cons \"%s\" %%load-path))") % getSchemeDir()).str();
      SCM libpathEvalString = scm_from_locale_string(libpath.c_str());
      scm_eval_string(libpathEvalString);

      std::string loaderFile = (format("%s/loader.scm") % getSchemeDir()).str();
      scm_c_primitive_load(loaderFile.c_str());
      
      if(!outfile.empty())
      {
        SCM outFile = scm_from_locale_string(outfile.c_str());
        SCM outMode = scm_from_locale_string("w");

        _port = scm_open_file(outFile, outMode);
      }
      else
      {
        _port = scm_current_output_port();
      }

      // Pin the output port reference so that the
      // garbage collector can't kill it when we aren't
      // looking.  This is heap allocated, so the conservative
      // gc (which walks the stack) can't necessarily see it.
      scm_gc_protect_object(_port);
    }

    virtual ~SexpWriter()
    {
      if(_port != scm_current_output_port())
        scm_close_output_port(_port);

      // We are done, so unpin this.
      scm_gc_unprotect_object(_port);
    }

    virtual void HandleTopLevelDecl(Decl * D)
    {
      if(FunctionDecl * FD = dyn_cast<FunctionDecl>(D))
      {
        printFunction(FD);
      }
      else if(TypedefDecl * TD = dyn_cast<TypedefDecl>(D))
      {
        QualType qt = TD->getUnderlyingType();

        if(RecordType * RT = dyn_cast<RecordType>(qt))
        {
          anonTagMap[RT] = TD->getName();
          printStruct(RT->getDecl(), TD->getName());
        }
        else if(EnumType * ET = dyn_cast<EnumType>(qt))
        {
          anonTagMap[ET] = TD->getName();
          printEnum(ET->getDecl(), TD->getName());
        }
      }
      else if(EnumDecl * ED = dyn_cast<EnumDecl>(D))
      {
        printEnum(ED);
      }
      else if(RecordDecl * RD = dyn_cast<RecordDecl>(D))
      {
        printStruct(RD);        
      }
      else if(/*ScopedDecl * SD = */dyn_cast<ScopedDecl>(D))
      {
        DOUT << "Warning: ScopedDecl\n";
      }
      else
      {
        DOUT << "WARNING: unhandled top level decl.\n";
      }
    }

  private:
    void printFunction(FunctionDecl * FD)
    {
      if(!FD->isThisDeclarationADefinition() ||
         FD->isImplicit())
        return;

      
      QualType retType = FD->getResultType();

      SCM mkFN = getFunction("make-function");
      SCM functionName = scm_from_locale_string(FD->getName());
      SCM functionType;
      if(!retType->isVoidType())
        functionType = decodeType(retType);
      else
        functionType = SCM_BOOL_F;
      
      SCM func = scm_call_2(mkFN, functionName, functionType);

      for(FunctionDecl::param_iterator it = FD->param_begin(),
            ed = FD->param_end(); it != ed; ++it)
      {
        func = attachArgument(func, *it);
      }

      FunctionTypeProto * proto = dyn_cast<FunctionTypeProto>(FD->getType());
      if(proto && proto->isVariadic())
      {
        SCM variadicFn = getFunction("function-set-variadic!");
        scm_call_1(variadicFn, func);
      }
      
      SCM writeFN = getFunction("write-function");
      scm_call_2(writeFN, func, _port);
    }

    void internalPrintStruct(RecordDecl * RD, const std::string & name)
    {
      if(!RD->isDefinition())
        return;
      // This case should have been handled already
      assert(!isa<EnumDecl>(RD) && !RD->isEnum());
      if(RD->isUnion())
      {
        cerr << "Warning, union not supported\n";
        return;
      }

      assert(RD->isStruct());

      SCM mkStruct = getFunction("make-cstruct");
      SCM structName = scm_from_locale_string(name.c_str());
      SCM schemeStruct = scm_call_1(mkStruct, structName);
      
      for(RecordDecl::field_const_iterator it = RD->field_begin(),
            ed = RD->field_end(); it != ed; ++it)
      {
        schemeStruct = attachField(schemeStruct, *it);
      }

      SCM writeStruct = getFunction("write-struct");
      scm_call_2(writeStruct, schemeStruct, _port);
    }
    
    void printStruct(RecordDecl * RD, const std::string & name)
    {
      internalPrintStruct(RD, name);
    }

    void printStruct(RecordDecl * RD)
    {
      internalPrintStruct(RD, RD->getName());
    }

    void internalPrintEnum(EnumDecl * ED, const std::string & name)
    {
      if(!ED->isDefinition())
        return;
      
      SCM mkEnum = getFunction("make-cenum");
      SCM enumName = scm_from_locale_string(name.c_str());
      SCM schemeEnum = scm_call_1(mkEnum, enumName);

      for(const EnumConstantDecl * ecd = ED->getEnumConstantList();
          ecd; ecd = dyn_cast_or_null<EnumConstantDecl>(ecd->getNextDeclarator()))
      {
        attachEnumValue(schemeEnum, ecd);
      }
        
      SCM writeEnum = getFunction("write-enum");
      scm_call_2(writeEnum, schemeEnum, _port);
    }

    void printEnum(EnumDecl * ED, const std::string & name)
    {
      internalPrintEnum(ED, name);
    }

    void printEnum(EnumDecl * ED)
    {
      internalPrintEnum(ED, ED->getName());
    }

    /*!
      Convert a Clang BuiltinType to a SCM representation
      (just a symbol for now).

      TODO: Convert this function to take some form of
      TargetData as a parameter so that these symbolic
      names can be associated with a bit-width.
    */
    SCM builtinToSCM(const BuiltinType * BT)
    {
      if(BT->isBooleanType())
        return scm_from_locale_symbol("bool");

      if(BT->isVoidType())
        return scm_from_locale_symbol("void");

      if(BT->getKind() == BuiltinType::Float)
        return scm_from_locale_symbol("float");

      if(BT->getKind() == BuiltinType::Double)
        return scm_from_locale_symbol("double");

      if(BT->getKind() == BuiltinType::LongDouble)
        return scm_from_locale_symbol("longdouble");
      
      std::string ty = "int";
      if(BT->isUnsignedIntegerType())
        ty = "u" + ty;

      std::ostringstream os;
      os << ty << bitWidth(BT);
      //ty = str(format("%s%d") % ty % bitWidth(BT));
      ty = os.str();

      return scm_from_locale_symbol(ty.c_str());
    }

    SCM pointerToSCM(const PointerType * PT)
    {
      clang::QualType t = PT->getPointeeType();

      if(!t->isFunctionType())
      {
        SCM mkPointer = getFunction("make-pointer");
        return scm_call_1(mkPointer, decodeType(t));
      }

      SCM mkFpointer = getFunction("make-function-pointer");
      // Don't wrap function pointers in an additional layer
      return scm_call_1(mkFpointer, decodeType(t));
    }

    SCM recordToSCM(const TagType * TT)
    {
      TagDecl * td = TT->getDecl();

    
      // This will generate one of:
      // - make-union
      // - make-struct
      // - make-enum
      // - make-class (hopefully not, actually)
      std::ostringstream os;
      os << "make-" << td->getKindName();
      SCM mkRecord = getFunction(os.str());

      std::string typeName = td->getName();
      if(typeName.empty())
      {
        typeName = anonTagMap[TT];
      }
      
      SCM name = scm_from_locale_string(typeName.c_str());
      return scm_call_1(mkRecord, name);
    }

    SCM makeConstantArray(SCM innerType, SCM size)
    {
      SCM mkArray = getFunction("make-constant-array");
      return scm_call_2(mkArray, innerType, size);
    }
  
    SCM decodeType(clang::QualType T)
    {
      SCM s;
      clang::Type * pT = T.getTypePtr();
    
      if(const BuiltinType * bt = dyn_cast<const BuiltinType>(pT))
      {
        s = builtinToSCM(bt);
      }
      else if(const PointerType * pt =
              dyn_cast<const PointerType>(pT))
      {
        s = pointerToSCM(pt);
      }
      else if(const RecordType * rt = dyn_cast<const RecordType>(pT))
      {
        // This should handle enum/struct/union
        s = recordToSCM(rt);
      }
      else if(const TypedefType * tt = dyn_cast<const TypedefType>(pT))
      {
        // If the underlying type is a struct, we want to transform
        // this into the form (struct "realname") instead of just
        // "typedefed name".
        //
        // Otherwise, treat this as some other kind of typedef and
        // just consider it opaque (e.g. we only report the typedef named).
        clang::QualType qt = tt->LookThroughTypedefs();
        s = decodeType(qt);
      }
      else if(const EnumType * et = dyn_cast<const EnumType>(pT))
      {
        // cerr << "Warning: Enum unhandled in decodeType\n";
        s = recordToSCM(et);
      }
      else if(const FunctionTypeProto * ft =
              dyn_cast<const FunctionTypeProto>(pT))
      {
        SCM scm_rtype = SCM_BOOL_F;

        QualType rtype = ft->getResultType();
        if(!rtype->isVoidType())
        {
          scm_rtype = decodeType(rtype);
        }

        SCM arglist = SCM_EOL;
      
        for(FunctionTypeProto::arg_type_iterator it =
              ft->arg_type_begin(); it != ft->arg_type_end(); ++it)
        {
          SCM curArg = decodeType(*it);
          arglist = scm_cons(curArg, arglist);
        }

        arglist = scm_reverse(arglist);
        SCM ftfunc = getFunction("make-function-pointer-type");
        // Make a new type with the given arglist and return type
        s = scm_call_2(ftfunc, arglist, scm_rtype);
      }
      else if(const ConstantArrayType * CAT =
              dyn_cast<const ConstantArrayType>(pT))
      {
        const APInt &i = CAT->getSize();
        int realSize = i.getLimitedValue();

        clang::QualType innerType = CAT->getElementType();
        SCM inner = decodeType(innerType);

        // Clang seems to do some strange normalization
        // step where a pointer to a pointer to a structure shows
        // up as a pointer to an array of length 1 of that structure
        // type.  
        if(realSize == 1)
        {
          SCM mkPointer = getFunction("make-pointer");
          s = scm_call_1(mkPointer, inner);
        }
        else
        {     
          s = makeConstantArray(inner, scm_from_int(realSize));
        }
      }
      else if(const TypeOfType * TOT = dyn_cast<const TypeOfType>(pT))
      {
        s = decodeType(TOT->getUnderlyingType());
      }
      else
      {
        // if(complete)
        // {
        cerr << "Warning, unknown type: ";
        pT->dump();
        cerr << std::endl;
        // }
        // At this point, we probably want to treat this as
        // a typedefed type of some sort (barring the notes
        // below).  Typedefs are represented, for now, as just
        // strings containing their name (which can be resolved
        // with typedef mappings to be emitted).
        std::string name;
        pT->getAsStringInternal(name);
        s = scm_from_locale_string(name.c_str());
      }
      /*
        May need to handle:
        - ConstantArrayType (e.g. int A[100])
        - IncompleteArrayType (e.g. int A[])
        - GCC vector type (e.g. __attribute__((vector_size(n)))
      */
    
      return s;
    }

    SCM attachArgument(SCM func, ParmVarDecl * p)
    {
      Type* argType = p->getType().getTypePtr();
      SCM makeArg = getFunction("make-argument");
      SCM name = scm_from_locale_string(p->getName());
      SCM ty = decodeType(p->getType());
      SCM canonicalType = decodeType(argType->getDesugaredType());
      SCM arg = scm_call_3(makeArg, name, canonicalType, ty);

      SCM attachFN = getFunction("function-add-argument");
      scm_call_2(attachFN, func, arg);
      return func;
    }

    SCM attachField(SCM schemeStruct, const FieldDecl * d)
    {
      SCM mkType = getFunction("make-field");
    
      Type * fieldType = d->getType().getTypePtr();
      SCM canonicalType = decodeType(fieldType->getDesugaredType());
      SCM name = scm_from_locale_string(d->getName());

      SCM field = scm_call_2(mkType, name, canonicalType);

      SCM addField = getFunction("struct-add-field");
      scm_call_2(addField, schemeStruct, field);
    
      return schemeStruct;
    }

    void attachEnumValue(SCM schemeEnum, const EnumConstantDecl * ecd)
    {
      SCM mkEnum = getFunction("make-enum-value");

      SCM name = scm_from_locale_string(ecd->getName());
      const llvm::APSInt & initVal = ecd->getInitVal();
      uint64_t realInit = initVal.getLimitedValue();
      SCM value = scm_from_uint64(realInit);

      SCM enumVal = scm_call_2(mkEnum, name, value);

      SCM addValue = getFunction("enum-add-value");
      scm_call_2(addValue, schemeEnum, enumVal);
    }

    
  private:
    SCM _port;
    std::map<const TagType*,std::string> anonTagMap;
  };
}

ASTConsumer * CreateSexpWriter(const std::string & infile,
                               const std::string & outfile)
{
  return new SexpWriter(infile, outfile);
}
