/*
 * Copyright (C) 2014 Indeed Inc.
 *
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
 package com.indeed.imhotep;

import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.indeed.imhotep.api.FTGSIterator;
import com.indeed.imhotep.api.RawFTGSIterator;
import com.indeed.imhotep.service.FTGSOutputStreamWriter;

import junit.framework.TestCase;

import org.apache.log4j.Appender;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.varia.LevelRangeFilter;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.annotation.Nullable;

/**
 * @author jsgroth
 */
@SuppressWarnings("unused")
public abstract class AbstractFTGSMergerCase extends TestCase {
    @BeforeClass
    public static void initLog4j() {
        BasicConfigurator.resetConfiguration();
        BasicConfigurator.configure();

        final Layout LAYOUT = new PatternLayout("[ %d{ISO8601} %-5p ] [%c{1}] %m%n");

        LevelRangeFilter ERROR_FILTER = new LevelRangeFilter();
        ERROR_FILTER.setLevelMin(Level.ERROR);
        ERROR_FILTER.setLevelMax(Level.FATAL);

        // everything including ERROR
        final Appender STDOUT = new ConsoleAppender(LAYOUT, ConsoleAppender.SYSTEM_OUT);

        // just things <= ERROR
        final Appender STDERR = new ConsoleAppender(LAYOUT, ConsoleAppender.SYSTEM_ERR);
        STDERR.addFilter(ERROR_FILTER);

        final Logger ROOT_LOGGER = Logger.getRootLogger();

        ROOT_LOGGER.removeAllAppenders();

        ROOT_LOGGER.setLevel(Level.WARN); // don't care about higher

        ROOT_LOGGER.addAppender(STDOUT);
        ROOT_LOGGER.addAppender(STDERR);
    }

    protected abstract FTGSIterator newFTGSMerger(Collection<? extends RawFTGSIterator> iterators, int numStats) throws IOException;

    public void testEmptyFields() throws IOException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final FTGSOutputStreamWriter w = new FTGSOutputStreamWriter(out);
        w.switchField("a", true);
        w.switchField("b", true);
        w.switchField("c", true);
        w.switchField("d", false);
        w.close();

        final int n = 13;
        List<RawFTGSIterator> iterators = new ArrayList<RawFTGSIterator>(n);
        for (int i = 0; i < n; i++) {
            final InputStream is = new ByteArrayInputStream(out.toByteArray());
            final InputStreamFTGSIterator temp = new InputStreamFTGSIterator(is, 0);
            iterators.add(temp);
        }

        FTGSIterator iter = newFTGSMerger(iterators, 0);
        assertTrue(iter.nextField());
        assertTrue(iter.fieldIsIntType());
        assertEquals("a", iter.fieldName());
        assertFalse(iter.nextTerm());
        assertTrue(iter.nextField());
        assertTrue(iter.fieldIsIntType());
        assertEquals("b", iter.fieldName());
        assertFalse(iter.nextTerm());
        assertTrue(iter.nextField());
        assertTrue(iter.fieldIsIntType());
        assertEquals("c", iter.fieldName());
        assertFalse(iter.nextTerm());
        assertTrue(iter.nextField());
        assertFalse(iter.fieldIsIntType());
        assertEquals("d", iter.fieldName());
        assertFalse(iter.nextTerm());
        assertFalse(iter.nextField());
    }

    @Test
    public void testIt() throws IOException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        writeStream(out);
        {
            final int n = 13;
            List<RawFTGSIterator> iterators = new ArrayList<RawFTGSIterator>(n);
            List<InputStream> inputStreams = new ArrayList<InputStream>(n);
            for (int i = 0; i < n; i++) {
                final InputStream is = new ByteArrayInputStream(out.toByteArray());
                inputStreams.add(is);
                final InputStreamFTGSIterator temp = new InputStreamFTGSIterator(is, 2);
                iterators.add(temp);
            }
            final long[] stats = new long[2];

            FTGSIterator input = newFTGSMerger(iterators, 2);
            assertTrue(input.nextField());
            assertEquals("abc", input.fieldName());
            assertEquals(true, input.fieldIsIntType());
            assertTrue(input.nextTerm());
            assertEquals(0, input.termIntVal());
            assertTrue(input.nextGroup());
            assertEquals(0, input.group());
            input.groupStats(stats);
            assertEquals(12345*n, stats[0]);
            // noinspection PointlessArithmeticExpression
            assertEquals(0*n, stats[1]);
            assertTrue(input.nextGroup());
            assertEquals(1, input.group());
            input.groupStats(stats);
            assertEquals(2*n, stats[0]);
            assertEquals(3*n, stats[1]);
            assertTrue(input.nextGroup());
            assertEquals(4, input.group());
            input.groupStats(stats);
            assertEquals(4*n, stats[0]);
            assertEquals(5*n, stats[1]);
            assertFalse(input.nextGroup());
            assertTrue(input.nextTerm());
            assertEquals(1, input.termIntVal());
            assertTrue(input.nextGroup());
            assertEquals(3, input.group());
            input.groupStats(stats);
            assertEquals(6*n, stats[0]);
            assertEquals(123456789012345l*n, stats[1]);
            assertFalse(input.nextGroup());
            assertTrue(input.nextTerm());
            assertEquals(3, input.termIntVal());
            assertTrue(input.nextGroup());
            assertEquals(5, input.group());
            input.groupStats(stats);
            assertEquals(3*n, stats[0]);
            assertEquals(4*n, stats[1]);
            assertFalse(input.nextGroup());
            assertFalse(input.nextTerm());
            assertTrue(input.nextField());
            assertEquals("xyz", input.fieldName());
            assertEquals(false, input.fieldIsIntType());
            assertTrue(input.nextTerm());
            assertEquals("foobar", input.termStringVal());
            assertTrue(input.nextGroup());
            assertEquals(0, input.group());
            input.groupStats(stats);
            assertEquals(999*n, stats[0]);
            assertEquals(1000*n, stats[1]);
            assertTrue(input.nextGroup());
            assertEquals(3, input.group());
            input.groupStats(stats);
            assertEquals(1001*n, stats[0]);
            assertEquals(1002*n, stats[1]);
            assertFalse(input.nextGroup());
            assertTrue(input.nextTerm());
            assertEquals("foobar2", input.termStringVal());
            assertTrue(input.nextGroup());
            assertEquals(100, input.group());
            input.groupStats(stats);
            assertEquals(999*n, stats[0]);
            assertEquals(997*n, stats[1]);
            assertTrue(input.nextGroup());
            assertEquals(300, input.group());
            input.groupStats(stats);
            assertEquals(995*n, stats[0]);
            assertEquals(993*n, stats[1]);
            assertFalse(input.nextGroup());
            assertTrue(input.nextTerm());
            assertEquals("foobaz", input.termStringVal());
            assertTrue(input.nextGroup());
            assertEquals(100, input.group());
            input.groupStats(stats);
            assertEquals(999*n, stats[0]);
            assertEquals(997*n, stats[1]);
            assertTrue(input.nextGroup());
            assertEquals(300, input.group());
            input.groupStats(stats);
            assertEquals(995*n, stats[0]);
            assertEquals(993*n, stats[1]);
            assertFalse(input.nextGroup());
            assertFalse(input.nextTerm());
            assertFalse(input.nextField());

            for (InputStream is : inputStreams) {
                assertEquals(-1, is.read());
            }
        }
    }

    @Test
    public void testGroupless() throws IOException {
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        writeStream(baos);
        final List<RawFTGSIterator> list = new ArrayList<RawFTGSIterator>(10);
        for (int i = 0; i < 10; ++i) {
            final InputStream is = new ByteArrayInputStream(baos.toByteArray());
            final InputStreamFTGSIterator it = new InputStreamFTGSIterator(is, 2);
            list.add(it);
        }
        final FTGSIterator it = newFTGSMerger(list, 2);
        assertTrue(it.nextField());
        assertEquals("abc", it.fieldName());
        assertTrue(it.fieldIsIntType());
        assertTrue(it.nextTerm());
        assertEquals(0, it.termIntVal());
        assertTrue(it.nextTerm());
        assertEquals(1, it.termIntVal());
        assertTrue(it.nextTerm());
        assertEquals(3, it.termIntVal());
        assertFalse(it.nextTerm());
        assertTrue(it.nextField());
        assertEquals("xyz", it.fieldName());
        assertFalse(it.fieldIsIntType());
        assertTrue(it.nextTerm());
        assertEquals("foobar", it.termStringVal());
        assertTrue(it.nextTerm());
        assertEquals("foobar2", it.termStringVal());
        assertTrue(it.nextTerm());
        assertEquals("foobaz", it.termStringVal());
        assertFalse(it.nextTerm());
        assertFalse(it.nextField());
    }

    @Test
    private static void writeStream(ByteArrayOutputStream out) throws IOException {
        final FTGSOutputStreamWriter writer = new FTGSOutputStreamWriter(out);
        writer.switchField("abc", true);
        writer.switchIntTerm(0, 0);
        writer.switchGroup(0);
        writer.addStat(12345);
        writer.addStat(0);
        writer.switchGroup(1);
        writer.addStat(2);
        writer.addStat(3);
        writer.switchGroup(4);
        writer.addStat(4);
        writer.addStat(5);
        writer.switchIntTerm(1, 0);
        writer.switchGroup(3);
        writer.addStat(6);
        writer.addStat(123456789012345l);
        writer.switchIntTerm(3, 0);
        writer.switchGroup(5);
        writer.addStat(3);
        writer.addStat(4);
        writer.switchIntTerm(100, 0); // this term shouldn't really get written
        writer.switchField("xyz", false);
        writer.switchBytesTerm("foobar".getBytes(), "foobar".getBytes().length, 0);
        writer.switchGroup(0);
        writer.addStat(999);
        writer.addStat(1000);
        writer.switchGroup(3);
        writer.addStat(1001);
        writer.addStat(1002);
        writer.switchBytesTerm("foobar2".getBytes(), "foobar2".getBytes().length, 0);
        writer.switchGroup(100);
        writer.addStat(999);
        writer.addStat(997);
        writer.switchGroup(300);
        writer.addStat(995);
        writer.addStat(993);
        writer.switchBytesTerm("foobaz".getBytes(), "foobaz".getBytes().length, 0);
        writer.switchGroup(100);
        writer.addStat(999);
        writer.addStat(997);
        writer.switchGroup(300);
        writer.addStat(995);
        writer.addStat(993);
        writer.close();
    }

    @Test
    public void testSomethingElse() throws IOException {
        final List<ByteArrayOutputStream> streams = new ArrayList<ByteArrayOutputStream>();
        {
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            streams.add(baos);
            final FTGSOutputStreamWriter osw = new FTGSOutputStreamWriter(baos);
            osw.switchField("companyid", true);
            osw.switchIntTerm(5, 10);
            osw.switchGroup(1);
            osw.addStat(10);
            osw.addStat(11);
            osw.switchGroup(2);
            osw.addStat(12);
            osw.addStat(13);
            osw.switchGroup(4);
            osw.addStat(14);
            osw.addStat(15);
            osw.switchIntTerm(10, 20);
            osw.switchGroup(1);
            osw.addStat(16);
            osw.addStat(17);
            osw.close();
            baos.close();
        }
        {
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            streams.add(baos);
            final FTGSOutputStreamWriter osw = new FTGSOutputStreamWriter(baos);
            osw.switchField("companyid", true);
            osw.switchIntTerm(5, 10);
            osw.switchGroup(2);
            osw.addStat(10);
            osw.addStat(11);
            osw.switchGroup(3);
            osw.addStat(12);
            osw.addStat(13);
            osw.switchGroup(5);
            osw.addStat(14);
            osw.addStat(15);
            osw.switchIntTerm(10, 20);
            osw.switchGroup(4);
            osw.addStat(16);
            osw.addStat(17);
            osw.close();
            baos.close();
        }
        {
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            streams.add(baos);
            final FTGSOutputStreamWriter osw = new FTGSOutputStreamWriter(baos);
            osw.switchField("companyid", true);
            osw.switchIntTerm(5, 10);
            osw.switchGroup(1);
            osw.addStat(10);
            osw.addStat(11);
            osw.switchGroup(2);
            osw.addStat(12);
            osw.addStat(13);
            osw.switchGroup(4);
            osw.addStat(14);
            osw.addStat(15);
            osw.switchIntTerm(9, 20);
            osw.switchGroup(3);
            osw.addStat(16);
            osw.addStat(17);
            osw.close();
            baos.close();
        }

        final List<RawFTGSIterator> iterators = new ArrayList<RawFTGSIterator>();
        for (ByteArrayOutputStream os : streams) {
            iterators.add(new InputStreamFTGSIterator(new ByteArrayInputStream(os.toByteArray()), 2));
        }
        final FTGSIterator merger = newFTGSMerger(iterators, 2);
        long[] stats = new long[2];

        assertTrue(merger.nextField());
        assertEquals("companyid", merger.fieldName());
        assertTrue(merger.fieldIsIntType());
        assertTrue(merger.nextTerm());
        assertEquals(5, merger.termIntVal());
        assertEquals(30, merger.termDocFreq());
        assertTrue(merger.nextGroup());
        assertEquals(1, merger.group());
        merger.groupStats(stats);
        assertEquals(20L, stats[0]);
        assertEquals(22L, stats[1]);
        assertTrue(merger.nextGroup());
        assertEquals(2, merger.group());
        merger.groupStats(stats);
        assertEquals(34L, stats[0]);
        assertEquals(37L, stats[1]);
        assertTrue(merger.nextGroup());
        assertEquals(3, merger.group());
        merger.groupStats(stats);
        assertEquals(12L, stats[0]);
        assertEquals(13L, stats[1]);
        assertTrue(merger.nextGroup());
        assertEquals(4, merger.group());
        merger.groupStats(stats);
        assertEquals(28L, stats[0]);
        assertEquals(30L, stats[1]);
        assertTrue(merger.nextGroup());
        assertEquals(5, merger.group());
        merger.groupStats(stats);
        assertEquals(14L, stats[0]);
        assertEquals(15L, stats[1]);
        assertFalse(merger.nextGroup());
        assertTrue(merger.nextTerm());
        assertEquals(9, merger.termIntVal());
        assertEquals(20, merger.termDocFreq());
        assertTrue(merger.nextGroup());
        assertEquals(3, merger.group());
        merger.groupStats(stats);
        assertEquals(16L, stats[0]);
        assertEquals(17L, stats[1]);
        assertFalse(merger.nextGroup());
        assertTrue(merger.nextTerm());
        assertEquals(10, merger.termIntVal());
        assertEquals(40, merger.termDocFreq());
        assertTrue(merger.nextGroup());
        assertEquals(1, merger.group());
        merger.groupStats(stats);
        assertEquals(16L, stats[0]);
        assertEquals(17L, stats[1]);
        assertTrue(merger.nextGroup());
        assertEquals(4, merger.group());
        merger.groupStats(stats);
        assertEquals(16L, stats[0]);
        assertEquals(17L, stats[1]);
        assertFalse(merger.nextGroup());
        assertFalse(merger.nextTerm());
        assertFalse(merger.nextField());
    }

    @Test
    public void testTermWithNoGroups() throws IOException {
        final FTGSIterator iterator = newFTGSMerger(Lists.newArrayList(makeMockIterator(0), makeMockIterator(0)), 0);
        assertTrue(iterator.nextField());
        assertFalse(iterator.fieldIsIntType());
        assertTrue(iterator.nextTerm());
        assertEquals("a", iterator.termStringVal());
        assertTrue(iterator.nextGroup());
        assertEquals(1, iterator.group());
        assertTrue(iterator.nextGroup());
        assertEquals(2, iterator.group());
        assertTrue(iterator.nextGroup());
        assertEquals(3, iterator.group());
        assertTrue(iterator.nextGroup());
        assertEquals(4, iterator.group());
        assertTrue(iterator.nextGroup());
        assertEquals(5, iterator.group());
        assertFalse(iterator.nextGroup());
        assertTrue(iterator.nextTerm());
        assertEquals("b", iterator.termStringVal());
        assertTrue(iterator.nextGroup());
        assertEquals(1, iterator.group());
        assertTrue(iterator.nextGroup());
        assertEquals(3, iterator.group());
        assertFalse(iterator.nextGroup());
        assertTrue(iterator.nextTerm());
        assertEquals("c", iterator.termStringVal());
        assertTrue(iterator.nextGroup());
        assertEquals(4, iterator.group());
        assertFalse(iterator.nextGroup());
        assertTrue(iterator.nextTerm());

        // it's ok if "d" is skipped since it matches no groups
        if ("d".equals(iterator.termStringVal())) {
            assertFalse(iterator.nextGroup());
            assertTrue(iterator.nextTerm());
        }
        
        assertEquals("e", iterator.termStringVal());
        assertTrue(iterator.nextGroup());
        assertEquals(5, iterator.group());
        assertFalse(iterator.nextGroup());
        assertFalse(iterator.nextTerm());
        assertFalse(iterator.nextField());
    }

    @Test
    public void testTestTest() throws IOException {
        final FTGSIterator iterator = newFTGSMerger(Lists.newArrayList(makeMockIterator3(), makeMockIterator2()), 2);
        long[] stats = new long[2];
        assertTrue(iterator.nextField());
        assertFalse(iterator.fieldIsIntType());
        assertTrue(iterator.nextTerm());
        assertEquals("a", iterator.termStringVal());
        assertTrue(iterator.nextGroup());
        assertEquals(1, iterator.group());
        iterator.groupStats(stats);
        assertEquals(18, stats[0]);
        assertEquals(20, stats[1]);
        assertTrue(iterator.nextGroup());
        assertEquals(2, iterator.group());
        iterator.groupStats(stats);
        assertEquals(22, stats[0]);
        assertEquals(24, stats[1]);
        assertTrue(iterator.nextGroup());
        assertEquals(3, iterator.group());
        iterator.groupStats(stats);
        assertEquals(26, stats[0]);
        assertEquals(28, stats[1]);
        assertTrue(iterator.nextGroup());
        assertEquals(4, iterator.group());
        iterator.groupStats(stats);
        assertEquals(30, stats[0]);
        assertEquals(32, stats[1]);
        assertTrue(iterator.nextGroup());
        assertEquals(5, iterator.group());
        iterator.groupStats(stats);
        assertEquals(34, stats[0]);
        assertEquals(36, stats[1]);
        assertFalse(iterator.nextGroup());
        assertTrue(iterator.nextTerm());
        assertEquals("b", iterator.termStringVal());
        assertTrue(iterator.nextGroup());
        assertEquals(1, iterator.group());
        iterator.groupStats(stats);
        assertEquals(38, stats[0]);
        assertEquals(40, stats[1]);
        assertTrue(iterator.nextGroup());
        assertEquals(3, iterator.group());
        iterator.groupStats(stats);
        assertEquals(42, stats[0]);
        assertEquals(44, stats[1]);
        assertFalse(iterator.nextGroup());
        assertTrue(iterator.nextTerm());
        assertEquals("c", iterator.termStringVal());
        assertTrue(iterator.nextGroup());
        assertEquals(4, iterator.group());
        iterator.groupStats(stats);
        assertEquals(46, stats[0]);
        assertEquals(48, stats[1]);
        assertFalse(iterator.nextGroup());
        assertTrue(iterator.nextTerm());

        // it's ok if "d" is skipped since it matches no groups
        if ("d".equals(iterator.termStringVal())) {
            assertFalse(iterator.nextGroup());
            assertTrue(iterator.nextTerm());
        }

        // it's ok if "e" is skipped since it matches no groups        
        if ("e".equals(iterator.termStringVal())) {
            assertFalse(iterator.nextGroup());
            assertTrue(iterator.nextTerm());
        }

        assertEquals("f", iterator.termStringVal());
        assertTrue(iterator.nextGroup());
        iterator.groupStats(stats);
        assertEquals(100, stats[0]);
        assertEquals(101, stats[1]);
        assertFalse(iterator.nextTerm());
        assertFalse(iterator.nextField());
    }

    private static RawFTGSIterator makeMockIterator(final int numStats) {
        return new RawFTGSIterator() {
            String[] fields = {"f1"};
            boolean[] intType = {false};
            int fieldIndex = -1;
            String[][] stringVals = {{"a", "b", "c", "d", "e"}};
            int termIndex = -1;
            int[][][] groups = {{
                    {1, 2, 3, 4, 5},
                    {1, 3},
                    {4},
                    {},
                    {5}
            }};
            long[][][][] groupStats = {{
                    {{17, 18}, {19, 20}, {21, 22}, {23, 24}, {25, 26}},
                    {{27, 28}, {29, 30}},
                    {{31, 32}},
                    {},
                    {{33, 34}}
            }};
            int groupIndex = -1;

            @Override
            public boolean nextField() {
                if (++fieldIndex >= fields.length) return false;
                termIndex = -1;
                return true;
            }

            @Override
            public String fieldName() {
                return fields[fieldIndex];
            }

            @Override
            public boolean fieldIsIntType() {
                return intType[fieldIndex];
            }

            @Override
            public boolean nextTerm() {
                if (++termIndex >= stringVals[fieldIndex].length) return false;
                groupIndex = -1;
                return true;
            }

            @Override
            public long termDocFreq() {
                return 0L;
            }

            @Override
            public long termIntVal() {
                return 0;
            }

            @Override
            public String termStringVal() {
                return stringVals[fieldIndex][termIndex];
            }

            @Override
            public byte[] termStringBytes() {
                return termStringVal().getBytes(Charsets.UTF_8);
            }

            @Override
            public int termStringLength() {
                return termStringBytes().length;
            }

            @Override
            public boolean nextGroup() {
                return ++groupIndex < groups[fieldIndex][termIndex].length;
            }

            @Override
            public int group() {
                return groups[fieldIndex][termIndex][groupIndex];
            }

            @Override
            public void groupStats(long[] stats) {
                System.arraycopy(groupStats[fieldIndex][termIndex][groupIndex], 0, stats, 0, numStats);
            }

            @Override
            public void close() {
            }
        };
    }

    private static RawFTGSIterator makeMockIterator2() {
        return new RawFTGSIterator() {
            String[] fields = {"f1"};
            boolean[] intType = {false};
            int fieldIndex = -1;
            String[][] stringVals = {{"a", "b", "c", "d", "e", "f"}};
            int termIndex = -1;
            int[][][] groups = {{
                    {1, 2, 3, 4, 5},
                    {1, 3},
                    {4},
                    {},
                    {},
                    {1}
            }};
            long[][][][] groupStats = {{
                    {{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}},
                    {{11, 12}, {13, 14}},
                    {{15, 16}},
                    {},
                    {},
                    {{100, 101}}
            }};
            int groupIndex = -1;

            @Override
            public boolean nextField() {
                if (++fieldIndex >= fields.length) return false;
                termIndex = -1;
                return true;
            }

            @Override
            public String fieldName() {
                return fields[fieldIndex];
            }

            @Override
            public boolean fieldIsIntType() {
                return intType[fieldIndex];
            }

            @Override
            public boolean nextTerm() {
                if (++termIndex >= stringVals[fieldIndex].length) return false;
                groupIndex = -1;
                return true;
            }

            @Override
            public long termDocFreq() {
                return 0L;
            }

            @Override
            public long termIntVal() {
                return 0;
            }

            @Override
            public String termStringVal() {
                return stringVals[fieldIndex][termIndex];
            }

            @Override
            public byte[] termStringBytes() {
                return termStringVal().getBytes(Charsets.UTF_8);
            }

            @Override
            public int termStringLength() {
                return termStringBytes().length;
            }

            @Override
            public boolean nextGroup() {
                return ++groupIndex < groups[fieldIndex][termIndex].length;
            }

            @Override
            public int group() {
                return groups[fieldIndex][termIndex][groupIndex];
            }

            @Override
            public void groupStats(long[] stats) {
                System.arraycopy(groupStats[fieldIndex][termIndex][groupIndex], 0, stats, 0, 2);
            }

            @Override
            public void close() {
            }
        };
    }

    private static RawFTGSIterator makeMockIterator3() {
        return new RawFTGSIterator() {
            String[] fields = {"f1"};
            boolean[] intType = {false};
            int fieldIndex = -1;
            String[][] stringVals = {{"a", "b", "c", "d"}};
            int termIndex = -1;
            int[][][] groups = {{
                    {1, 2, 3, 4, 5},
                    {1, 3},
                    {4},
                    {},
            }};
            long[][][][] groupStats = {{
                    {{17, 18}, {19, 20}, {21, 22}, {23, 24}, {25, 26}},
                    {{27, 28}, {29, 30}},
                    {{31, 32}},
                    {},
            }};
            int groupIndex = -1;

            @Override
            public boolean nextField() {
                if (++fieldIndex >= fields.length) return false;
                termIndex = -1;
                return true;
            }

            @Override
            public String fieldName() {
                return fields[fieldIndex];
            }

            @Override
            public boolean fieldIsIntType() {
                return intType[fieldIndex];
            }

            @Override
            public boolean nextTerm() {
                if (++termIndex >= stringVals[fieldIndex].length) return false;
                groupIndex = -1;
                return true;
            }

            @Override
            public long termDocFreq() {
                return 0L;
            }

            @Override
            public long termIntVal() {
                return 0;
            }

            @Override
            public String termStringVal() {
                return stringVals[fieldIndex][termIndex];
            }

            @Override
            public byte[] termStringBytes() {
                return termStringVal().getBytes(Charsets.UTF_8);
            }

            @Override
            public int termStringLength() {
                return termStringBytes().length;
            }

            @Override
            public boolean nextGroup() {
                return ++groupIndex < groups[fieldIndex][termIndex].length;
            }

            @Override
            public int group() {
                return groups[fieldIndex][termIndex][groupIndex];
            }

            @Override
            public void groupStats(long[] stats) {
                System.arraycopy(groupStats[fieldIndex][termIndex][groupIndex], 0, stats, 0, 2);
            }

            @Override
            public void close() {
            }
        };
    }
}
