package perf;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;

abstract class ObjectWriterTestBase<T1,T2>
{
    protected int hash;

    protected abstract int targetSizeMegs();

    protected void test(ObjectMapper mapper,
            String desc1, T1 inputValue1, Class<? extends T1> inputClass1,
            String desc2, T2 inputValue2, Class<? extends T2> inputClass2)
        throws Exception
    {
        final int REPS;
        {
            final byte[] input1 = mapper.writeValueAsBytes(inputValue1);
            final byte[] input2 = mapper.writeValueAsBytes(inputValue2);
            
            // Let's try to guestimate suitable size, N megs of output
            REPS = (int) ((double) (targetSizeMegs() * 1000 * 1000) / (double) input1.length);
            System.out.printf("Read %d bytes to bind (%d as array); will do %d repetitions\n",
                    input1.length, input2.length, REPS);
        }

        final ObjectWriter writer0 = mapper.writer().with(SerializationFeature.EAGER_SERIALIZER_FETCH);
        final ObjectWriter writer1 = writer0.forType(inputClass1);
        final ObjectWriter writer2 = writer0.forType(inputClass2);
        
        int i = 0;
        int roundsDone = 0;
        final int TYPES = 2;

        // Skip first 5 seconds
        long startMeasure = System.currentTimeMillis() + 5000L;
        System.out.print("Warming up");
        
        final double[] timesMsec = new double[TYPES];
        
        while (true) {
            final int round = (i % TYPES);
            final boolean lf = (++i % TYPES) == 0;

            String msg;

            ObjectWriter writer;
            Object value;
            switch (round) {
            case 0:
                msg = desc1;
                writer = writer1;
                value = inputValue1;
                break;
            case 1:
                msg = desc2;
                writer = writer2;
                value = inputValue2;
                break;
            default:
                throw new Error();
            }

            double msecs = testSer(REPS, value, writer);

            // skip first N seconds to let results stabilize
            if (startMeasure > 0L) {
                if ((round != 0) || (System.currentTimeMillis() < startMeasure)) {
                    System.out.print(".");
                    continue;
                }
                startMeasure = 0L;
                System.out.println();
                System.out.println("Starting measurements...");
                Thread.sleep(250L);
                System.out.println();
            }

            timesMsec[round] += msecs;

            if ((i % 17) == 0) {
                System.out.println("[GC]");
                Thread.sleep(100L);
                System.gc();
                Thread.sleep(100L);
            }
            
            System.out.printf("Test '%s' [hash: 0x%s] -> %.1f msecs\n", msg, hash, msecs);
            Thread.sleep(50L);
            if (!lf) {
                continue;
            }
            
            if ((++roundsDone % 3) == 0) {
                double den = (double) roundsDone;
                System.out.printf("Averages after %d rounds (%s/%s): %.1f / %.1f msecs\n",
                        roundsDone, desc1, desc2,
                        timesMsec[0] / den, timesMsec[1] / den);
            }
            System.out.println();
        }
    }

    protected double testSer(int REPS, Object value, ObjectWriter writer) throws Exception
    {
        final NopOutputStream out = new NopOutputStream();
        long start = System.nanoTime();
        while (--REPS >= 0) {
            writer.writeValue(out, value);
        }
        hash = out.size();
        long nanos = System.nanoTime() - start;
        out.close();
        return _msecsFromNanos(nanos);
    }

    protected final double _msecsFromNanos(long nanos) {
        return (nanos / 1000000.0);
    }
}
