/*
 * Copyright (c) 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.nodes.rubinius;

import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.nodes.core.CoreClass;
import org.jruby.truffle.nodes.core.CoreMethod;
import org.jruby.truffle.nodes.core.CoreMethodNode;
import org.jruby.truffle.nodes.dispatch.DispatchHeadNode;
import org.jruby.truffle.runtime.RubyArguments;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.core.RubyClass;
import org.jruby.truffle.runtime.core.RubyObject;
import org.jruby.truffle.runtime.core.RubyString;
import org.jruby.truffle.runtime.rubinius.RubiniusByteArray;
import org.jruby.truffle.runtime.util.TypeConversionUtils;

@CoreClass(name = "ByteArray")
public abstract class ByteArrayNodes {
    @CoreMethod(names = "allocate", onSingleton = true)
    public abstract static class AllocateNode extends CoreMethodNode {
        public AllocateNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
        }

        public AllocateNode(AllocateNode prev) {
            super(prev);
        }

        @Specialization
        public RubyObject allocate(RubyObject baClass, RubyObject size) {
            throw new RaiseException(getContext().getCoreLibrary().typeError("ByteArray cannot be created via allocate()", this));
        }
    }

    @CoreMethod(names = {"new", "allocate_sized"}, onSingleton = true, required = 1)
    public abstract static class AllocateSizedNode extends CoreMethodNode {
        @Child
        protected DispatchHeadNode bytesToIntNode;

        public AllocateSizedNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            bytesToIntNode = new DispatchHeadNode(context);
        }

        public AllocateSizedNode(AllocateSizedNode prev) {
            super(prev);
            bytesToIntNode = prev.bytesToIntNode;
        }

        @Specialization
        public RubyObject allocate_sized(VirtualFrame frame, RubyObject bytes) {
            RubyClass self = (RubyClass) RubyArguments.getSelf(frame.getArguments());
            return RubiniusByteArray.allocate_sized(this, self, TypeConversionUtils.convertToLong(this, bytesToIntNode, frame, bytes));
        }
    }

    @CoreMethod(names = "fetch_bytes", required = 2)
    public abstract static class FetchBytesNode extends CoreMethodNode {
        @Child
        protected DispatchHeadNode countToIntNode;
        @Child
        protected DispatchHeadNode startToIntNode;

        public FetchBytesNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            startToIntNode = new DispatchHeadNode(context);
            countToIntNode = new DispatchHeadNode(context);
        }

        public FetchBytesNode(FetchBytesNode prev) {
            super(prev);
            startToIntNode = prev.startToIntNode;
            countToIntNode = prev.countToIntNode;
        }

        @Specialization
        public RubyObject fetch_bytes(VirtualFrame frame, RubiniusByteArray self, RubyObject start, RubyObject count) {
            return self.fetch_bytes(this, TypeConversionUtils.convertToLong(this, startToIntNode, frame, start), TypeConversionUtils.convertToLong(this, countToIntNode, frame, count));
        }
    }

    @CoreMethod(names = "move_bytes")
    public abstract static class MoveBytesNode extends CoreMethodNode {
        @Child
        protected DispatchHeadNode destToIntNode;
        @Child
        protected DispatchHeadNode countToIntNode;
        @Child
        protected DispatchHeadNode startToIntNode;

        public MoveBytesNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            startToIntNode = new DispatchHeadNode(context);
            countToIntNode = new DispatchHeadNode(context);
            destToIntNode = new DispatchHeadNode(context);
        }

        public MoveBytesNode(MoveBytesNode prev) {
            super(prev);
            startToIntNode = prev.startToIntNode;
            countToIntNode = prev.countToIntNode;
            destToIntNode = prev.destToIntNode;
        }

        @Specialization
        public RubyObject move_bytes(VirtualFrame frame, RubiniusByteArray self, RubyObject start, RubyObject count, RubyObject dest) {
            self.move_bytes(this, TypeConversionUtils.convertToLong(this, startToIntNode, frame, start), TypeConversionUtils.convertToLong(this, countToIntNode, frame, count),
                    TypeConversionUtils.convertToLong(this, destToIntNode, frame, dest));
            return count;
        }
    }

    @CoreMethod(names = {"get_byte", "[]"})
    public abstract static class GetByteNode extends CoreMethodNode {
        @Child
        protected DispatchHeadNode indexToIntNode;

        public GetByteNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            indexToIntNode = new DispatchHeadNode(context);
        }

        public GetByteNode(GetByteNode prev) {
            super(prev);
            indexToIntNode = prev.indexToIntNode;
        }

        @Specialization
        public int get_byte(VirtualFrame frame, RubiniusByteArray self, RubyObject index) {
            return self.get_byte(this, TypeConversionUtils.convertToLong(this, indexToIntNode, frame, index));
        }
    }

    @CoreMethod(names = {"set_byte", "[]="})
    public abstract static class SetByteNode extends CoreMethodNode {
        @Child
        protected DispatchHeadNode valueToIntNode;
        @Child
        protected DispatchHeadNode indexToIntNode;

        public SetByteNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            indexToIntNode = new DispatchHeadNode(context);
            valueToIntNode = new DispatchHeadNode(context);
        }

        public SetByteNode(SetByteNode prev) {
            super(prev);
            indexToIntNode = prev.indexToIntNode;
            valueToIntNode = prev.valueToIntNode;
        }

        @Specialization
        public int set_byte(VirtualFrame frame, RubiniusByteArray self, RubyObject index, RubyObject value) {
            return self.set_byte(this, TypeConversionUtils.convertToLong(this, indexToIntNode, frame, index), TypeConversionUtils.convertToLong(this, valueToIntNode, frame, value));
        }
    }

    @CoreMethod(names = "compare_bytes")
    public abstract static class CompareBytesNode extends CoreMethodNode {
        @Child
        protected DispatchHeadNode bToIntNode;
        @Child
        protected DispatchHeadNode aToIntNode;

        public CompareBytesNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            aToIntNode = new DispatchHeadNode(context);
            bToIntNode = new DispatchHeadNode(context);
        }

        public CompareBytesNode(CompareBytesNode prev) {
            super(prev);
            aToIntNode = prev.aToIntNode;
            bToIntNode = prev.bToIntNode;
        }

        @Specialization
        public int compare_bytes(VirtualFrame frame, RubiniusByteArray self, RubiniusByteArray other, RubyObject a, RubyObject b) {
            return self.compare_bytes(this, other, TypeConversionUtils.convertToLong(this, aToIntNode, frame, a), TypeConversionUtils.convertToLong(this, bToIntNode, frame, b));
        }
    }

    @CoreMethod(names = "size")
    public abstract static class SizeNode extends CoreMethodNode {
        public SizeNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
        }

        public SizeNode(SizeNode prev) {
            super(prev);
        }

        @Specialization
        public int size(RubiniusByteArray self) {
            return self.size();
        }
    }

    @CoreMethod(names = "locate")
    public abstract static class LocateNode extends CoreMethodNode {
        @Child
        protected DispatchHeadNode max_oToIntNode;
        @Child
        protected DispatchHeadNode startToIntNode;
        @Child
        protected DispatchHeadNode patternToStringNode;

        public LocateNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            patternToStringNode = new DispatchHeadNode(context);
            startToIntNode = new DispatchHeadNode(context);
            max_oToIntNode = new DispatchHeadNode(context);
        }

        public LocateNode(LocateNode prev) {
            super(prev);
            patternToStringNode = prev.patternToStringNode;
            startToIntNode = prev.startToIntNode;
            max_oToIntNode = prev.max_oToIntNode;
        }

        @Specialization
        public Object locate(VirtualFrame frame, RubiniusByteArray self, RubyObject pattern, RubyObject start, RubyObject max_o) {
            return self.locate((RubyString) patternToStringNode.call(frame, pattern, "to_s", null), TypeConversionUtils.convertToLong(this, startToIntNode, frame, start),
                    TypeConversionUtils.convertToLong(this, max_oToIntNode, frame, max_o));
        }
    }

    @CoreMethod(names = "prepend")
    public abstract static class PrependNode extends CoreMethodNode {
        @Child
        protected DispatchHeadNode strToStringNode;

        public PrependNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            strToStringNode = new DispatchHeadNode(context);
        }

        public PrependNode(PrependNode prev) {
            super(prev);
            strToStringNode = prev.strToStringNode;
        }

        @Specialization
        public RubyObject prepend(VirtualFrame frame, RubiniusByteArray self, RubyObject str) {
            return self.prepend((RubyString) strToStringNode.call(frame, str, "to_s", null));
        }
    }

    @CoreMethod(names = "utf8_char")
    public abstract static class Utf8CharNode extends CoreMethodNode {
        public Utf8CharNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
        }

        public Utf8CharNode(Utf8CharNode prev) {
            super(prev);
        }

        @Specialization
        public RubyObject utf8_char(RubiniusByteArray self, RubyObject offset) {
            return self.utf8_char(this, offset);
        }
    }

    @CoreMethod(names = "reverse")
    public abstract static class ReverseNode extends CoreMethodNode {
        @Child
        protected DispatchHeadNode o_totalToIntNode;
        @Child
        protected DispatchHeadNode o_startToIntNode;

        public ReverseNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            o_startToIntNode = new DispatchHeadNode(context);
            o_totalToIntNode = new DispatchHeadNode(context);
        }

        public ReverseNode(ReverseNode prev) {
            super(prev);
            o_startToIntNode = prev.o_startToIntNode;
            o_totalToIntNode = prev.o_totalToIntNode;
        }

        @Specialization
        public RubyObject reverse(VirtualFrame frame, RubiniusByteArray self, RubyObject o_start, RubyObject o_total) {
            return self.reverse(TypeConversionUtils.convertToLong(this, o_startToIntNode, frame, o_start), TypeConversionUtils.convertToLong(this, o_totalToIntNode, frame, o_total));
        }
    }
}
