
// Compiler implementation of the D programming language
// Copyright (c) 1999-2012 by Digital Mars
// All Rights Reserved
// written by Walter Bright
// http://www.digitalmars.com
// License for redistribution is by either the Artistic License
// in artistic.txt, or the GNU General Public License in gnu.txt.
// See the included readme.txt for details.

#include <stdio.h>
#include <assert.h>

#include "root.h"
#include "dsymbol.h"
#include "import.h"
#include "identifier.h"
#include "module.h"
#include "scope.h"
#include "hdrgen.h"
#include "mtype.h"
#include "declaration.h"
#include "id.h"
#include "attrib.h"

/********************************* Import ****************************/

Import::Import(Loc loc, Identifiers *packages, Identifier *id, Identifier *aliasId,
        int isstatic)
    : Dsymbol()
{
    assert(id);
    this->loc = loc;
    this->packages = packages;
    this->id = id;
    this->aliasId = aliasId;
    this->isstatic = isstatic;
    protection = PROTundefined;
    pkg = NULL;
    mod = NULL;
}

void Import::addAlias(Identifier *name, Identifier *alias)
{
    if (isstatic)
        error("cannot have an import bind list");

    names.push(name);
    aliases.push(alias);
}

const char *Import::kind()
{
    return isstatic ? (char *)"static import" : (char *)"import";
}

enum PROT Import::prot()
{
    return protection;
}

Dsymbol *Import::syntaxCopy(Dsymbol *s)
{
    assert(!s);

    Import *si = new Import(loc, packages, id, aliasId, isstatic);

    for (size_t i = 0; i < names.dim; i++)
    {
        si->addAlias(names[i], aliases[i]);
    }

    return si;
}

void Import::load(Scope *sc)
{
    //printf("Import::load('%s')\n", toChars());

    // See if existing module
    DsymbolTable *dst = Package::resolve(packages, NULL, &pkg);
#if TARGET_NET  //dot net needs modules and packages with same name
#else
    if (pkg && pkg->isModule())
    {
        ::error(loc, "can only import from a module, not from a member of module %s. Did you mean `import %s : %s`?",
             pkg->toChars(), pkg->toPrettyChars(), id->toChars());
        mod = pkg->isModule(); // Error recovery - treat as import of that module
        return;
    }
#endif
    Dsymbol *s = dst->lookup(id);
    if (s)
    {
#if TARGET_NET
        mod = (Module *)s;
#else
        if (s->isModule())
            mod = (Module *)s;
        else
        {
            if (pkg)
            {
            ::error(loc, "can only import from a module, not from package %s.%s",
                pkg->toPrettyChars(), id->toChars());
            }
            else
            {
                ::error(loc, "can only import from a module, not from package %s",
                    id->toChars());
            }
        }
#endif
    }

    if (!mod)
    {
        // Load module
        mod = Module::load(loc, packages, id);
        if (mod)
        {
        dst->insert(id, mod);           // id may be different from mod->ident,
                                        // if so then insert alias
        if (!mod->importedFrom)
            mod->importedFrom = sc ? sc->module->importedFrom : Module::rootModule;
    }
    }
    if (!pkg)
        pkg = mod;

    //printf("-Import::load('%s'), pkg = %p\n", toChars(), pkg);
}

void escapePath(OutBuffer *buf, const char *fname)
{
    while (1)
    {
        switch (*fname)
        {
            case 0:
                return;
            case '(':
            case ')':
            case '\\':
                buf->writebyte('\\');
            default:
                buf->writebyte(*fname);
                break;
        }
        fname++;
    }
}

void Import::importAll(Scope *sc)
{
    if (!mod)
    {
       load(sc);
       mod->importAll(0);

       /* Default to private importing
        */
       enum PROT prot = sc->protection;
       if (!sc->explicitProtection)
           prot = PROTprivate;
       sc->scopesym->importScope(this, prot);
    }
}

void Import::semantic(Scope *sc)
{
    //printf("Import::semantic('%s')\n", toChars());

    // Load if not already done so
    if (!mod)
    {   load(sc);
        if (mod)
            mod->importAll(0);
    }

    if (mod)
    {
#if 0
        if (mod->loc.linnum != 0)
        {   /* If the line number is not 0, then this is not
             * a 'root' module, i.e. it was not specified on the command line.
             */
            mod->importedFrom = sc->module->importedFrom;
            assert(mod->importedFrom);
        }
#endif

        // Modules need a list of each imported module
        //printf("%s imports %s\n", sc->module->toChars(), mod->toChars());
        sc->module->aimports.push(mod);

        /* Default to private importing
         */
        enum PROT prot = sc->protection;
        if (!sc->explicitProtection)
            prot = PROTprivate;
        for (Scope *scd = sc; scd; scd = scd->enclosing)
        {
            if (scd->scopesym)
            {
                scd->scopesym->importScope(this, prot);
                break;
            }
        }

        mod->semantic();

        if (mod->needmoduleinfo)
        {   //printf("module4 %s because of %s\n", sc->module->toChars(), mod->toChars());
            sc->module->needmoduleinfo = 1;
        }

        if (aliasId)
        {
            AliasDeclaration *ad = new AliasDeclaration(loc, aliasId, mod);
            sc->insert(ad);
            ad->semantic(sc);
        }
    }
    //printf("-Import::semantic('%s'), pkg = %p\n", toChars(), pkg);

    if (global.params.moduleDeps != NULL &&
        // object self-imports itself, so skip that (Bugzilla 7547)
        !(id == Id::object && sc->module->ident == Id::object))
    {
        /* The grammar of the file is:
         *      ImportDeclaration
         *          ::= BasicImportDeclaration [ " : " ImportBindList ] [ " -> "
         *      ModuleAliasIdentifier ] "\n"
         *
         *      BasicImportDeclaration
         *          ::= ModuleFullyQualifiedName " (" FilePath ") : " Protection
         *              " [ " static" ] : " ModuleFullyQualifiedName " (" FilePath ")"
         *
         *      FilePath
         *          - any string with '(', ')' and '\' escaped with the '\' character
         */

        OutBuffer *ob = global.params.moduleDeps;

        ob->writestring(sc->module->toPrettyChars());
        ob->writestring(" (");
        escapePath(ob, sc->module->srcfile->toChars());
        ob->writestring(") : ");

        ProtDeclaration::protectionToCBuffer(ob, sc->protection);
        if (isstatic)
            StorageClassDeclaration::stcToCBuffer(ob, STCstatic);
        ob->writestring(": ");

        if (packages)
        {
            for (size_t i = 0; i < packages->dim; i++)
            {
                Identifier *pid = (*packages)[i];
                ob->printf("%s.", pid->toChars());
            }
        }

        ob->writestring(id->toChars());
        ob->writestring(" (");
        if (mod)
            escapePath(ob, mod->srcfile->toChars());
        else
            ob->writestring("???");
        ob->writebyte(')');

        for (size_t i = 0; i < names.dim; i++)
        {
            if (i == 0)
                ob->writebyte(':');
            else
                ob->writebyte(',');

            Identifier *name = names[i];
            Identifier *alias = aliases[i];

            if (!alias)
            {
                ob->printf("%s", name->toChars());
                alias = name;
            }
            else
                ob->printf("%s=%s", alias->toChars(), name->toChars());
        }

        if (aliasId)
                ob->printf(" -> %s", aliasId->toChars());

        ob->writenl();
    }

    //printf("-Import::semantic('%s'), pkg = %p\n", toChars(), pkg);
}

void Import::semantic2(Scope *sc)
{
    //printf("Import::semantic2('%s')\n", toChars());
    mod->semantic2();
    if (mod->needmoduleinfo)
    {   //printf("module5 %s because of %s\n", sc->module->toChars(), mod->toChars());
        sc->module->needmoduleinfo = 1;
    }
}

Dsymbol *Import::search(Loc loc, Identifier *ident, int flags)
{
    //printf("%s.Import::search(ident = '%s', flags = x%x)\n", toChars(), ident->toChars(), flags);

    if (!pkg)
    {   load(NULL);
        mod->semantic();
    }

    if (names.dim) // selective import
    {
        for (size_t i = 0; i < names.dim; i++)
        {
            Identifier *name = (Identifier *)names[i];
            Identifier *alias = (Identifier *)aliases[i];

            if (!alias)
                alias = name;

            if (alias->equals(ident))
                return mod->search(loc, name, flags);
        }

        // What should happen when renamed and selective imports are mixed?
        // This makes the whole module available with the renamed id.
        if (aliasId && aliasId->equals(ident))
            return mod;
    }
    else // non-selective import
    {
        // For renamed imports, only the alias name is visible.
        if (aliasId)
        {
            if (aliasId->equals(ident))
                return mod;
            return 0;
        }

        // For non-static imports, prefer symbols in the module over the module name.
        if (!isstatic)
        {
            Dsymbol *s = mod->search(loc, ident, flags);
            if (s)
                return s;
        }

        // Make the start of the package name available.
        if (pkg->ident->equals(ident))
        {
            return pkg;
        }
    }
    return 0;
}

void Import::toCBuffer(OutBuffer *buf, HdrGenState *hgs)
{
    if (hgs->hdrgen && id == Id::object)
        return;         // object is imported by default

    if (isstatic)
        buf->writestring("static ");
    buf->writestring("import ");
    if (aliasId)
    {
        buf->printf("%s = ", aliasId->toChars());
    }
    if (packages && packages->dim)
    {
        for (size_t i = 0; i < packages->dim; i++)
        {   Identifier *pid = (*packages)[i];

            buf->printf("%s.", pid->toChars());
        }
    }
    buf->printf("%s", id->toChars());
    if (names.dim)
    {
        buf->writestring(" : ");
        for (size_t i = 0; i < names.dim; i++)
        {
            Identifier *name = names[i];
            Identifier *alias = aliases[i];

            if (alias)
                buf->printf("%s = %s", alias->toChars(), name->toChars());
            else
                buf->printf("%s", name->toChars());

            if (i < names.dim - 1)
                buf->writestring(", ");
        }
    }
    buf->printf(";");
    buf->writenl();
}

char *Import::toChars()
{
    return id->toChars();
}
