#include "oink.gr.gen.h"        // CCParse_Oink
#include "oink_global.h"
#include "garburator_blacklister.h"
#include "squash_util.h"

bool Blacklister::allowedToRewrite(Variable *v) {
  return blacklist.find(v) == blacklist.end() 
    && templateParamLocSet.find(v->loc) == templateParamLocSet.end();
}

void Blacklister::postvisitExpression(Expression *e) {
  static const StringRef str_Enumerate = globalStrTable("Enumerate");
  static const StringRef str_nsBaseHashtable = globalStrTable("nsBaseHashtable");
  E_funCall *fun = e->ifE_funCall();
  if (!fun) return;
  
  Variable *vFunc = expr2var(fun->func);
  if (!vFunc || !vFunc->type->isFunctionType()) return;

  FunctionType *ftype = vFunc->type->asFunctionType();
  //functions that are passed to enumerate..should not have their signatures changed
  if (ftype->isMethod() && vFunc->name == str_Enumerate) {
    Type *t = ftype->getReceiver()->type;
    if (!t->isPtrOrRef()) return;
    t = t->getAtType();
    if (!t->isCVAtomicType()) return;

    AtomicType *atomic = t->asCVAtomicType()->atomic;
    if (!atomic->isCompoundType()) return;

    if (atomic->asCompoundType()->name != str_nsBaseHashtable) return;

    //if we got here then fcall is definitly nsBaseHashtable::Enumerate
    E_addrOf *arg = fun->args->first()->expr->ifE_addrOf();
    if (!arg) return;
  
    E_variable *evar = arg->expr->ifE_variable();
    blacklistVar(evar->var);
    return;
  }
  blacklistTemplParams(fun);
}

void Blacklister::blacklistTemplParams(E_funCall *fun) {
  Variable *vFunc = expr2var(fun->func);
  if (!vFunc || !vFunc->type->isFunctionType()) return;

  FunctionType *ftype = vFunc->type->asFunctionType();
  
  SObjListIterNC<Variable> params(ftype->params);
  if (ftype->isMethod()) params.adv();
  
  for (FakeList<ArgExpression>* args = fun->args;
       ! args->isEmpty() && !params.isDone();
       args = args->butFirst(), params.adv()) {
    Expression *argExpr = args->first()->expr;
    // check for &COMPtr that are passed to templ params
    if (params.data()->type->isPointerType()
	&& argExpr->isE_addrOf()) {
      E_variable *evar = argExpr->asE_addrOf()->expr->ifE_variable();
      if (!evar) continue;
      argExpr = evar;
      // otherwise only deal with COMPtr&
    } else if (!params.data()->type->isReferenceType()) continue;
    Variable *paramVar = params.data();
    Type *t = paramVar->type->getAtType();
    if (!t->isCVAtomicType()) continue;
    
    AtomicType *aArg = t->asCVAtomicType()->atomic;
    if (!aArg->isCompoundType()) continue;
    if (aArg->asCompoundType()->name != str_nsCOMPtr) {
      continue;
    }
    if (templateParamLocSet.find(paramVar->loc) != templateParamLocSet.end()) {
      // usage of these functions should stay as is
      if( vFunc->name == str_getter_AddRefs
	  || vFunc->name == str_address_of
	  || vFunc->name == str_swap) continue;

      // local vars get marked as "don't bloody touch"      
      if (E_variable *evar = argExpr->ifE_variable()) {
	blacklistVar(evar->var);
      }
    }
  }
}

// be careful with COMPtr<T>* in constructors
// They don't get rewritten to avoid changing semantics
void Blacklister::visitConstructorDecl(D_func *df) {
  for (FakeList<ASTTypeId>* params = df->params;
       ! params->isEmpty();
       params = params->butFirst()) {
    ASTTypeId *p = params->first();
    Variable *v = p->decl->var;
    Type *t = v->type;
    if (!t->isPointerType()) continue;
    t = skipPtrOrRef(t);
    if (!t->isCompoundType()) continue;
    CompoundType *ct = t->asCompoundType();
  
    if (ct->name != str_nsCOMPtr) continue;
  
    blacklistVar(v);
  }
}

bool Blacklister::visitMember(Member *m) {
  switch (m->kind()) {
  case Member::MR_DECL: 
    {
      MR_decl *mr = m->asMR_decl();
      if (!mr || !mr->d->decllist) break;
      
      Declarator* d = mr->d->decllist->first();
      Variable *v = d->var;
      
      static const StringRef str_DestroyAnonymousContent = globalStrTable("DestroyAnonymousContent");
      if (!v || !v->name) break;
      if (v->type->isFunctionType()
	  && v->type->asFunctionType()->isConstructor()) {
	visitConstructorDecl(d->decl->skipGroups()->asD_func());
      }
      else if (v->name == str_DestroyAnonymousContent) blacklistVar(v);
    }
    break;
  case Member::MR_FUNC:
    {
      MR_func *f = m->asMR_func();
      Declarator *d = f->f->nameAndParams;
      if (f->f->dflags & DF_IMPLICIT) return false;

      if (!d->var->type->isFunctionType()) break;
      FunctionType *ft = d->var->type->asFunctionType();
      if (!ft->isConstructor()) break;
      visitConstructorDecl(d->decl->skipGroups()->asD_func());
    }
  default:
    break;
  }
  return true;
}

/** There isn't a way in elsa to figure out if a Variable->type is specified as a template
 * so I walk the template declarations and save the variable locations..because every time
 * templates are instantiated, the new variables are created..but location stays the same */
void Blacklister::postvisitDeclarator(Declarator *d) {
  // finds COMPtr<T>(&|*) params in functions and records their loc
  if (!d->var->type->isFunctionType()) return;
  FunctionType *ftype = d->var->type->asFunctionType();
  if (ftype->isConversionOperator()
      || d->decl->getDeclaratorId()->isPQ_operator()) return;
  static const StringRef str_CallQueryInterface = 
    globalStrTable("CallQueryInterface");
  if (d->var->name == str_CallQueryInterface) return;
  SObjListIterNC<Variable> params(ftype->params);
  if (ftype->isMethod()) params.adv();
  for (;
      !params.isDone();
       params.adv()) {
    Variable *v = params.data();
    Type *t = v->type;
    if (!t->isPtrOrRef()) continue;
    t = t->getAtType();
    if (!t->isCVAtomicType()) continue;
    CVAtomicType *cv = t->asCVAtomicType();
    // params of template classes that take weird pointers
    // are possible nsCOMPtr* candidates
    if (d->var->isTemplateFunction(true) && cv->atomic->isTypeVariable()) {
      //cerr << "sataneiro:" << toString(v->loc) << v->type->toString() << endl;
      templateParamLocSet.insert(v->loc);
      continue;
    }
    if (!cv->atomic->isPseudoInstantiation()) continue;
    
    PseudoInstantiation *pi = cv->atomic->asPseudoInstantiation();
    if (!pi->primary->isCompoundType()) continue;

    if (pi->primary->asCompoundType()->name != str_nsCOMPtr) continue;
    if (pi->args.isEmpty()) continue;
    STemplateArgument *ta = pi->args.first();
    
    if (!ta->getType()->isCVAtomicType()) continue;
    if (!ta->getType()->asCVAtomicType()->atomic->isTypeVariable()) continue;
    templateParamLocSet.insert(v->loc);
    //cerr << toString(v->loc) << ": " << v->toString() << ":" <<   v->name << endl;  
  }
}
void Blacklister::blacklistVar(Variable *v) 
{
    unsigned int size = blacklist.size();
    blacklist.insert(v);
    if (size != blacklist.size()) {
      cerr << toString(v->loc) <<  ": " << "Blacklisting " << v->name << endl;
    }
}
