/*
 *
 *  Copyright 2013 Netflix, 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.netflix.zeno.examples;

import com.netflix.zeno.examples.pojos.A;
import com.netflix.zeno.examples.pojos.B;
import com.netflix.zeno.examples.pojos.C;
import com.netflix.zeno.examples.serializers.ExampleSerializerFactory;
import com.netflix.zeno.fastblob.FastBlobStateEngine;
import com.netflix.zeno.fastblob.io.FastBlobReader;
import com.netflix.zeno.fastblob.io.FastBlobWriter;
import com.netflix.zeno.fastblob.state.FastBlobTypeDeserializationState;

import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.junit.Test;

/**
 * An example detailing the basic serialization flow.  Follow along in the Zeno documentation
 * in the section <a href="https://github.com/Netflix/zeno/wiki/Transporting-data">transporting data</a>.
 * 
 * @author dkoszewnik
 *
 */
public class BasicSerializationExample {

    private FastBlobStateEngine stateEngine;

    @Test
    public void basicSerializationCycle() {
        /// First we create a state engine, we need to tell it about our data model by
        /// passing it a serializer factory which creates our top-level serializers.
        stateEngine = new FastBlobStateEngine(new ExampleSerializerFactory());

        /// For this example, we're just storing the serialized data in memory.
        byte snapshot[] = createSnapshot();
        byte delta[] = createDelta();

        /// Now we can pretend to be the client, and deserialize the data into a separate state engine.
        FastBlobStateEngine clientStateEngine = deserializeLatestData(snapshot, delta);

        /// We can grab the data for any class.  Here, we grab the values for class A.
        FastBlobTypeDeserializationState<A> aState = clientStateEngine.getTypeDeserializationState("A");

        /// the following will loop through all values after loading the snapshot
        /// and subsequently applying the delta.  It will print out "1" followed by "3".
        System.out.println("All As: ");
        for(A deserializedA : aState){
            System.out.println(deserializedA.getIntValue());
        }

        /// As another example, we can grab the values for class B.
        FastBlobTypeDeserializationState<B> bState = clientStateEngine.getTypeDeserializationState("B");

        /// Even though we didn't directly add the Bs, they were added because we added objects which
        /// referenced them.  Here, we iterate over all of the Bs after applying the delta.
        /// This will print out "1", "2", "3", "4", "6"
        System.out.println("All Bs: ");
        for(B deserializedB : bState) {
            System.out.println(deserializedB.getBInt());
        }
    }

    public FastBlobStateEngine deserializeLatestData(byte snapshot[], byte delta[]) {
        /// now we are on the client.  We need to create a state engine, and again
        /// tell it about our data model.
        FastBlobStateEngine stateEngine = new FastBlobStateEngine(new ExampleSerializerFactory());

        /// we need to create a FastBlobReader, which is responsible for reading
        /// serialized blobs.
        FastBlobReader reader = new FastBlobReader(stateEngine);

        /// get a stream from the snapshot file location
        ByteArrayInputStream snapshotStream = new ByteArrayInputStream(snapshot);
        /// get a stream from the delta file location
        ByteArrayInputStream deltaStream = new ByteArrayInputStream(delta);


        try {
            /// first read the snapshot
            reader.readSnapshot(snapshotStream);
            /// then apply the delta
            reader.readDelta(deltaStream);
        } catch (IOException e) {
            /// either of these methods throws an exception if the FastBlobReader
            /// is unable to read from the provided stream.
        } finally {
            /// it is your responsibility to close the streams.  The FastBlobReader will not do this.
            try {
                snapshotStream.close();
                deltaStream.close();
            } catch (IOException ignore) { }
        }

        return stateEngine;
    }

    public byte[] createSnapshot() {
        /// We add each of our object instances to the state engine.
        /// This operation is thread safe and can be called from multiple processing threads.
        stateEngine.add("A", getExampleA1());
        stateEngine.add("A", getExampleA2());

        /// The following lets the state engine know that we have finished adding all of our
        /// objects to the state.
        stateEngine.prepareForWrite();

        /// Create a writer, which will be responsible for creating snapshot and/or delta blobs.
        FastBlobWriter writer = new FastBlobWriter(stateEngine);

        /// Create an output stream to somewhere.  This can be to a local file on disk
        /// or directly to the blob destination.  The VMS team writes this data directly
        /// to disk, and then spawns a new thread to upload the data to S3.
        /// We need to pass a DataOutputStream.  Remember that DataOutputStream is not buffered,
        /// so if you write to a FileOutputStream or other non-buffered source, you likely want to
        /// make the DataOutputStream wrap a BufferedOutputStream
        /// (e.g. new DataOutputStream(new BufferedOutputStream(new FileOutputStream(destinationFile))))
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream outputStream = new DataOutputStream(baos);

        try {
            /// write the snapshot to the output stream.
            writer.writeSnapshot(outputStream);
        } catch(IOException e) {
            /// the FastBlobWriter throws an IOException if it is
            /// unable to write to the provided OutputStream
        } finally {
            /// it is your responsibility to close the stream.  The FastBlobWriter will not do this.
            try {
                outputStream.close();
            } catch(IOException ignore) { }
        }

        byte snapshot[] = baos.toByteArray();

        return snapshot;
    }

    public byte[] createDelta() {
        /// The following call informs the state engine that we have finished writing
        /// all of our snapshot / delta files, and we are ready to begin adding fresh
        /// object instances for the next cycle.
        stateEngine.prepareForNextCycle();

        // Again, we add each of our object instances to the state engine.
        // This operation is still thread safe and can be called from multiple processing threads.
        stateEngine.add("A", getExampleA1());
        stateEngine.add("A", getExampleA2Prime());

        /// We must again let the state engine know that we have finished adding all of our
        /// objects to the state.
        stateEngine.prepareForWrite();

        /// Create a writer, which will be responsible for creating snapshot and/or delta blobs.
        FastBlobWriter writer = new FastBlobWriter(stateEngine);

        /// Again create an output stream
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream outputStream = new DataOutputStream(baos);

        try {
            /// This time write the delta file.
            writer.writeDelta(outputStream);
        } catch(IOException e) {
            /// thrown if the FastBlobWriter was unable to write to the provided stream.
        } finally {
            try {
                outputStream.close();
            } catch(IOException ignore){ }
        }

        byte delta[] = baos.toByteArray();

        return delta;
    }


    public A getExampleA1() {
        B b1 = new B(1, "one");
        B b2 = new B(2, "two");

        List<B> bList = new ArrayList<B>();
        bList.add(b1);
        bList.add(b2);

        C c = new C(Long.MAX_VALUE, new byte[] { 1, 2, 3, 4, 5 });

        A a = new A(bList, c, 1);

        return a;
    }

    public A getExampleA2() {
        B b3 = new B(3, "three");
        B b4 = new B(4, "four");
        B b5 = new B(5, "five");

        List<B> bList = new ArrayList<B>();
        bList.add(b3);
        bList.add(b4);
        bList.add(b5);

        C c = new C(Long.MAX_VALUE, new byte[] { 10, 9, 8, 7, 6 });

        A a = new A(bList, c, 2);

        return a;
    }

    public A getExampleA2Prime() {
        B c3 = new B(3, "three");
        B c4 = new B(4, "four");
        B c6 = new B(6, "six");

        List<B> cList = new ArrayList<B>();
        cList.add(c3);
        cList.add(c4);
        cList.add(c6);

        C d = new C(Long.MAX_VALUE, new byte[] { 10, 9, 8, 7, 6 });

        A a = new A(cList, d, 2);

        return a;
    }

}