package com.fasterxml.jackson.core.filter;

import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.SerializedString;

/**
 * Low-level tests for explicit, hand-written tests for generator-side
 * filtering.
 */
@SuppressWarnings("resource")
public class BasicGeneratorFilteringTest extends BaseTest
{
    static class NameMatchFilter extends TokenFilter
    {
        private final Set<String> _names;
        
        public NameMatchFilter(String... names) {
            _names = new HashSet<String>(Arrays.asList(names));
        }

        @Override
        public TokenFilter includeElement(int index) {
            return this;
        }

        @Override
        public TokenFilter includeProperty(String name) {
            if (_names.contains(name)) {
                return TokenFilter.INCLUDE_ALL;
            }
            return this;
        }

        @Override
        protected boolean _includeScalar() { return false; }
    }

    static class IndexMatchFilter extends TokenFilter
    {
        private final BitSet _indices;
        
        public IndexMatchFilter(int... ixs) {
            _indices = new BitSet();
            for (int ix : ixs) {
                _indices.set(ix);
            }
        }

        @Override
        public TokenFilter includeProperty(String name) {
            return this;
        }
        
        @Override
        public TokenFilter includeElement(int index) {
            if (_indices.get(index)) {
                return TokenFilter.INCLUDE_ALL;
            }
            return null;
        }

        @Override
        protected boolean _includeScalar() { return false; }
    }

    /*
    /**********************************************************
    /* Test methods
    /**********************************************************
     */

    private final JsonFactory JSON_F = new JsonFactory();

    public void testNonFiltering() throws Exception
    {
        // First, verify non-filtering
        StringWriter w = new StringWriter();
        JsonGenerator gen = JSON_F.createGenerator(w);
        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
        writeJsonDoc(JSON_F, JSON, gen);
        assertEquals(aposToQuotes(
                "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}"),
                w.toString());
    }

    public void testSingleMatchFilteringWithoutPath() throws Exception
    {
        StringWriter w = new StringWriter();
        JsonGenerator gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
                new NameMatchFilter("value"),
                false, // includePath
                false // multipleMatches
                );
        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
        writeJsonDoc(JSON_F, JSON, gen);

        // 21-Apr-2015, tatu: note that there were plans to actually
        //     allow "immediate parent inclusion" for matches on property
        //    names. This behavior was NOT included in release however, so:
//        assertEquals(aposToQuotes("{'value':3}"), w.toString());

        assertEquals(aposToQuotes("3"), w.toString());
    }

    public void testSingleMatchFilteringWithPath() throws Exception
    {
        StringWriter w = new StringWriter();
        JsonGenerator origGen = JSON_F.createGenerator(w);
        NameMatchFilter filter = new NameMatchFilter("value");
        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(origGen,
                filter,
                true, // includePath
                false // multipleMatches
                );

        // Hmmh. Should we get access to eventual target?
        assertSame(w, gen.getOutputTarget());
        assertNotNull(gen.getFilterContext());
        assertSame(filter, gen.getFilter());

        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
        writeJsonDoc(JSON_F, JSON, gen);
        assertEquals(aposToQuotes("{'ob':{'value':3}}"), w.toString());

        assertEquals(1, gen.getMatchCount());
    }

    public void testSingleMatchFilteringWithPathSkippedArray() throws Exception
    {
        StringWriter w = new StringWriter();
        JsonGenerator origGen = JSON_F.createGenerator(w);
        NameMatchFilter filter = new NameMatchFilter("value");
        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(origGen,
                filter,
                true, // includePath
                false // multipleMatches
                );

        // Hmmh. Should we get access to eventual target?
        assertSame(w, gen.getOutputTarget());
        assertNotNull(gen.getFilterContext());
        assertSame(filter, gen.getFilter());

        final String JSON = "{'array':[1,[2,3]],'ob':[{'value':'bar'}],'b':{'foo':[1,'foo']}}";
        writeJsonDoc(JSON_F, JSON, gen);
        assertEquals(aposToQuotes("{'ob':[{'value':'bar'}]}"), w.toString());
        assertEquals(1, gen.getMatchCount());
    }

    // Alternative take, using slightly different calls for FIELD_NAME, START_ARRAY
    public void testSingleMatchFilteringWithPathAlternate1() throws Exception
    {
        StringWriter w = new StringWriter();
        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
                new NameMatchFilter("value"),
                true, // includePath
                false // multipleMatches
                );
        //final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':[3],'value2':'foo'},'b':true}";

        gen.writeStartObject();
        gen.writeFieldName(new SerializedString("a"));
        gen.writeNumber(123);

        gen.writeFieldName("array");
        gen.writeStartArray(2);
        gen.writeNumber("1");
        gen.writeNumber((short) 2);
        gen.writeEndArray();

        gen.writeFieldName(new SerializedString("ob"));
        gen.writeStartObject();
        gen.writeNumberField("value0", 2);
        gen.writeFieldName(new SerializedString("value"));
        gen.writeStartArray(1);
        gen.writeString(new SerializedString("x")); // just to vary generation method
        gen.writeEndArray();
        gen.writeStringField("value2", "foo");

        gen.writeEndObject();

        gen.writeBooleanField("b", true);
        
        gen.writeEndObject();
        gen.close();

        assertEquals(aposToQuotes("{'ob':{'value':['x']}}"), w.toString());
        assertEquals(1, gen.getMatchCount());
    }

    public void testSingleMatchFilteringWithPathRawBinary() throws Exception
    {
        StringWriter w = new StringWriter();
        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
                new NameMatchFilter("array"),
                true, // includePath
                false // multipleMatches
                );
        //final String JSON = "{'header':['ENCODED',raw],'array':['base64stuff',1,2,3,4,5,6.25,7.5],'extra':[1,2,3,4,5,6.25,7.5]}";

        gen.writeStartObject();

        gen.writeFieldName("header");
        gen.writeStartArray();
        gen.writeBinary(new byte[] { 1 });
        gen.writeRawValue(new SerializedString("1"));
        gen.writeRawValue("2");
        gen.writeEndArray();
        
        gen.writeFieldName("array");

        gen.writeStartArray();
        gen.writeBinary(new byte[] { 1 });
        gen.writeNumber((short) 1);
        gen.writeNumber((int) 2);
        gen.writeNumber((long) 3);
        gen.writeNumber(BigInteger.valueOf(4));
        gen.writeRaw(" ");
        gen.writeNumber(new BigDecimal("5.0"));
        gen.writeRaw(new SerializedString(" /*x*/"));
        gen.writeNumber(6.25f);
        gen.writeNumber(7.5);
        gen.writeEndArray();

        gen.writeArrayFieldStart("extra");
        gen.writeNumber((short) 1);
        gen.writeNumber((int) 2);
        gen.writeNumber((long) 3);
        gen.writeNumber(BigInteger.valueOf(4));
        gen.writeNumber(new BigDecimal("5.0"));
        gen.writeNumber(6.25f);
        gen.writeNumber(7.5);
        gen.writeEndArray();
        
        gen.writeEndObject();
        gen.close();

        assertEquals(aposToQuotes("{'array':['AQ==',1,2,3,4 ,5.0 /*x*/,6.25,7.5]}"), w.toString());
        assertEquals(1, gen.getMatchCount());
    }
    
    public void testMultipleMatchFilteringWithPath1() throws Exception
    {
        StringWriter w = new StringWriter();
        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
                new NameMatchFilter("value0", "value2"),
                true, /* includePath */ true /* multipleMatches */ );
        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
        writeJsonDoc(JSON_F, JSON, gen);
        assertEquals(aposToQuotes("{'ob':{'value0':2,'value2':4}}"), w.toString());
        assertEquals(2, gen.getMatchCount());
    }

    public void testMultipleMatchFilteringWithPath2() throws Exception
    {
        StringWriter w = new StringWriter();

        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
                new NameMatchFilter("array", "b", "value"),
                true, true);
        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
        writeJsonDoc(JSON_F, JSON, gen);
        assertEquals(aposToQuotes("{'array':[1,2],'ob':{'value':3},'b':true}"), w.toString());
        assertEquals(3, gen.getMatchCount());
    }

    public void testMultipleMatchFilteringWithPath3() throws Exception
    {
        StringWriter w = new StringWriter();

        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
                new NameMatchFilter("value"),
                true, true);
        final String JSON = "{'root':{'a0':true,'a':{'value':3},'b':{'value':4}},'b0':false}";
        writeJsonDoc(JSON_F, JSON, gen);
        assertEquals(aposToQuotes("{'root':{'a':{'value':3},'b':{'value':4}}}"), w.toString());
        assertEquals(2, gen.getMatchCount());
    }

    public void testIndexMatchWithPath1() throws Exception
    {
        StringWriter w = new StringWriter();
        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
                new IndexMatchFilter(1),
                true, true);
        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
        writeJsonDoc(JSON_F, JSON, gen);
        assertEquals(aposToQuotes("{'array':[2]}"), w.toString());

        w = new StringWriter();
        gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
                new IndexMatchFilter(0),
                true, true);
        writeJsonDoc(JSON_F, JSON, gen);
        assertEquals(aposToQuotes("{'array':[1]}"), w.toString());
        assertEquals(1, gen.getMatchCount());
    }

    public void testIndexMatchWithPath2() throws Exception
    {
        StringWriter w = new StringWriter();
        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
                new IndexMatchFilter(0,1),
                true, true);
        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
        writeJsonDoc(JSON_F, JSON, gen);
        assertEquals(aposToQuotes("{'array':[1,2]}"), w.toString());
        assertEquals(2, gen.getMatchCount());
    }

    public void testWriteStartObjectWithObject() throws Exception
    {
        StringWriter w = new StringWriter();

        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
                TokenFilter.INCLUDE_ALL,
                true, true);

        String value = "val";

        gen.writeStartObject(new Object());
        gen.writeFieldName("field1");
        {
            gen.writeStartObject(value);
            gen.writeEndObject();
        }

        gen.writeFieldName("field2");
        gen.writeString("val2");

        gen.writeEndObject();
        gen.close();
        assertEquals(aposToQuotes("{'field1':{},'field2':'val2'}"), w.toString());
    }
}
