/*****
* name.cc
* Andy Hammerlindl2002/07/14
*
* Qualified names (such as x, f, builtin.sin, a.b.c.d, etc.) can be used
* either as variables or a type names.  This class stores qualified
* names used in nameExp and nameTy in the abstract syntax, and
* implements the exp and type functions.
*****/

#include "name.h"
#include "frame.h"
#include "record.h"
#include "coenv.h"
#include "inst.h"

namespace absyntax {
using namespace types;
using trans::access;
using trans::qualifiedAccess;
using trans::action;
using trans::READ;
using trans::WRITE;
using trans::CALL;
using vm::inst;


types::ty *signatureless(types::ty *t) {
 if (overloaded *o=dynamic_cast<overloaded *>(t))
   return o->signatureless();
 else
   return (t && !t->getSignature()) ? t : 0;
}


void name::forceEquivalency(action act, coenv &e,
                           types::ty *target, types::ty *source)
{
 if (act == READ)
   e.implicitCast(getPos(), target, source);
 else if (!equivalent(target, source)) {
   em.compiler(getPos());
   em << "type mismatch in variable: "
      << *target
      << " vs " << *source;
 }
}

frame *name::frameTrans(coenv &e)
{
 if (types::ty *t=signatureless(varGetType(e))) {
   if (t->kind == types::ty_record) {
     varTrans(READ, e, t);
     return ((record *)t)->getLevel();
   }
   else
     return 0;
 }
 else
   return tyFrameTrans(e);
}


types::ty *name::getType(coenv &e, bool tacit)
{
 types::ty *t=signatureless(varGetType(e));
 if (!tacit && t && t->kind == ty_error)
   // Report errors associated with regarding the name as a variable.
   varTrans(trans::READ, e, t);
 return t ? t : typeTrans(e, tacit);
}


varEntry *simpleName::getVarEntry(coenv &e)
{
 // If the name refers to a signatureless variable,
 // return its varEntry.
 types::ty *t=signatureless(varGetType(e));
 if (t)
     return e.e.lookupVarByType(id, t);

 // Otherwise, the name refers to a type.
 // Return its varEntry.
 tyEntry *ent = e.e.lookupTyEntry(id);
 return ent ? ent->v : 0;
}

void simpleName::varTrans(action act, coenv &e, types::ty *target)
{
 varEntry *v = e.e.lookupVarByType(id, target);

 if (v) {
   v->encode(act, getPos(), e.c);
   forceEquivalency(act, e, target, v->getType());
 }
 else {
   em.error(getPos());
   em << "no matching variable of name \'" << id << "\'";
 }
}

types::ty *simpleName::varGetType(coenv &e)
{
 return e.e.varGetType(id);
}

trans::varEntry *simpleName::getCallee(coenv &e, signature *sig)
{
 varEntry *ve = e.e.lookupVarBySignature(id, sig);
 return ve;
}

types::ty *simpleName::typeTrans(coenv &e, bool tacit)
{
 types::ty *t = e.e.lookupType(id);
 if (t) {
   return t;
 }
 else {
   if (!tacit) {
     em.error(getPos());
     em << "no type of name \'" << id << "\'";
   }
   return primError();
 }
}

tyEntry *simpleName::tyEntryTrans(coenv &e)
{
 tyEntry *ent = e.e.lookupTyEntry(id);
 if (!ent) {
   em.error(getPos());
   em << "no type of name \'" << id << "\'";
   return new tyEntry(primError(), nullptr, nullptr, nullPos);
 }
 return ent;
}

frame *simpleName::tyFrameTrans(coenv &e)
{
 tyEntry *ent = e.e.lookupTyEntry(id);
 if (ent && ent->t->kind==types::ty_record && ent->v) {
   ent->v->encode(READ, getPos(), e.c);
   return ent->v->getLevel();
 }
 else
   return 0;
}

void simpleName::prettyprint(ostream &out, Int indent)
{
 prettyindent(out, indent);
 out << "simpleName '" << id << "'\n";
}

AsymptoteLsp::SymbolLit simpleName::getLit() const
{
 return AsymptoteLsp::SymbolLit(static_cast<std::string>(id));
}

record *qualifiedName::castToRecord(types::ty *t, bool tacit)
{
 switch (t->kind) {
   case ty_overloaded:
     if (!tacit) {
       em.compiler(qualifier->getPos());
       em << "name::getType returned overloaded";
     }
     return 0;
   case ty_record:
     return (record *)t;
   case ty_error:
     return 0;
   default:
     if (!tacit) {
       em.error(qualifier->getPos());
       em << "type \'" << *t << "\' is not a structure";
     }
     return 0;
 }
}

bool qualifiedName::varTransVirtual(action act, coenv &e,
                                   types::ty *target, types::ty *qt)
{
 varEntry *v = qt->virtualField(id, target->getSignature());
 if (v) {
   // Push qualifier onto stack.
   qualifier->varTrans(READ, e, qt);

   v->encode(act, getPos(), e.c);

   // A virtual field was used.
   return true;
 }

 // No virtual field.
 return false;
}

void qualifiedName::varTransField(action act, coenv &e,
                                 types::ty *target, record *r)
{
 varEntry *v = r->e.lookupVarByType(id, target);

 if (v) {
   frame *f = qualifier->frameTrans(e);
   if (f)
     v->encode(act, getPos(), e.c, f);
   else
     v->encode(act, getPos(), e.c);

   forceEquivalency(act, e, target, v->getType());
 }
 else {
   em.error(getPos());
   em << "no matching field of name \'" << id << "\' in \'" << *r << "\'";
 }
}

void qualifiedName::varTrans(action act, coenv &e, types::ty *target)
{
 types::ty *qt = qualifier->getType(e);

 // Use virtual fields if applicable.
 if (varTransVirtual(act, e, target, qt))
   return;

 record *r = castToRecord(qt);
 if (r)
   varTransField(act, e, target, r);
}

types::ty *qualifiedName::varGetType(coenv &e)
{
 types::ty *qt = qualifier->getType(e, true);

 // Look for virtual fields.
 types::ty *t = qt->virtualFieldGetType(id);
 if (t)
   return t;

 record *r = castToRecord(qt, true);
 return r ? r->e.varGetType(id) : 0;
}

trans::varEntry *qualifiedName::getCallee(coenv &e, signature *sig)
{
 // getTypeAsCallee is an optimization attempt.  We don't try optimizing the
 // rarer qualifiedName call case.
 // TODO: See if this is worth implementing.
 //cout << "FAIL BY QUALIFIED NAME" << endl;
 return 0;
}

trans::varEntry *qualifiedName::getVarEntry(coenv &e)
{
 varEntry *qv = qualifier->getVarEntry(e);

 types::ty *qt = qualifier->getType(e, true);
 record *r = castToRecord(qt, true);
 if (r) {
   types::ty *t = signatureless(r->e.varGetType(id));
   varEntry *v = t ? r->e.lookupVarByType(id, t) : 0;
   return trans::qualifyVarEntry(qv,v);
 }
 else
   return qv;
}

types::ty *qualifiedName::typeTrans(coenv &e, bool tacit)
{
 types::ty *rt = qualifier->getType(e, tacit);

 record *r = castToRecord(rt, tacit);
 if (!r)
   return primError();

 tyEntry *ent = r->e.lookupTyEntry(id);
 if (ent) {
   if (!tacit)
     ent->reportPerm(READ, getPos(), e.c);
   return ent->t;
 }
 else {
   if (!tacit) {
     em.error(getPos());
     em << "no matching field or type of name \'" << id << "\' in \'"
        << *r << "\'";
   }
   return primError();
 }
}

tyEntry *qualifiedName::tyEntryTrans(coenv &e)
{
 types::ty *rt = qualifier->getType(e, false);

 record *r = castToRecord(rt, false);
 if (!r)
   return new tyEntry(primError(), nullptr, nullptr, nullPos);

 tyEntry *ent = r->e.lookupTyEntry(id);
 if (!ent) {
   em.error(getPos());
   em << "no matching type of name \'" << id << "\' in \'"
      << *r << "\'";
   return new tyEntry(primError(), nullptr, nullptr, nullPos);
 }
 ent->reportPerm(READ, getPos(), e.c);

 return trans::qualifyTyEntry(qualifier->getVarEntry(e), ent);
}

frame *qualifiedName::tyFrameTrans(coenv &e)
{
 frame *f=qualifier->frameTrans(e);
 tyEntry *ent = e.e.lookupTyEntry(id);
 if (ent && ent->t->kind==types::ty_record && ent->v) {
   if (f)
     ent->v->encode(READ, getPos(), e.c, f);
   else
     ent->v->encode(READ, getPos(), e.c);
   return ent->v->getLevel();
 }
 else
   return f;
}

void qualifiedName::prettyprint(ostream &out, Int indent)
{
 prettyindent(out, indent);
 out << "qualifiedName '" << id << "'\n";

 qualifier->prettyprint(out, indent+1);
}

AsymptoteLsp::SymbolLit qualifiedName::getLit() const
{
 std::vector<std::string> accessors;
 name const* currentScope = this->qualifier;

 while (auto* qn = dynamic_cast<qualifiedName const*>(currentScope))
 {
   accessors.push_back(static_cast<std::string>(qn->getName()));
   currentScope = qn->qualifier;
 }
 accessors.push_back(static_cast<std::string>(currentScope->getName()));

 return AsymptoteLsp::SymbolLit(static_cast<std::string>(id), std::move(accessors));
}

} // namespace absyntax