/**
 * Copyright (c) 2012-2014 André Bargull
 * Alle Rechte vorbehalten / All Rights Reserved.  Use is subject to license terms.
 *
 * <https://github.com/anba/es6draft>
 */
package com.github.anba.es6draft.regexp;

import java.util.regex.MatchResult;

import org.joni.Matcher;
import org.joni.Option;
import org.joni.Regex;
import org.joni.Region;

/**
 * {@link MatchState} implementation for Joni {@link Regex} regular expressions
 */
final class JoniMatchState implements MatchState {
    private final Matcher matcher;
    private final String string;
    private final int byteLength;
    private int begin = -1, end = 0;
    private Region region;

    public JoniMatchState(Matcher matcher, String string) {
        this.matcher = matcher;
        this.string = string;
        this.byteLength = string.length() << 1;
    }

    private JoniMatchState(String string, int begin, int end, Region region) {
        this.matcher = null;
        this.string = string;
        this.byteLength = string.length() << 1;
        this.begin = begin;
        this.end = end;
        this.region = region;
    }

    private boolean update(int r) {
        begin = matcher.getBegin();
        end = matcher.getEnd();
        region = matcher.getRegion();
        return r > Matcher.FAILED;
    }

    private void ensureResult() {
        if (begin < 0)
            throw new IllegalStateException("No match!");
    }

    private void ensureValidGroup(int group) {
        if (group < 0 || group > groupCount())
            throw new IndexOutOfBoundsException("Invalid group: " + group);
    }

    @Override
    public MatchResult toMatchResult() {
        return new JoniMatchState(string, begin, end, region != null ? region.clone() : null);
    }

    @Override
    public boolean find() {
        int start = end != begin ? end : end + 2;
        return update(matcher.search(start, byteLength, Option.NONE));
    }

    @Override
    public boolean find(int start) {
        return update(matcher.search(start << 1, byteLength, Option.NONE));
    }

    @Override
    public boolean matches(int start) {
        return update(matcher.match(start << 1, byteLength, Option.NONE));
    }

    @Override
    public int start() {
        ensureResult();
        return begin >> 1;
    }

    @Override
    public int start(int group) {
        ensureResult();
        ensureValidGroup(group);
        return (group == 0 ? begin : region.beg[group]) >> 1;
    }

    @Override
    public int end() {
        ensureResult();
        return end >> 1;
    }

    @Override
    public int end(int group) {
        ensureResult();
        ensureValidGroup(group);
        return (group == 0 ? end : region.end[group]) >> 1;
    }

    @Override
    public String group() {
        return group(0);
    }

    @Override
    public String group(int group) {
        int start = start(group), end = end(group);
        if (start == -1 || end == -1) {
            return null;
        }
        return string.substring(start, end);
    }

    @Override
    public int groupCount() {
        Region region = this.region;
        return region != null ? region.numRegs - 1 : 0;
    }
}

@@ -554,42 +554,8 @@
 ng;%0A
-    private final int byteLength;%0A
     
@@ -741,56 +741,8 @@
 ng;%0A
-        this.byteLength = string.length() %3C%3C 1;%0A
     
@@ -886,56 +886,8 @@
 ng;%0A
-        this.byteLength = string.length() %3C%3C 1;%0A
     
@@ -1171,108 +1171,606 @@
 ate 
-void ensureResult() %7B%0A        if (begin %3C 0)%0A            throw new IllegalStateException(%22No match!%22
+int stringLength() %7B%0A        return string.length();%0A    %7D%0A%0A    private int byteLength() %7B%0A        return string.length() %3C%3C 1;%0A    %7D%0A%0A    private int toStringIndex(int byteIndex) %7B%0A        return byteIndex %3E%3E 1;%0A    %7D%0A%0A    private int toByteIndex(int stringIndex) %7B%0A        return stringIndex %3C%3C 1;%0A    %7D%0A%0A    private void ensureResult() %7B%0A        if (begin %3C 0)%0A            throw new IllegalStateException(%22No match!%22);%0A    %7D%0A%0A    private void ensureValidIndex(int index) %7B%0A        if (index %3C 0 %7C%7C index %3E stringLength())%0A            throw new IndexOutOfBoundsException(%22Invalid index: %22 + index
 );%0A 
@@ -2247,32 +2247,34 @@
 tart, byteLength
+()
 , Option.NONE));
@@ -2324,32 +2324,65 @@
 nd(int start) %7B%0A
+        ensureValidIndex(start);%0A
         return u
@@ -2402,26 +2402,34 @@
 .search(
-start %3C%3C 1
+toByteIndex(start)
 , byteLe
@@ -2424,32 +2424,34 @@
 art), byteLength
+()
 , Option.NONE));
@@ -2504,32 +2504,65 @@
 es(int start) %7B%0A
+        ensureValidIndex(start);%0A
         return u
@@ -2585,18 +2585,26 @@
 tch(
-start %3C%3C 1
+toByteIndex(start)
 , by
@@ -2611,16 +2611,18 @@
 teLength
+()
 , Option
@@ -2719,18 +2719,28 @@
 urn 
-begin %3E%3E 1
+toStringIndex(begin)
 ;%0A  
@@ -2856,32 +2856,45 @@
 %0A        return 
+toStringIndex
 (group == 0 ? be
@@ -2909,37 +2909,32 @@
 gion.beg%5Bgroup%5D)
- %3E%3E 1
 ;%0A    %7D%0A%0A    @Ov
@@ -3006,16 +3006,26 @@
 urn 
-end %3E%3E 1
+toStringIndex(end)
 ;%0A  
@@ -3147,16 +3147,29 @@
  return 
+toStringIndex
 (group =
@@ -3198,21 +3198,16 @@
 %5Bgroup%5D)
- %3E%3E 1
 ;%0A    %7D%0A

Move string/byte-index conversion into separate methods