/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.trevni;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

class ColumnOutputBuffer {
  private ColumnFileWriter writer;
  private ColumnMetaData meta;
  private Codec codec;
  private Checksum checksum;
  private OutputBuffer buffer;
  private List<BlockDescriptor> blockDescriptors;
  private List<byte[]> blockData;
  private List<byte[]> firstValues;
  private int rowCount;
  private long size = 4;                          // room for block count

  public ColumnOutputBuffer(ColumnFileWriter writer, ColumnMetaData meta)
    throws IOException {
    this.writer = writer;
    this.meta = meta;
    this.codec = Codec.get(meta);
    this.checksum = Checksum.get(meta);
    this.buffer = new OutputBuffer();
    this.blockDescriptors = new ArrayList<BlockDescriptor>();
    this.blockData = new ArrayList<byte[]>();
    if (meta.hasIndexValues())
      this.firstValues = new ArrayList<byte[]>();
  }

  public ColumnMetaData getMeta() { return meta; }
  public OutputBuffer getBuffer() { return buffer; }

  public void startRow() throws IOException {
    if (buffer.isFull())
      flushBuffer();
  }

  public void writeLength(int length) throws IOException {
    throw new TrevniRuntimeException("Not an array column: "+meta);
  }

  public void writeValue(Object value) throws IOException {
    buffer.writeValue(value, meta.getType());
    if (meta.hasIndexValues() && rowCount == 0)
      firstValues.add(buffer.toByteArray());
  }

  public void endRow() throws IOException {
    rowCount++;
  }

  void flushBuffer() throws IOException {
    if (rowCount == 0) return;
    ByteBuffer raw = buffer.asByteBuffer();
    ByteBuffer c = codec.compress(raw);

    blockDescriptors.add(new BlockDescriptor(rowCount,
                                             raw.remaining(),
                                             c.remaining()));

    ByteBuffer data = ByteBuffer.allocate(c.remaining() + checksum.size());
    data.put(c);
    data.put(checksum.compute(raw));
    blockData.add(data.array());

    int sizeIncrement =
      (4*3)                                       // descriptor
      + (firstValues != null                      // firstValue
         ? firstValues.get(firstValues.size()-1).length
         : 0)
      + data.position();                         // data
    
    writer.incrementSize(sizeIncrement);
    size += sizeIncrement;                         

    buffer = new OutputBuffer();
    rowCount = 0;
  }

  public long size() throws IOException {
    flushBuffer();
    return size;
  }

  public void writeTo(OutputStream out) throws IOException {
    OutputBuffer header = new OutputBuffer();
    header.writeFixed32(blockDescriptors.size());
    for (int i = 0; i < blockDescriptors.size(); i++) {
      blockDescriptors.get(i).writeTo(header);
      if (meta.hasIndexValues())
        header.write(firstValues.get(i));
    }
    header.writeTo(out);

    for (byte[] data : blockData)
      out.write(data);
  }

}
