package org.jruby.ast;

import org.jcodings.Encoding;
import org.jruby.Ruby;
import org.jruby.RubyString;
import org.jruby.lexer.yacc.ISourcePosition;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;

/**
 * Base class for all D (e.g. Dynamic) node types like DStrNode, DSymbolNode, etc...
 */
public abstract class DNode extends ListNode {
    protected Encoding encoding; // If encoding is set then we should obey 1.9 semantics.

    public DNode(ISourcePosition position) {
        this(position, null);
    }

    public DNode(ISourcePosition position, Encoding encoding) {
        super(position);

        this.encoding = encoding;
    }

    @Override
    public IRubyObject interpret(Ruby runtime, ThreadContext context, IRubyObject self, Block aBlock) {
        return buildDynamicString(runtime, context, self, aBlock);
    }

    // Enebo: Without massive rethink of AST system I think we are stuck with an if check
    public boolean is19() {
        return encoding != null;
    }
    
    public boolean isSameEncoding(StrNode strNode) {
        return strNode.getValue().getEncoding() == encoding;
    }

    protected RubyString allocateString(Ruby runtime) {
        ByteList empty = new ByteList();

        if (is19()) empty.setEncoding(encoding);

        return runtime.newString(empty);
    }

    public void appendToString(Ruby runtime, ThreadContext context, IRubyObject self, Block aBlock, RubyString string, Node node) {
        if (node instanceof StrNode && (!is19() || isSameEncoding((StrNode) node))) {
            string.getByteList().append(((StrNode) node).getValue());
        } else if (is19()) {
            string.append19(node.interpret(runtime, context, self, aBlock));
        } else {
            string.append(node.interpret(runtime, context, self, aBlock));
        }
    }

    public RubyString buildDynamicString(Ruby runtime, ThreadContext context, IRubyObject self, Block aBlock) {
        RubyString string = allocateString(runtime);

        int size = size();
        for (int i = 0; i < size; i++) {
            appendToString(runtime, context, self, aBlock, string, get(i));
        }

        return string;
    }

    @Override
    public String definition(Ruby runtime, ThreadContext context, IRubyObject self, Block aBlock) {
        String definition = super.definition(runtime, context, self, aBlock);
        return is19() && definition == null ? "expression" : definition;
    }
}