/*
 * Copyright (c) 2013, 2014 Oracle and/or its affiliates. All rights reserved. This
 * code is released under a tri EPL/GPL/LGPL license. You can use it,
 * redistribute it and/or modify it under the terms of the:
 *
 * Eclipse Public License version 1.0
 * GNU General Public License version 2
 * GNU Lesser General Public License version 2.1
 */
package org.jruby.truffle.runtime.core;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import com.oracle.truffle.api.*;
import com.oracle.truffle.api.source.*;
import com.oracle.truffle.api.frame.FrameDescriptor;
import org.jruby.common.IRubyWarnings;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.RubyRootNode;
import org.jruby.truffle.nodes.methods.SymbolProcNode;
import org.jruby.truffle.runtime.*;
import org.jruby.truffle.runtime.methods.*;
import org.jruby.util.ByteList;

/**
 * Represents the Ruby {@code Symbol} class.
 */
public class RubySymbol extends RubyObject {

    private final String symbol;
    private final ByteList symbolBytes;

    private RubySymbol(RubyClass symbolClass, String symbol, ByteList byteList) {
        super(symbolClass);
        this.symbol = symbol;
        this.symbolBytes = byteList;
    }

    public static RubySymbol newSymbol(RubyContext runtime, String name) {
        return runtime.getSymbolTable().getSymbol(name);
    }

    public RubyProc toProc(SourceSection sourceSection, final RubyNode currentNode) {
        // TODO(CS): cache this?

        RubyNode.notDesignedForCompilation();

        final RubyContext context = getContext();

        final SharedMethodInfo sharedMethodInfo = SharedMethodInfo.generatedBlock(sourceSection, symbol);

        final RubyRootNode rootNode = new RubyRootNode(context, sourceSection, new FrameDescriptor(), sharedMethodInfo,
                new SymbolProcNode(context, sourceSection, symbol));

        final CallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode);

        return new RubyProc(context.getCoreLibrary().getProcClass(), RubyProc.Type.PROC, sharedMethodInfo, callTarget,
                callTarget, null, getContext().getCoreLibrary().getNilObject(), null);
    }

    public org.jruby.RubySymbol getJRubySymbol() {
        RubyNode.notDesignedForCompilation();

        return getContext().getRuntime().newSymbol(symbolBytes);
    }

    @Override
    public int hashCode() {
        return symbol.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        if (other == this) {
            return true;
        } else if (other instanceof RubySymbol) {
            return symbol == ((RubySymbol) other).symbol;
        } else if (other instanceof RubyString) {
            return other.equals(symbol);
        } else {
            return super.equals(other);
        }
    }

    @Override
    public String toString() {
        return symbol;
    }

    public RubyString toRubyString() {
         return getContext().makeString(toString());
    }

    public static final class SymbolTable {

        private final ConcurrentHashMap<ByteList, RubySymbol> symbolsTable = new ConcurrentHashMap<>();
        private final RubyContext context;

        public SymbolTable(RubyContext context) {
            this.context = context;
        }

        public RubySymbol getSymbol(String name) {
            ByteList byteList = org.jruby.RubySymbol.symbolBytesFromString(context.getRuntime(), name);

            RubySymbol symbol = symbolsTable.get(byteList);

            if (symbol == null) {
                symbol = createSymbol(name);
            }
            return symbol;
        }

        public RubySymbol getSymbol(ByteList byteList) {
            // TODO(CS): is this broken? ByteList is mutable...

            RubySymbol symbol = symbolsTable.get(byteList);

            if (symbol == null) {
                symbol = createSymbol(byteList);
            }
            return symbol;

        }

        private RubySymbol createSymbol(ByteList byteList) {
            RubySymbol symbol = new RubySymbol(context.getCoreLibrary().getSymbolClass(), byteList.toString(), byteList);
            symbolsTable.put(byteList, symbol);
            return symbol;
        }

        private RubySymbol createSymbol(String name) {
            ByteList byteList = org.jruby.RubySymbol.symbolBytesFromString(context.getRuntime(), name);
            RubySymbol symbol = new RubySymbol(context.getCoreLibrary().getSymbolClass(), name, byteList);

            RubySymbol existingSymbol = symbolsTable.putIfAbsent(byteList, symbol);
            return existingSymbol == null ? symbol : existingSymbol;
        }

        public ConcurrentHashMap<ByteList, RubySymbol> getSymbolsTable(){
            return symbolsTable;
        }
    }

    @Override
    public boolean hasNoSingleton() {
        return true;
    }

}
