/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004-2005 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 David Corbin <dcorbin@users.sourceforge.net>
 * Copyright (C) 2006 Nick Sieger <nicksieger@gmail.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import jregex.Matcher;
import jregex.Pattern;
import jregex.REFlags;
import org.jruby.parser.ReOptions;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallbackFactory;
import org.jruby.runtime.CallType;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.ByteList;
import org.jruby.util.KCode;
import org.jruby.util.Sprintf;

/**
 *
 * @author  amoore
 */
public class RubyRegexp extends RubyObject implements ReOptions {
    private static final RegexpTranslator REGEXP_TRANSLATOR = new RegexpTranslator();

    // \013 is a vertical tab. Java does not support the \v notation used by
    // Ruby.
    private static final Pattern SPECIAL_CHARS = new Pattern("([\\\t\\\n\\\f\\\r\\ \\#\\\013\\+\\-\\[\\]\\.\\?\\*\\(\\)\\{\\}\\|\\\\\\^\\$])");    

	/** Class which represents the multibyte character set code.
	 * (should be an enum in Java 5.0).
	 * 
	 * Warning: THIS IS NOT REALLY SUPPORTED BY JRUBY. 
	 */

    private ByteList source;
    private Pattern pattern;
    private KCode code;
    private int flags;

    KCode getCode() {
        return code;
    }

	// lastTarget and matcher currently only used by searchAgain
	private String lastTarget = null;
	private Matcher matcher = null;

    public RubyRegexp(Ruby runtime, RubyClass klass) {
        super(runtime, klass);
    }

    private RubyRegexp(Ruby runtime) {
        super(runtime, runtime.getClass("Regexp"));
    }
    
    private static ObjectAllocator REGEXP_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            RubyRegexp instance = new RubyRegexp(runtime, klass);
            
            return instance;
        }
    };

    public static RubyClass createRegexpClass(Ruby runtime) {
        RubyClass regexpClass = runtime.defineClass("Regexp", runtime.getObject(), REGEXP_ALLOCATOR);
        regexpClass.index = ClassIndex.REGEXP;
        
        CallbackFactory callbackFactory = runtime.callbackFactory(RubyRegexp.class);
        
        regexpClass.defineConstant("IGNORECASE", runtime.newFixnum(RE_OPTION_IGNORECASE));
        regexpClass.defineConstant("EXTENDED", runtime.newFixnum(RE_OPTION_EXTENDED));
        regexpClass.defineConstant("MULTILINE", runtime.newFixnum(RE_OPTION_MULTILINE));

        regexpClass.defineFastMethod("initialize", callbackFactory.getFastOptMethod("initialize"));
        regexpClass.defineFastMethod("initialize_copy", callbackFactory.getFastMethod("initialize_copy",RubyKernel.IRUBY_OBJECT));        
        regexpClass.defineFastMethod("==", callbackFactory.getFastMethod("equal", RubyKernel.IRUBY_OBJECT));
        regexpClass.defineFastMethod("eql?", callbackFactory.getFastMethod("equal", RubyKernel.IRUBY_OBJECT));
        regexpClass.defineFastMethod("===", callbackFactory.getFastMethod("eqq", RubyKernel.IRUBY_OBJECT));
        regexpClass.defineFastMethod("=~", callbackFactory.getFastMethod("match", RubyKernel.IRUBY_OBJECT));
        regexpClass.defineFastMethod("~", callbackFactory.getFastMethod("match2"));
        regexpClass.defineFastMethod("match", callbackFactory.getFastMethod("match_m", RubyKernel.IRUBY_OBJECT));
        regexpClass.defineFastMethod("inspect", callbackFactory.getFastMethod("inspect"));
        regexpClass.defineFastMethod("source", callbackFactory.getFastMethod("source"));
        regexpClass.defineFastMethod("casefold?", callbackFactory.getFastMethod("casefold"));
        regexpClass.defineFastMethod("kcode", callbackFactory.getFastMethod("kcode"));
        regexpClass.defineFastMethod("to_s", callbackFactory.getFastMethod("to_s"));
        regexpClass.defineFastMethod("hash", callbackFactory.getFastMethod("hash"));

        regexpClass.getMetaClass().defineFastMethod("new", callbackFactory.getFastOptSingletonMethod("newInstance"));
        regexpClass.getMetaClass().defineFastMethod("compile", callbackFactory.getFastOptSingletonMethod("newInstance"));
        regexpClass.getMetaClass().defineFastMethod("quote", callbackFactory.getFastOptSingletonMethod("quote"));
        regexpClass.getMetaClass().defineFastMethod("escape", callbackFactory.getFastSingletonMethod("quote", RubyString.class));
        regexpClass.getMetaClass().defineFastMethod("last_match", callbackFactory.getFastOptSingletonMethod("last_match_s"));
        regexpClass.getMetaClass().defineFastMethod("union", callbackFactory.getFastOptSingletonMethod("union"));

        return regexpClass;
    }
    
    public int getNativeTypeIndex() {
        return ClassIndex.REGEXP;
    }


    public static final byte NIL_P_SWITCHVALUE = 1;
    public static final byte EQUALEQUAL_SWITCHVALUE = 2;
    public static final byte TO_S_SWITCHVALUE = 3;
    public static final byte HASH_SWITCHVALUE = 4;
    public static final byte MATCH_SWITCHVALUE = 5;
    public static final byte EQQ_SWITCHVALUE = 6;
    
    public IRubyObject callMethod(ThreadContext context, RubyModule rubyclass, int methodIndex, String name,
            IRubyObject[] args, CallType callType, Block block) {
        // If tracing is on, don't do STI dispatch
        if (context.getRuntime().hasEventHooks()) return super.callMethod(context, rubyclass, name, args, callType, block);
        
        switch (getRuntime().getSelectorTable().table[rubyclass.index][methodIndex]) {
        case NIL_P_SWITCHVALUE:
            if (args.length != 0) throw context.getRuntime().newArgumentError("wrong number of arguments(" + args.length + " for " + 0 + ")");
            return nil_p();
        case EQUALEQUAL_SWITCHVALUE:
            if (args.length != 1) throw context.getRuntime().newArgumentError("wrong number of arguments(" + args.length + " for " + 1 + ")");
            return equal(args[0]);
        case TO_S_SWITCHVALUE:
            if (args.length != 0) throw context.getRuntime().newArgumentError("wrong number of arguments(" + args.length + " for " + 0 + ")");
            return to_s();
        case HASH_SWITCHVALUE:
            if (args.length != 0) throw context.getRuntime().newArgumentError("wrong number of arguments(" + args.length + " for " + 0 + ")");
            return hash();
        case MATCH_SWITCHVALUE:
            if (args.length != 1) throw context.getRuntime().newArgumentError("wrong number of arguments(" + args.length + " for " + 1 + ")");
            return match(args[0]);
        case EQQ_SWITCHVALUE:
            if (args.length != 1) throw context.getRuntime().newArgumentError("wrong number of arguments(" + args.length + " for " + 1 + ")");
            return eqq(args[0]);
        case 0:
        default:
            return super.callMethod(context, rubyclass, name, args, callType, block);
        }
    }
    
    public void initialize(ByteList regex, int options) {
        try {
            source = regex;
            pattern = REGEXP_TRANSLATOR.translate(regex, options, code.flags());
            flags = REGEXP_TRANSLATOR.flagsFor(options, code.flags());
        } catch(jregex.PatternSyntaxException e) {
            //            System.err.println(regex);
            //            e.printStackTrace();
            throw getRuntime().newRegexpError(e.getMessage());
        }
    }

    public void initialize(String regex, int options) {
        try {
            source = ByteList.create(regex);
            pattern = REGEXP_TRANSLATOR.translate(regex, options, code.flags());
            flags = REGEXP_TRANSLATOR.flagsFor(options, code.flags());
        } catch(jregex.PatternSyntaxException e) {
            //            System.err.println(regex);
            //            e.printStackTrace();
            throw getRuntime().newRegexpError(e.getMessage());
        }
    }

    public static String escapeSpecialChars(String original) {
    	return SPECIAL_CHARS.replacer("\\\\$1").replace(original);
    }

    private void recompileIfNeeded() {
        checkInitialized();
    }

    private void checkInitialized() {
        if (pattern == null) {
            throw getRuntime().newTypeError("uninitialized Regexp");
        }
    }
    
    public static RubyRegexp regexpValue(IRubyObject obj) {
        if (obj instanceof RubyRegexp) {
            return (RubyRegexp) obj;
        } else if (obj instanceof RubyString) {
            return newRegexp(obj.getRuntime().newString(escapeSpecialChars(((RubyString) obj).toString())), 0, null);
        } else {
            throw obj.getRuntime().newArgumentError("can't convert arg to Regexp");
        }
    }

    // Methods of the Regexp class (rb_reg_*):

    public static RubyRegexp newRegexp(RubyString str, int options, String lang) {
        return newRegexp(str.getRuntime(), str.getByteList(), options, lang);
    }
    
    public static RubyRegexp newRegexp(Ruby runtime, String source, Pattern pattern, int flags, String lang) {
        RubyRegexp re = new RubyRegexp(runtime);
        re.code = KCode.create(runtime, lang);
        re.source = ByteList.create(source);
        re.pattern = pattern;
        re.flags = flags;
        return re;
    }
    
    public static RubyRegexp newRegexp(Ruby runtime, ByteList source, Pattern pattern, int flags, String lang) {
        RubyRegexp re = new RubyRegexp(runtime);
        re.code = KCode.create(runtime, lang);
        re.source = source;
        re.pattern = pattern;
        re.flags = flags;
        return re;
    }
    
    public static RubyRegexp newRegexp(Ruby runtime, String str, int options, String kcode) {
        RubyRegexp re = new RubyRegexp(runtime);
        re.code = KCode.create(runtime, kcode);
        re.initialize(str, options);
        return re;
    }
    
    public static RubyRegexp newRegexp(Ruby runtime, ByteList str, int options, String kcode) {
        RubyRegexp re = new RubyRegexp(runtime);
        re.code = KCode.create(runtime, kcode);
        re.initialize(str, options);
        return re;
    }
    
    public static RubyRegexp newInstance(IRubyObject recv, IRubyObject[] args) {
        RubyClass klass = (RubyClass)recv;
        
        RubyRegexp re = (RubyRegexp) klass.allocate();
        
        re.callInit(args, Block.NULL_BLOCK);
        
        return re;
    }

    public IRubyObject initialize(IRubyObject[] args) {
        ByteList pat =
            (args[0] instanceof RubyRegexp)
                ? ((RubyRegexp) args[0]).source().getByteList()
                : RubyString.stringValue(args[0]).getByteList();
        int opts = 0;
        if (args.length > 1) {
            if (args[1] instanceof RubyFixnum) {
                opts = (int) ((RubyFixnum) args[1]).getLongValue();
            } else if (args[1].isTrue()) {
                opts |= RE_OPTION_IGNORECASE;
            }
        }
        if (args.length > 2) {
        	code = KCode.create(getRuntime(), RubyString.stringValue (args[2]).toString());
        } else {
        	code = KCode.create(getRuntime(), null);
        }
        initialize(pat, opts);
        return getRuntime().getNil();
    }

    /** rb_reg_s_quote
     * 
     */
    public static RubyString quote(IRubyObject recv, IRubyObject[] args) {
        if (args.length == 0 || args.length > 2) {
            throw recv.getRuntime().newArgumentError(0, args.length);
        }

        KCode kcode = recv.getRuntime().getKCode();
        
        if (args.length > 1) {
            kcode = KCode.create(recv.getRuntime(), args[1].toString());
        }
        
        RubyString str = args[0].convertToString();

        if (kcode == KCode.NONE) {
            return quote(recv, str);
        }
        
        try {
            // decode with the specified encoding, escape as appropriate, and reencode
            // FIXME: This could probably be more efficent.
            ByteList bytes = str.getByteList();
            CharBuffer decoded = kcode.decoder().decode(ByteBuffer.wrap(bytes.unsafeBytes(), bytes.begin(), bytes.length()));
            String escaped = escapeSpecialChars(decoded.toString());
            ByteBuffer encoded = kcode.encoder().encode(CharBuffer.wrap(escaped));
            
            return (RubyString)RubyString.newString(recv.getRuntime(), encoded.array()).infectBy(str);
        } catch (CharacterCodingException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * Utility version of quote that doesn't use encoding
     */
    public static RubyString quote(IRubyObject recv, RubyString str) {        
        return (RubyString) recv.getRuntime().newString(escapeSpecialChars(str.toString())).infectBy(str);
    }

    /** 
     * 
     */
    public static IRubyObject last_match_s(IRubyObject recv, IRubyObject[] args) {
        if (args.length == 0) {
            return recv.getRuntime().getCurrentContext().getBackref();
        }
        
        // FIXME: 
        return ((RubyMatchData)recv.getRuntime().getCurrentContext().getBackref()).aref(args);
    }

    /** rb_reg_equal
     * 
     */
    public IRubyObject equal(IRubyObject other) {
        if (other == this) {
            return getRuntime().getTrue();
        }
        if (!(other instanceof RubyRegexp)) {
            return getRuntime().getFalse();
        }
        RubyRegexp re = (RubyRegexp) other;
        checkInitialized();
        if (!(re.pattern.toString().equals(pattern.toString()) && re.flags == this.flags)) {
            return getRuntime().getFalse();
        }
        
        if (code != re.code) {
        	return getRuntime().getFalse();
        }
        
        return getRuntime().getTrue();
    }

    /** rb_reg_match2
     * 
     */
    public IRubyObject match2() {
        IRubyObject target = getRuntime().getCurrentContext().getLastline();
        
        return target instanceof RubyString ? match(target) : getRuntime().getNil();
    }
    
    /** rb_reg_eqq
     * 
     */
    public IRubyObject eqq(IRubyObject target) {
        if(!(target instanceof RubyString)) {
            target = target.checkStringType();
            if(target.isNil()) {
                getRuntime().getCurrentContext().setBackref(getRuntime().getNil());
                return getRuntime().getFalse();
            }
        }
        RubyString ss = RubyString.stringValue(target);
    	String string = ss.toString();
        if (string.length() == 0 && "^$".equals(pattern.toString())) {
    		string = "\n";
        }
    	
        int result = search(string, ss, 0);
        
        return result < 0 ? getRuntime().getFalse() : getRuntime().getTrue();
    }

    /** rb_reg_match
     * 
     */
    public IRubyObject match(IRubyObject target) {
        if (target.isNil()) {
            return getRuntime().getFalse();
        }
        // FIXME:  I think all String expecting functions has this magic via RSTRING
    	if (target instanceof RubySymbol || target instanceof RubyHash || target instanceof RubyArray) {
    		return getRuntime().getFalse();
    	}
        // FIXME: make Unicode-aware
    	RubyString ss = RubyString.stringValue(target);
    	String string = ss.toString();
        if (string.length() == 0 && "^$".equals(pattern.toString())) {
    		string = "\n";
        }
    	
        int result = search(string, ss, 0);
        
        return result < 0 ? getRuntime().getNil() :
        	getRuntime().newFixnum(result);
    }

    /** rb_reg_match_m
     * 
     */
    public IRubyObject match_m(IRubyObject target) {
        if (target.isNil()) return target;

        IRubyObject result = match(target);
        
        return result.isNil() ? result : 
            getRuntime().getCurrentContext().getBackref().rbClone(Block.NULL_BLOCK);
    }

    /** rb_reg_source
     * 
     */
    public RubyString source() {
        checkInitialized();
        return getRuntime().newString(source);
    }

    public IRubyObject kcode() {
        if(code == KCode.NIL) {
            return code.kcode(getRuntime());
        } else {
            return getRuntime().newString(code.kcode(getRuntime()).toString().toLowerCase());
        }
    }

    /** rb_reg_casefold_p
     * 
     */
    public RubyBoolean casefold() {
        checkInitialized();
        return getRuntime().newBoolean((flags & REFlags.IGNORE_CASE) != 0);
    }

    /** rb_reg_nth_match
     *
     */
    public static IRubyObject nth_match(int n, IRubyObject match) {
        IRubyObject nil = match.getRuntime().getNil();
        if (match.isNil()) {
            return nil;
        }
        
        RubyMatchData rmd = (RubyMatchData) match;
        
        if (n > rmd.getSize()) {
            return nil;
        }
        
        if (n < 0) {
            n += rmd.getSize();
            if (n <= 0) {
                return nil;
            }
        }
        return rmd.group(n);
    }

    /** rb_reg_last_match
     *
     */
    public static IRubyObject last_match(IRubyObject match) {
        return match.isNil() ? match : ((RubyMatchData) match).group(0);
    }

    /** rb_reg_match_pre
     *
     */
    public static IRubyObject match_pre(IRubyObject match) {
        return match.isNil() ? match : ((RubyMatchData) match).pre_match();
    }

    /** rb_reg_match_post
     *
     */
    public static IRubyObject match_post(IRubyObject match) {
        return match.isNil() ? match : ((RubyMatchData) match).post_match();
    }

    /** rb_reg_match_last
     *
     */
    public static IRubyObject match_last(IRubyObject match) {
        if (match.isNil()) {
            return match;
        }
        RubyMatchData md = (RubyMatchData) match;
        for (long i = md.getSize() - 1; i > 0; i--) {
            if (!md.group(i).isNil()) {
                return md.group(i);
            }
        }
        return md.getRuntime().getNil();
    }

    /** rb_reg_search
     *
     */
    public int search(String target, RubyString rtarget, int pos) {
        if (pos > target.length()) {
            return -1;
        }
        recompileIfNeeded();

        // If nothing match then nil will be returned
        IRubyObject result = match(target, rtarget, pos);
        getRuntime().getCurrentContext().setBackref(result);

        // If nothing match then -1 will be returned
        return result instanceof RubyMatchData ? ((RubyMatchData) result).matchStartPosition() : -1;
    }
    
    public IRubyObject search2(String str, RubyString rtarget) {
        IRubyObject result = match(str, rtarget, 0);
        
        getRuntime().getCurrentContext().setBackref(result);
        
    	return result;
    }
	
    public int searchAgain(String target, RubyString rtarget, boolean utf) {
        if (matcher == null || !target.equals(lastTarget)) {
            matcher = pattern.matcher(target);
            lastTarget = target;
        }
        
        if (!matcher.find()) {
            return -1;
        }
        
        RubyMatchData match = (utf) ? 
            ((RubyMatchData)new RubyMatchData.JavaString(getRuntime(), target, matcher))
            :
            ((RubyMatchData)new RubyMatchData.RString(getRuntime(), rtarget, matcher));

        
        getRuntime().getCurrentContext().setBackref(match);
        
        return match.matchStartPosition();
    }
    
    public IRubyObject match(String target, RubyString rtarget, int startPos) {
        boolean utf8 = getCode() == KCode.UTF8;
        String t = target;
        if(utf8) {
            try {
                byte[] bs = ByteList.plain(target);
                String string = new String(bs, 0, startPos, "UTF8");
                startPos = string.length();
                t = new String(ByteList.plain(target),"UTF8");
            } catch(Exception e) {
            }
        }

    	Matcher aMatcher = pattern.matcher(t);
    	
        aMatcher.setPosition(startPos);

        if (aMatcher.find()) {
            return (utf8) ? 
                ((RubyMatchData)new RubyMatchData.JavaString(getRuntime(), target, aMatcher))
                :
                ((RubyMatchData)new RubyMatchData.RString(getRuntime(), rtarget, aMatcher));

        }
        return getRuntime().getNil();
    }

    /** rb_reg_regsub
    *
    */
    public RubyString regsub(IRubyObject str, RubyString src, RubyMatchData match) {
        ByteList val = null;

        ByteList strList = str.convertToString().getByteList();
        byte[]strBytes = strList.unsafeBytes();
        int p, s;
        p = s = strList.begin;
        int e = s + strList.realSize;

        while (s < e) {
            int ss = s;
            char c = (char)(strBytes[s++] & 0xFF);
            if (c != '\\' || s == e) continue;

            if (val == null) {
                val = new ByteList(ss - p);
                val.append(strBytes, p, ss - p);
            } else {
                val.append(strBytes, p, ss - p);
            }

            c = (char)(strBytes[s++] & 0xFF);
            p = s;

            int no;
            Matcher mat = match.matcher;

            ByteList srcList;
            byte[]srcBytes;

            switch (c) {
                case '0' :
                case '1' :
                case '2' :
                case '3' :
                case '4' :
                case '5' :
                case '6' :
                case '7' :
                case '8' :
                case '9' :
                    no = c - '0';
                    break;
                case '&' :
                    no = 0;
                    break;
                case '`' :
                    srcList = src.getByteList();
                    srcBytes = srcList.unsafeBytes();
                    val.append(srcBytes, srcList.begin, mat.start(0));
                    continue;
                case '\'':
                    srcList = src.getByteList();
                    srcBytes = srcList.unsafeBytes();
                    val.append(srcBytes, srcList.begin + mat.end(0), srcList.length() - mat.end(0));
                    continue;

                case '+' :
                    no = mat.groupCount() - 1;
                    while (!mat.isCaptured(no) && no > 0) no--;
                    if (no == 0) continue;
                    break;

                case '\\':
                    val.append(strBytes, s - 1, 1);
                    continue;

                default:
                    val.append(strBytes, s - 2, 2);
                    continue;
            }

            if (no >= 0) {
                if (no >= mat.groupCount()) continue;
                if (!mat.isCaptured(no)) continue;
                srcList = src.getByteList();
                srcBytes = srcList.unsafeBytes();
                val.append(srcBytes, srcList.begin + mat.start(no), mat.end(no) - mat.start(no));
            }
            
        }

        if (p < e) {
            if (val == null) {
                val = new ByteList(e - p);
                val.append(strBytes, p, e - p);
            } else {
                val.append(strBytes, p, e - p);
            }
        }

        if (val == null) return (RubyString)str;
        return RubyString.newString(getRuntime(), val);
    }

    /** rb_reg_init_copy
     * 
     */
    public IRubyObject initialize_copy(IRubyObject original) {
        if (this == original) return this;
        
        if (!(getMetaClass() == original.getMetaClass())){ // MRI also does a pointer comparison here
            throw getRuntime().newTypeError("wrong argument class");
        }

        RubyRegexp origRegexp = (RubyRegexp)original;
        source = origRegexp.source;
        pattern = origRegexp.pattern;
        code = origRegexp.code;

        return this;
    }

    /** rb_reg_inspect
     *
     */
    public IRubyObject inspect() {
        final ByteList regex = source;
        final int length = regex.length();
        StringBuffer sb = new StringBuffer(length + 2);

        sb.append('/');
        for (int i = 0; i < length; i++) {
            char c = regex.charAt(i);

            if (RubyString.isAlnum(c)) {
                sb.append(c);
            } else if (c == '/') {
                if (i == 0 || regex.charAt(i - 1) != '\\') {
                    sb.append("\\");
                }
            	sb.append(c);
            } else if (RubyString.isPrint(c)) {
                sb.append(c);
            } else if (c == '\n') {
                sb.append('\\').append('n');
            } else if (c == '\r') {
                sb.append('\\').append('r');
            } else if (c == '\t') {
                sb.append('\\').append('t');
            } else if (c == '\f') {
                sb.append('\\').append('f');
            } else if (c == '\u000B') {
                sb.append('\\').append('v');
            } else if (c == '\u0007') {
                sb.append('\\').append('a');
            } else if (c == '\u001B') {
                sb.append('\\').append('e');
            } else {
                sb.append(Sprintf.sprintf(getRuntime(),"\\%.3o",c));
            }
        }
        sb.append('/');

        if(code == KCode.NONE) {
            sb.append('n');
        } else if(code == KCode.UTF8) {
            sb.append('u');
        } else if(code == KCode.SJIS) {
            sb.append('s');
        }
        if ((flags & REFlags.IGNORE_CASE) > 0) {
            sb.append('i');
        }
  
        if ((flags & REFlags.DOTALL) > 0) {
            sb.append('m');
        }
        
        if ((flags & REFlags.IGNORE_SPACES) > 0) {
            sb.append('x');
        }

        return getRuntime().newString(sb.toString());
    }
    
    /**
     * rb_reg_s_union
     */
    public static IRubyObject union(IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = recv.getRuntime();
        
        if (args.length == 0) {
            return newInstance(recv, new IRubyObject[] {runtime.newString("(?!)")});
        }
        
        if (args.length == 1) {
            IRubyObject arg = args[0].convertToType(runtime.getClass("Regexp"), 0, "to_regexp", false);
            if (!arg.isNil()) {
                return arg;
            }
            return newInstance(recv, new IRubyObject[] {quote(recv, args[0].convertToString())});
        }
        
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < args.length; i++) {
        	if (i > 0) {
        		buffer.append("|");
            }
        	IRubyObject arg = args[i].convertToType(runtime.getClass("Regexp"), 0, "to_regexp", false);
            if (arg.isNil()) {
                arg = quote(recv, args[i].convertToString());
            }
            buffer.append(arg.toString());
        }
        
        return newInstance(recv, new IRubyObject[] {runtime.newString(buffer.toString())});
    }

    
    public IRubyObject to_s() {
        return getRuntime().newString(toString());
    }
    
    public String toString() {
    	StringBuffer buffer = new StringBuffer(100);
    	StringBuffer off = new StringBuffer(3);
    	
    	buffer.append("(?");
    	
    	flagToString(buffer, off, REFlags.DOTALL, 'm');
    	flagToString(buffer, off, REFlags.IGNORE_CASE, 'i');
    	flagToString(buffer, off, REFlags.IGNORE_SPACES, 'x');

		if (off.length() > 0) {
			buffer.append('-').append(off);
		}

    	buffer.append(':');
        buffer.append(pattern.toString().replaceAll("^/|([^\\\\])/", "$1\\\\/"));
		buffer.append(')');

    	return buffer.toString();
    }

    /** Helper method for the {@link #toString() toString} method which creates
     * an <i>on-off</i> pattern of {@link Pattern Pattern} flags. 
     * 
	 * @param buffer the default buffer for the output
	 * @param off temporary buffer for the off flags
	 * @param flag a Pattern flag
	 * @param c the char which represents the flag
	 */
	private void flagToString(StringBuffer buffer, StringBuffer off, int flag, char c) {
		if ((flags & flag) != 0) {
    		buffer.append(c);
    	} else {
    		off.append(c);
    	}
	}

    public static RubyRegexp unmarshalFrom(UnmarshalStream input) throws java.io.IOException {
        RubyRegexp result = newRegexp(input.getRuntime(), 
                                      input.unmarshalString(), input.unmarshalInt(), null);
        input.registerLinkTarget(result);
        return result;
    }

    public static void marshalTo(RubyRegexp regexp, MarshalStream output) throws java.io.IOException {
        output.writeString(regexp.pattern.toString());

        int _flags = 0;
        if ((regexp.flags & REFlags.DOTALL) > 0) {
            _flags |= RE_OPTION_MULTILINE;
        }
        if ((regexp.flags & REFlags.IGNORE_CASE) > 0) {
            _flags |= RE_OPTION_IGNORECASE;
        }
        if ((regexp.flags & REFlags.IGNORE_SPACES) > 0) {
            _flags |= RE_OPTION_EXTENDED;
        }
        output.writeInt(_flags);
    }
	
	public Pattern getPattern() {
		return this.pattern;
	}

    public RubyFixnum hash() {
        return getRuntime().newFixnum(this.pattern.toString().hashCode());
    }
}
