#include "Node.h"

extern oop s_self;
extern oop s__self_2d_3e;

//  static int Tag;

int BlockNode::Tag= 0;

//  int mTag;
//  oop mEncoder;
//  oop mArguments;
//  oop mSequence;

BlockNode::BlockNode(oop arguments, oop sequence)
{
  mEncoder= 0;
  mArguments= arguments;
  mSequence= sequence;
  mTag= 0;
}

oop BlockNode::withArgumentsSequence(oop arguments, oop sequence)
{
  return new BlockNode(arguments, sequence);
}

oop BlockNode::withSequence(oop sequence)
{
  return withArgumentsSequence(ListNode::empty(), sequence);
}

oop BlockNode::arguments(void)
{
  return mArguments;
}

oop BlockNode::sequence(void)
{
  return mSequence;
}

int BlockNode::isOpenBlock(void)
{
  return (0 == arity()) && !mSequence->hasReturn();
}

int BlockNode::arity(void)
{
  return mArguments->size();
}

oop BlockNode::encode(oop encoder)
{
  mTag= ++Tag;
  encoder->addBlock(this);
  mLocation= encoder->pushStack();
  mEncoder= encoder->fork();
  //mEncoder->declare(s_self, new LocalVariable(s_self, s__self_2d_3e, mEncoder->level()));
  //mEncoder->getClass()->encodeForBlock(encoder);
  oop arguments= new OrderedCollection();
  iterate(mArguments, current)
    arguments->add(mEncoder->declareLocal(current.element()));
  mArguments= arguments->asArray();
  mSequence
    ->ensureBlockReturn()
    ->encode(mEncoder);
  return this;
}

oop BlockNode::genDefinition(oop unit)
{
  oop parent= mEncoder->parent();
  mEncoder->genDefinitions();
  if (!parent->exportNLR() && !parent->exports() && !parent->exportSelf() && !parent->exportOuter())
    unit->outputStream()->format("static oop c_%d= 0;\n", mTag);
  return this;
}

oop BlockNode::genImplementation(oop unit)
{
  mEncoder->genImplementations();
  oop stream= unit->outputStream();
  stream->format("static oop b_%d(oop _self", mTag);
  iterate(mArguments, iter)
    stream->format(", oop %s", iter.element()->mangledName()->cString());
  stream->format(")\n"
		 "{\n");
  if (mEncoder->exports())
    {
      stream->format("  oop *_state= (oop *)_newPointers(sizeof(oop) * %d);\n", mEncoder->exports());
      iterate(mArguments, arg)
	if ((*arg)->isExported())
	  stream->format("  _state[%d]= %s;\n", (*arg)->offset(), (*arg)->mangledName()->cString());
    }
  mSequence->genBody(unit, mEncoder);
  stream->format("}\n\n");
  return this;
}

oop BlockNode::genInitialisation(oop unit)
{
  mEncoder->genInitialisations();
  oop parent= mEncoder->parent();
  if (!parent->exportNLR() && !parent->exports() && !parent->exportSelf() && !parent->exportOuter())
    unit->outputStream()->format("  c_%d= _bind(v_StaticBlock, s__5fentry__5farity_)(v_StaticBlock, b_%d, %d);\n", mTag, mTag, mArguments->size());
  return this;
}

oop BlockNode::genStatic(oop unit)
{
  unit->outputStream()->format("  _%d= c_%d;\n", mLocation, mTag);
  return this;
}


oop BlockNode::genFull(oop unit)
{
  oop parent= mEncoder->parent();
  unit->outputStream()
    ->format("  _%d= _bind(v_BlockClosure, s__5fentry__5farity_receiver_state_outer_)("
	     "v_BlockClosure, "
	     "b_%d, "	// entry
	     "%d, "	// arity
	     "%s, "	// receiver
	     "%s, "	// state
	     "%s"	// outer
	     ");\n",
	     mLocation,
	     mTag,
	     mArguments->size(),
	     parent->parent() ? "((struct t_BlockClosure *)_self)->receiver" : "self",
	     parent->exports() ? "_state" : "0",
	     parent->parent() ? "_self" : "0"
	     );
  return this;
}

oop BlockNode::genNLR(oop unit)
{
  oop parent= mEncoder->parent();
  unit->outputStream()
    ->format("  _%d= _bind(v_BlockClosureNLR, s__5fentry__5farity_receiver_state_envp_)("
	     "v_BlockClosureNLR, "
	     "b_%d, "	// entry
	     "%d, "	// arity
	     "%s, "	// receiver
	     "%s, "	// state
	     "&_env"	// envp
	     ");\n",
	     mLocation,
	     mTag,
	     mArguments->size(),
	     parent->parent() ? "((struct t_BlockClosure *)_self)->receiver" : "self",
	     parent->exports() ? "_state" : "0"
	     );
  return this;
}

oop BlockNode::gen(oop unit)
{
  oop parent= mEncoder->parent();
  if      (parent->exportNLR())							return genNLR(unit);
  else if (parent->exports() || parent->exportSelf() || parent->exportOuter())	return genFull(unit);
  else										return genStatic(unit);
}
