/// Author: Aziz Köksal
/// License: GPL3
/// $(Maturity high)
module dil.ast.Declarations;

public import dil.ast.Declaration;
import dil.ast.Node,
       dil.ast.Expression,
       dil.ast.Types,
       dil.ast.Statements,
       dil.ast.Parameters,
       dil.ast.NodeCopier;
import dil.lexer.IdTable;
import dil.semantic.Symbols;
import dil.Enums;
import common;

class CompoundDeclaration : Declaration
{
  this()
  {
    hasBody = true;
    mixin(set_kind);
  }

  void opCatAssign(Declaration d)
  {
    addChild(d);
  }

  void opCatAssign(CompoundDeclaration ds)
  {
    addChildren(ds.children);
  }

  Declaration[] decls()
  {
    return cast(Declaration[])this.children;
  }

  void decls(Declaration[] decls)
  {
    this.children = decls;
  }

  mixin(copyMethod);
}

/// Single semicolon.
class EmptyDeclaration : Declaration
{
  this()
  {
    mixin(set_kind);
  }
  mixin(copyMethod);
}

/// Illegal declarations encompass all tokens that don't
/// start a DeclarationDefinition.
/// See_Also: dil.lexer.Token.isDeclDefStartToken()
class IllegalDeclaration : Declaration
{
  this()
  {
    mixin(set_kind);
  }
  mixin(copyMethod);
}

/// FQN = fully qualified name
alias Token*[] ModuleFQN; // Identifier ("." Identifier)*

class ModuleDeclaration : Declaration
{
  Token* typeIdent;
  Token* moduleName;
  Token*[] packages;
  this(Token* ident, ModuleFQN moduleFQN)
  {
    mixin(set_kind);
    assert(moduleFQN.length != 0);
    this.typeIdent = ident;
    this.moduleName = moduleFQN[$-1];
    this.packages = moduleFQN[0..$-1];
  }

  char[] getFQN()
  {
    auto pname = getPackageName('.');
    if (pname.length)
      return pname ~ "." ~ getName();
    else
      return getName();
  }

  char[] getName()
  {
    if (moduleName)
      return moduleName.ident.str;
    return null;
  }

  char[] getPackageName(char separator)
  {
    char[] pname;
    foreach (pckg; packages)
      if (pckg)
        pname ~= pckg.ident.str ~ separator;
    if (pname.length)
      pname = pname[0..$-1]; // Remove last separator
    return pname;
  }

  mixin(copyMethod);
}

class ImportDeclaration : Declaration
{
  private alias Token*[] Ids;
  ModuleFQN[] moduleFQNs;
  Ids moduleAliases;
  Ids bindNames;
  Ids bindAliases;

  this(ModuleFQN[] moduleFQNs, Ids moduleAliases, Ids bindNames,
       Ids bindAliases, bool isStatic)
  {
    mixin(set_kind);
    this.moduleFQNs = moduleFQNs;
    this.moduleAliases = moduleAliases;
    this.bindNames = bindNames;
    this.bindAliases = bindAliases;
    if (isStatic)
      this.stcs |= StorageClass.Static;
  }

  char[][] getModuleFQNs(char separator)
  {
    char[][] FQNs;
    foreach (moduleFQN; moduleFQNs)
    {
      char[] FQN;
      foreach (ident; moduleFQN)
        if (ident)
          FQN ~= ident.ident.str ~ separator;
      FQNs ~= FQN[0..$-1]; // Remove last separator
    }
    return FQNs;
  }

  mixin(copyMethod);
}

class AliasDeclaration : Declaration
{
  Declaration decl;
  this(Declaration decl)
  {
    mixin(set_kind);
    addChild(decl);
    this.decl = decl;
  }
  mixin(copyMethod);
}

class AliasThisDeclaration : Declaration
{
  Token* ident;
  this(Token* ident)
  {
    mixin(set_kind);
    this.ident = ident;
  }
  mixin(copyMethod);
}

class TypedefDeclaration : Declaration
{
  Declaration decl;
  this(Declaration decl)
  {
    mixin(set_kind);
    addChild(decl);
    this.decl = decl;
  }
  mixin(copyMethod);
}

class EnumDeclaration : Declaration
{
  Token* name;
  TypeNode baseType;
  EnumMemberDeclaration[] members;
  this(Token* name, TypeNode baseType, EnumMemberDeclaration[] members, bool hasBody)
  {
    super.hasBody = hasBody;
    mixin(set_kind);
    addOptChild(baseType);
    addOptChildren(members);

    this.name = name;
    this.baseType = baseType;
    this.members = members;
  }

  /// Returns the Identifier object of a variable.
  Identifier* nameId()
  {
    return name ? name.ident : null;
  }

  Enum symbol;

  mixin(copyMethod);
}

class EnumMemberDeclaration : Declaration
{
  TypeNode type; // D 2.0
  Token* name;
  Expression value;
  this(Token* name, Expression value)
  {
    mixin(set_kind);
    addOptChild(value);

    this.name = name;
    this.value = value;
  }

  // D 2.0
  this(TypeNode type, Token* name, Expression value)
  {
    addOptChild(type);
    this.type = type;
    this(name, value);
  }

  /// Returns the Identifier object of a variable.
  Identifier* nameId()
  {
    return name ? name.ident : null;
  }

  EnumMember symbol;

  mixin(copyMethod);
}

class TemplateDeclaration : Declaration
{
  Token* name;
  TemplateParameters tparams;
  Expression constraint; // D 2.0
  CompoundDeclaration decls;
  bool isMixin; /// Is this a mixin template? (D2 feature.)

  this(Token* name, TemplateParameters tparams, Expression constraint,
       CompoundDeclaration decls)
  {
    super.hasBody = true;
    mixin(set_kind);
    addChild(tparams);
    addOptChild(constraint);
    addChild(decls);

    this.name = name;
    this.tparams = tparams;
    this.constraint = constraint;
    this.decls = decls;
  }

  /// Returns the Identifier object of this declaration. May be null.
  Identifier* nameId()
  {
    assert(name !is null);
    return name.ident;
  }

  /// Returns true if this is a template that wraps a class, struct etc.
  bool isWrapper()
  { // "mixin template" is D2.
    return begin.kind != TOK.Template && begin.kind != TOK.Mixin;
  }

  Template symbol; /// The template symbol for this declaration.

  mixin(copyMethod);
}

// Note: tparams is commented out because the Parser wraps declarations
//       with template parameters inside a TemplateDeclaration.

abstract class AggregateDeclaration : Declaration
{
  Token* name;
//   TemplateParameters tparams;
  CompoundDeclaration decls;
  this(Token* name, /+TemplateParameters tparams, +/CompoundDeclaration decls)
  {
    super.hasBody = decls !is null;
    this.name = name;
//     this.tparams = tparams;
    this.decls = decls;
  }

  /// Returns the Identifier object of this declaration. May be null.
  Identifier* nameId()
  {
    return name ? name.ident : null;
  }
}

class ClassDeclaration : AggregateDeclaration
{
  BaseClassType[] bases;
  this(Token* name, /+TemplateParameters tparams, +/BaseClassType[] bases, CompoundDeclaration decls)
  {
    super(name, /+tparams, +/decls);
    mixin(set_kind);
//     addChild(tparams);
    addOptChildren(bases);
    addOptChild(decls);

    this.bases = bases;
  }

  Class symbol; /// The class symbol for this declaration.

  mixin(copyMethod);
}

class InterfaceDeclaration : AggregateDeclaration
{
  BaseClassType[] bases;
  this(Token* name, /+TemplateParameters tparams, +/BaseClassType[] bases, CompoundDeclaration decls)
  {
    super(name, /+tparams, +/decls);
    mixin(set_kind);
//     addChild(tparams);
    addOptChildren(bases);
    addOptChild(decls);

    this.bases = bases;
  }

  alias dil.semantic.Symbols.Interface Interface;

  Interface symbol; /// The interface symbol for this declaration.

  mixin(copyMethod);
}

class StructDeclaration : AggregateDeclaration
{
  uint alignSize;
  this(Token* name, /+TemplateParameters tparams, +/CompoundDeclaration decls)
  {
    super(name, /+tparams, +/decls);
    mixin(set_kind);
//     addChild(tparams);
    addOptChild(decls);
  }

  void setAlignSize(uint alignSize)
  {
    this.alignSize = alignSize;
  }

  Struct symbol; /// The struct symbol for this declaration.

  mixin(copyMethod);
}

class UnionDeclaration : AggregateDeclaration
{
  this(Token* name, /+TemplateParameters tparams, +/CompoundDeclaration decls)
  {
    super(name, /+tparams, +/decls);
    mixin(set_kind);
//     addChild(tparams);
    addOptChild(decls);
  }

  Union symbol; /// The union symbol for this declaration.

  mixin(copyMethod);
}

class ConstructorDeclaration : Declaration
{
  Parameters params;
  FuncBodyStatement funcBody;
  this(Parameters params, FuncBodyStatement funcBody)
  {
    super.hasBody = true;
    mixin(set_kind);
    addChild(params);
    addChild(funcBody);

    this.params = params;
    this.funcBody = funcBody;
  }
  mixin(copyMethod);
}

class StaticConstructorDeclaration : Declaration
{
  FuncBodyStatement funcBody;
  this(FuncBodyStatement funcBody)
  {
    super.hasBody = true;
    mixin(set_kind);
    addChild(funcBody);

    this.funcBody = funcBody;
  }
  mixin(copyMethod);
}

class DestructorDeclaration : Declaration
{
  FuncBodyStatement funcBody;
  this(FuncBodyStatement funcBody)
  {
    super.hasBody = true;
    mixin(set_kind);
    addChild(funcBody);

    this.funcBody = funcBody;
  }
  mixin(copyMethod);
}

class StaticDestructorDeclaration : Declaration
{
  FuncBodyStatement funcBody;
  this(FuncBodyStatement funcBody)
  {
    super.hasBody = true;
    mixin(set_kind);
    addChild(funcBody);

    this.funcBody = funcBody;
  }
  mixin(copyMethod);
}

class FunctionDeclaration : Declaration
{
  TypeNode returnType;
  Token* name;
//   TemplateParameters tparams;
  Parameters params;
  FuncBodyStatement funcBody;
  LinkageType linkageType;
  bool cantInterpret = false;
  this(TypeNode returnType, Token* name,/+ TemplateParameters tparams,+/
       Parameters params, FuncBodyStatement funcBody)
  {
    super.hasBody = funcBody.funcBody !is null;
    mixin(set_kind);
    addOptChild(returnType);
//     addChild(tparams);
    addChild(params);
    addChild(funcBody);

    this.returnType = returnType;
    this.name = name;
//     this.tparams = tparams;
    this.params = params;
    this.funcBody = funcBody;
  }

  /// Returns the Identifier object of this declaration. May be null.
  Identifier* nameId()
  {
    return name ? name.ident : null;
  }

  // Sets the LinkageType of this function.
  void setLinkageType(LinkageType linkageType)
  {
    this.linkageType = linkageType;
  }

  bool isTemplatized()
  { // E.g.: void func(T)(T t)
    //                  ^ params.begin.prevNWS
    return params.begin.prevNWS.kind == TOK.RParen;
  }

  mixin(copyMethod);
}

/// $(BNF VariablesDeclaration :=
///  Type? Identifier ("=" Init)? ("," Identifier ("=" Init)?)* ";")
class VariablesDeclaration : Declaration
{
  TypeNode typeNode;
  Token*[] names;
  Expression[] inits;
  this(TypeNode typeNode, Token*[] names, Expression[] inits)
  {
    // No empty arrays allowed. Both arrays must be of same size.
    assert(names.length != 0 && names.length == inits.length);
    // If no type (in case of AutoDeclaration), first value mustn't be null.
    assert(typeNode ? 1 : inits[0] !is null);
    mixin(set_kind);
    addOptChild(typeNode);
    foreach (init; inits)
      addOptChild(init);

    this.typeNode = typeNode;
    this.names = names;
    this.inits = inits;
  }

  /// Returns the Identifier object of this declaration. May be null.
  /// Params:
  ///   i = the index to the 'names' array member.
  Identifier* nameId(uint i)
  {
    return names[i] ? names[i].ident : null;
  }

  LinkageType linkageType;

  void setLinkageType(LinkageType linkageType)
  {
    this.linkageType = linkageType;
  }

  /// Returns the first non-null init expression, or null if there is none.
  Expression firstInit()
  {
    foreach (init; inits)
      if (init !is null)
        return init;
    return null;
  }

  Variable[] variables;

  mixin(copyMethod);
}

class InvariantDeclaration : Declaration
{
  FuncBodyStatement funcBody;
  this(FuncBodyStatement funcBody)
  {
    super.hasBody = true;
    mixin(set_kind);
    addChild(funcBody);

    this.funcBody = funcBody;
  }
  mixin(copyMethod);
}

class UnittestDeclaration : Declaration
{
  FuncBodyStatement funcBody;
  this(FuncBodyStatement funcBody)
  {
    super.hasBody = true;
    mixin(set_kind);
    addChild(funcBody);

    this.funcBody = funcBody;
  }
  mixin(copyMethod);
}

abstract class ConditionalCompilationDeclaration : Declaration
{
  Token* spec;
  Token* cond;
  Declaration decls, elseDecls;

  this(Token* spec, Token* cond, Declaration decls, Declaration elseDecls)
  {
    super.hasBody = decls !is null;
    addOptChild(decls);
    addOptChild(elseDecls);

    this.spec = spec;
    this.cond = cond;
    this.decls = decls;
    this.elseDecls = elseDecls;
  }

  bool isSpecification()
  {
    return decls is null;
  }

  bool isCondition()
  {
    return decls !is null;
  }

  /// The branch to be compiled in.
  Declaration compiledDecls;
}

class DebugDeclaration : ConditionalCompilationDeclaration
{
  this(Token* spec, Token* cond, Declaration decls, Declaration elseDecls)
  {
    super(spec, cond, decls, elseDecls);
    mixin(set_kind);
  }
  mixin(copyMethod);
}

class VersionDeclaration : ConditionalCompilationDeclaration
{
  this(Token* spec, Token* cond, Declaration decls, Declaration elseDecls)
  {
    super(spec, cond, decls, elseDecls);
    mixin(set_kind);
  }
  mixin(copyMethod);
}

class StaticIfDeclaration : Declaration
{
  Expression condition;
  Declaration ifDecls, elseDecls;
  this(Expression condition, Declaration ifDecls, Declaration elseDecls)
  {
    super.hasBody = true;
    mixin(set_kind);
    addChild(condition);
    addChild(ifDecls);
    addOptChild(elseDecls);

    this.condition = condition;
    this.ifDecls = ifDecls;
    this.elseDecls = elseDecls;
  }
  mixin(copyMethod);
}

class StaticAssertDeclaration : Declaration
{
  Expression condition, message;
  this(Expression condition, Expression message)
  {
    super.hasBody = true;
    mixin(set_kind);
    addChild(condition);
    addOptChild(message);

    this.condition = condition;
    this.message = message;
  }
  mixin(copyMethod);
}

class NewDeclaration : Declaration
{
  Parameters params;
  FuncBodyStatement funcBody;
  this(Parameters params, FuncBodyStatement funcBody)
  {
    super.hasBody = true;
    mixin(set_kind);
    addChild(params);
    addChild(funcBody);

    this.params = params;
    this.funcBody = funcBody;
  }
  mixin(copyMethod);
}

class DeleteDeclaration : Declaration
{
  Parameters params;
  FuncBodyStatement funcBody;
  this(Parameters params, FuncBodyStatement funcBody)
  {
    super.hasBody = true;
    mixin(set_kind);
    addChild(params);
    addChild(funcBody);

    this.params = params;
    this.funcBody = funcBody;
  }
  mixin(copyMethod);
}

abstract class AttributeDeclaration : Declaration
{
  Declaration decls;
  this(Declaration decls)
  {
    super.hasBody = true;
    addChild(decls);
    this.decls = decls;
  }

  void setDecls(Declaration decls)
  {
    this.decls = decls;
    if (children.length)
      children[0] = decls;
    else
      addChild(decls);
  }
}

class ProtectionDeclaration : AttributeDeclaration
{
  Protection prot;
  this(Protection prot, Declaration decls)
  {
    super(decls);
    mixin(set_kind);
    this.prot = prot;
  }
  mixin(copyMethod);
}

class StorageClassDeclaration : AttributeDeclaration
{
  StorageClass stcs;
  this(StorageClass stcs, Declaration decls)
  {
    super(decls);
    mixin(set_kind);

    this.stcs = stcs;
  }
  mixin(copyMethod);
}

class LinkageDeclaration : AttributeDeclaration
{
  LinkageType linkageType;
  this(LinkageType linkageType, Declaration decls)
  {
    super(decls);
    mixin(set_kind);

    this.linkageType = linkageType;
  }
  mixin(copyMethod);
}

class AlignDeclaration : AttributeDeclaration
{
  int size;
  Token* sizetok;
  this(Token* sizetok, Declaration decls)
  {
    super(decls);
    mixin(set_kind);
    this.size = sizetok ? sizetok.int_ : -1;
    this.sizetok = sizetok;
  }
  mixin(copyMethod);
}

class PragmaDeclaration : AttributeDeclaration
{
  Token* idtok;
  Expression[] args;
  this(Token* ident, Expression[] args, Declaration decls)
  {
    addOptChildren(args); // Add args before calling super().
    super(decls);
    mixin(set_kind);

    this.idtok = ident;
    this.args = args;
  }

  /// TODO: remove this and rename 'idtok' to 'ident'.
  Identifier* ident()
  {
    return idtok.ident;
  }

  mixin(copyMethod);
}

class MixinDeclaration : Declaration
{
  /// IdExpression := IdentifierExpression | TemplateInstanceExpression
  /// MixinTemplate := IdExpression ("." IdExpression)*
  Expression templateExpr;
  Token* mixinIdent; /// Optional mixin identifier.
  Expression argument; /// "mixin" "(" AssignExpression ")"
  Declaration decls; /// Initialized in the semantic phase.

  this(Expression templateExpr, Token* mixinIdent)
  {
    mixin(set_kind);
    addChild(templateExpr);

    this.templateExpr = templateExpr;
    this.mixinIdent = mixinIdent;
  }

  this(Expression argument)
  {
    mixin(set_kind);
    addChild(argument);

    this.argument = argument;
  }

  bool isMixinExpression()
  {
    return argument !is null;
  }

  mixin(copyMethod);
}
