/*
 * Copyright (c) 2012-2015 Spotify AB
 *
 * 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.spotify.netty4.handler.codec.zmtp;

import com.google.common.collect.Lists;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.List;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;

import static com.spotify.netty4.handler.codec.zmtp.ZMTPVersion.ZMTP10;
import static com.spotify.netty4.handler.codec.zmtp.ZMTPWireFormats.wireFormat;
import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.util.CharsetUtil.UTF_8;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
import static org.junit.Assert.assertThat;

@RunWith(MockitoJUnitRunner.class)
public class ZMTPWriterTest {

  private final List<Object> out = Lists.newArrayList();

  @Test
  public void testOneFrame() throws Exception {
    final ZMTPWriter writer = ZMTPWriter.create(ZMTP10);
    final ByteBuf buf = Unpooled.buffer();
    writer.reset(buf);

    ByteBuf frame = writer.frame(11, false);
    assertThat(frame, is(sameInstance(buf)));

    final ByteBuf content = copiedBuffer("hello world", UTF_8);

    frame.writeBytes(content.duplicate());

    final ZMTPFramingDecoder decoder = new ZMTPFramingDecoder(wireFormat(ZMTP10), new RawDecoder());
    decoder.decode(null, buf, out);

    assertThat(out, hasSize(1));
    assertThat(out, contains((Object) singletonList(content)));
  }

  @Test
  public void testTwoFrames() throws Exception {
    final ZMTPWriter writer = ZMTPWriter.create(ZMTP10);
    final ByteBuf buf = Unpooled.buffer();
    writer.reset(buf);

    final ByteBuf f0 = copiedBuffer("hello ", UTF_8);
    final ByteBuf f1 = copiedBuffer("hello ", UTF_8);

    writer.frame(f0.readableBytes(), true).writeBytes(f0.duplicate());
    writer.frame(f1.readableBytes(), false).writeBytes(f1.duplicate());

    final ZMTPFramingDecoder decoder = new ZMTPFramingDecoder(wireFormat(ZMTP10), new RawDecoder());
    decoder.decode(null, buf, out);

    assertThat(out, hasSize(1));
    assertThat(out, contains((Object) asList(f0, f1)));
  }

  @Test
  public void testReframe() throws Exception {
    final ZMTPFramingDecoder decoder = new ZMTPFramingDecoder(wireFormat(ZMTP10), new RawDecoder());
    final ZMTPWriter writer = ZMTPWriter.create(ZMTP10);
    final ByteBuf buf = Unpooled.buffer();

    writer.reset(buf);

    // Request a frame with margin in anticipation of a larger payload...
    // ... but write a smaller payload
    final ByteBuf content = copiedBuffer("hello world", UTF_8);
    writer.frame(content.readableBytes() * 2, true).writeBytes(content.duplicate());

    // And rewrite the frame accordingly
    writer.reframe(content.readableBytes(), false);

    // Verify that the message can be parsed
    decoder.decode(null, buf, out);
    assertThat(out, hasSize(1));
    assertThat(out, contains((Object) singletonList(content)));

    // Write and verify another message
    final ByteBuf next = copiedBuffer("next", UTF_8);
    writer.frame(next.readableBytes(), false).writeBytes(next.duplicate());

    out.clear();
    decoder.decode(null, buf, out);
    assertThat(out, hasSize(1));
    assertThat(out, contains((Object) singletonList(next)));
  }


  private class RawDecoder implements ZMTPDecoder {

    private long length;

    private List<ByteBuf> frames = Lists.newArrayList();

    public void header(final ChannelHandlerContext ctx, final long length, final boolean more,
                       final List<Object> out) {
      this.length = length;
    }

    @Override
    public void content(final ChannelHandlerContext ctx, final ByteBuf data,
                        final List<Object> out) {
      if (data.readableBytes() < length) {
        return;
      }
      frames.add(data.readBytes((int) length));
    }

    @Override
    public void finish(final ChannelHandlerContext ctx, final List<Object> out) {
      out.add(frames);
      frames = Lists.newArrayList();
    }

    @Override
    public void close() {
      for (final ByteBuf frame : frames) {
        frame.release();
      }
      frames.clear();
    }
  }
}