package me.prettyprint.cassandra.service.template;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;

import me.prettyprint.cassandra.model.ExecutionResult;
import me.prettyprint.cassandra.model.HColumnImpl;
import me.prettyprint.cassandra.model.HSuperColumnImpl;
import me.prettyprint.cassandra.serializers.BooleanSerializer;
import me.prettyprint.cassandra.serializers.ByteBufferSerializer;
import me.prettyprint.cassandra.serializers.BytesArraySerializer;
import me.prettyprint.cassandra.serializers.DateSerializer;
import me.prettyprint.cassandra.serializers.IntegerSerializer;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.serializers.UUIDSerializer;
import me.prettyprint.hector.api.Serializer;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.beans.HSuperColumn;
import me.prettyprint.hector.api.factory.HFactory;

import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Provides access to the current row of data during super column queries. 
 * 
 * @author zznate
 * @param <N> the super column's sub column name type
 */
public class SuperCfResultWrapper<K,SN,N> extends AbstractResultWrapper<K,N> implements SuperCfResult<K,SN,N> {
  private static final Logger log = LoggerFactory.getLogger(SuperCfResultWrapper.class);
  
  private Map<SN,Map<N,HColumn<N,ByteBuffer>>> columns = new LinkedHashMap<SN,Map<N,HColumn<N,ByteBuffer>>>();
  private Iterator<Map.Entry<ByteBuffer, List<ColumnOrSuperColumn>>> rows;
  private Map.Entry<ByteBuffer, List<ColumnOrSuperColumn>> entry;
  private ExecutionResult<Map<ByteBuffer,List<ColumnOrSuperColumn>>> executionResult;
  private List<SN> superColumns;
  private Map<N,HColumn<N,ByteBuffer>> subColumns = new LinkedHashMap<N,HColumn<N,ByteBuffer>>();
  private SN currentSuperColumn;
  private boolean hasEntries;

  private Serializer<SN> sNameSerializer;
  
  public SuperCfResultWrapper(Serializer<K> keySerializer,
      Serializer<SN> sNameSerializer,
      Serializer<N> subSerializer,
      ExecutionResult<Map<ByteBuffer, List<ColumnOrSuperColumn>>> executionResult) {
    super(keySerializer, subSerializer, executionResult);
    this.sNameSerializer = sNameSerializer;
    this.rows = executionResult.get().entrySet().iterator();    
    next();
    hasEntries = getSuperColumns() != null && getSuperColumns().size() > 0;
  }    

  @Override
  public SuperCfResult<K, SN, N> next() {
    if ( !hasNext() ) {
      throw new NoSuchElementException("No more rows left on this HColumnFamily");
    }
    entry = rows.next();    
    log.debug("found entry {} with value {}", getKey(), entry.getValue());
    applyToRow(entry.getValue());
    return this;
  }
  
  private void applyToRow(List<ColumnOrSuperColumn> cosclist) {
    superColumns = new ArrayList<SN>(cosclist.size());
    for (Iterator<ColumnOrSuperColumn> iterator = cosclist.iterator(); iterator.hasNext();) {
      ColumnOrSuperColumn cosc = iterator.next();
      SN sColName = sNameSerializer.fromByteBuffer(cosc.super_column.name);
      log.debug("cosc {}", cosc.super_column);
       
      superColumns.add(sColName);
      Iterator<Column> tcolumns = cosc.getSuper_column().getColumnsIterator();
      Map<N,HColumn<N,ByteBuffer>> subColMap = new LinkedHashMap<N, HColumn<N,ByteBuffer>>();
      while ( tcolumns.hasNext() ) {
        Column col = tcolumns.next();
        subColMap.put(columnNameSerializer.fromByteBuffer(col.name), new HColumnImpl<N, ByteBuffer>(col, columnNameSerializer, ByteBufferSerializer.get()));
      }
      columns.put(sColName, subColMap);
    }
  }
  
  
  public List<SN> getSuperColumns() {
    return superColumns;
  }
  
  
  @Override
  public ByteBuffer getColumnValue(N columnName) {
    HColumn<N,ByteBuffer> col = getColumn( columnName );
    return col != null ? col.getValue() : null; 
  }


  @Override
  public K getKey() {
    return keySerializer.fromByteBuffer(entry.getKey().duplicate());
  }


  @Override
  public HColumn<N, ByteBuffer> getColumn(N columnName) {
    return subColumns == null ? null : subColumns.get( columnName );
  }


  @Override
  public Collection<N> getColumnNames() {
    return subColumns != null ? subColumns.keySet() : new ArrayList<N>();
  }


  @Override
  public boolean hasNext() {
    return rows.hasNext();
  }

  @Override
  public void remove() {
    rows.remove();    
  }

  private <V> V extractType(SN sColumnName, N columnName, Serializer<V> valueSerializer) {
    Map<N, HColumn<N, ByteBuffer>> map = columns.get(sColumnName);
    if ( map != null && map.get(columnName) != null )
      return valueSerializer.fromByteBuffer(map.get(columnName).getValue());
    return null;
  }
  
  @Override
  public Boolean getBoolean(SN sColumnName, N columnName) {    
    return extractType(sColumnName, columnName, BooleanSerializer.get());
  }


  @Override
  public byte[] getByteArray(SN sColumnName, N columnName) {
    return extractType(sColumnName, columnName, BytesArraySerializer.get());
  }


  @Override
  public Date getDate(SN sColumnName, N columnName) {
    return extractType(sColumnName, columnName, DateSerializer.get());
  }


  @Override
  public Integer getInteger(SN sColumnName, N columnName) {
    return extractType(sColumnName, columnName, IntegerSerializer.get());
  }


  @Override
  public Long getLong(SN sColumnName, N columnName) {
    return extractType(sColumnName, columnName, LongSerializer.get());
  }


  @Override
  public String getString(SN sColumnName, N columnName) {
    return extractType(sColumnName, columnName, StringSerializer.get());
  }



  @Override
  public UUID getUUID(SN sColumnName, N columnName) {
    return extractType(sColumnName, columnName, UUIDSerializer.get());
  }

  @Override
  public ByteBuffer getByteBuffer(SN sColumnName, N columnName) {
    return extractType(sColumnName, columnName, ByteBufferSerializer.get());
  }

  
  @Override
  public SN getActiveSuperColumn() {
    return currentSuperColumn;
  }
    

  @Override
  public HSuperColumn<SN, N, ByteBuffer> getSuperColumn(SN sColumnName) {
    Map<N, HColumn<N, ByteBuffer>> subCols = columns.get(sColumnName);
    HSuperColumnImpl<SN, N, ByteBuffer> scol = new HSuperColumnImpl<SN, N, ByteBuffer>(sColumnName, new ArrayList<HColumn<N,ByteBuffer>>(subCols.values()), 
        HFactory.createClock(), sNameSerializer, columnNameSerializer, ByteBufferSerializer.get());
    return scol;
  }

  @Override
  public void applySuperColumn(SN sColumnName) {
    this.currentSuperColumn = sColumnName;
    this.subColumns = columns.get(currentSuperColumn);

  }

  @Override
  public boolean hasResults() {
    return hasEntries;
  }
  
  

}

@@ -3427,16 +3427,28 @@
 umn.name
+.duplicate()
 );%0A     
@@ -3845,16 +3845,28 @@
 col.name
+.duplicate()
 ), new H

use duplicate on undrelying column to avoid advancing bytebuffer. Addresses issue #381