/**
 * Copyright (C) 2009-2013 FoundationDB, LLC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.foundationdb.server.api.dml.scan;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import com.foundationdb.server.rowdata.RowData;
import com.foundationdb.server.rowdata.RowDataExtractor;
import com.foundationdb.server.rowdata.RowDef;
import com.foundationdb.server.api.dml.ColumnSelector;
import com.foundationdb.server.api.dml.SetColumnSelector;
import com.foundationdb.util.ArgumentValidation;

public class NiceRow extends NewRow {
    private final static int INITIAL_ROW_DATA_SIZE = RowData.CREATE_ROW_INITIAL_SIZE;

    private final Map<Integer,Object> fields;
    private final int tableId;

    public NiceRow(int tableId, RowDef rowDef)
    {
        super(rowDef);
        ArgumentValidation.notNull("tableId", tableId);
        fields = new TreeMap<>();
        this.tableId = tableId;
    }

    public NiceRow(NewRow copyFrom) {
        this(copyFrom.getTableId(), copyFrom.getRowDef());
        fields.putAll( copyFrom.getFields() );
    }

    @Override
    public Object put(int index, Object object) {
        return fields.put(index, object);
    }

    @Override
    public int getTableId() {
        return tableId;
    }

    @Override
    public Object get(int column) {
        return fields.get(column);
    }

    @Override
    public boolean hasValue(int columnId) {
        return fields.containsKey(columnId);
    }

    @Override
    public Object remove(int columnId) {
        return fields.remove(columnId);
    }

    @Override
    public Map<Integer,Object> getFields() {
        return fields;
    }

    public static NewRow fromRowData(RowData origData, RowDef rowDef)
    {
        Set<Integer> activeColumns = new HashSet<>();
        for(int fieldIndex=0, fieldsCount=rowDef.getFieldCount(); fieldIndex < fieldsCount; ++fieldIndex) {
            final long location = rowDef.fieldLocation(origData, fieldIndex);
            // Null != not specified. NewRow, NiceRow, RowData all need the concept of specified vs not-specified
            // fields.
            if (true) { // location != 0) {
                activeColumns.add( fieldIndex );
            }
        }

        NewRow retval = new NiceRow(rowDef.getRowDefId(), rowDef);
        RowDataExtractor extractor = new RowDataExtractor(origData, rowDef);
        for (int pos : activeColumns) {
            Object value = extractor.get(rowDef.getFieldDef(pos));
            Object old = retval.put(pos, value);
            assert old == null : String.format("put(%s, %s) --> %s", pos, value, old);
        }

        return retval;
    }

    @Override
    public RowData toRowData() {
        final Object[] objects = new Object[ rowDef.getFieldCount() ];
        for (Map.Entry<Integer,Object> entry : fields.entrySet()) {
            objects[ entry.getKey() ] = entry.getValue();
        }
        if (rowDef.table().getPrimaryKeyIncludingInternal().isAkibanPK() && fields.size() == rowDef.getFieldCount() - 1) {
            // Last column will be filled in by the row id counter. Need a non-null value for now.
            objects[fields.size()] = -1;
        }
        final RowData retval = new RowData(new byte[INITIAL_ROW_DATA_SIZE]);
        retval.createRow(rowDef, objects, true);

        return retval;
    }

    @Override
    public ColumnSelector getActiveColumns() {
        return new SetColumnSelector(fields.keySet());
    }

    @Override
    public boolean isColumnNull(int columnId) {
        return get(columnId) == null;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder().append("[{");
        sb.append( rowDef.table().getName().getTableName() );
        if (fields.isEmpty()) {
            return sb.append("} <empty>]").toString();
        }
        sb.append("} ");
        int nextExpectedPos = 0;
        for (Map.Entry<Integer,Object> entry : fields.entrySet()) {
            final int pos = entry.getKey();
            if (pos != nextExpectedPos) {
                sb.append("... ");
            }
            
            final Object value = entry.getValue();
            sb.append('(').append(entry.getKey()).append(": ");
            if (value != null) {
                sb.append(value.getClass().getSimpleName()).append(' ');
            }
            sb.append(value).append(") ");

            nextExpectedPos = pos + 1;
        }
        sb.setCharAt(sb.length() - 1, ']');
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        NiceRow niceRow = (NiceRow) o;

        return fields.equals(niceRow.fields) && (tableId == niceRow.tableId);

    }

    @Override
    public int hashCode() {
        int result = fields != null ? fields.hashCode() : 0;
        result = 31 * result + tableId;
        return result;
    }
}

@@ -3573,281 +3573,8 @@
   %7D%0A
-        if (rowDef.table().getPrimaryKeyIncludingInternal().isAkibanPK() && fields.size() == rowDef.getFieldCount() - 1) %7B%0A            // Last column will be filled in by the row id counter. Need a non-null value for now.%0A            objects%5Bfields.size()%5D = -1;%0A        %7D%0A
     
@@ -3695,17 +3695,16 @@
  true);%0A
-%0A
         

Remove the extra setting of the HiddenPK in NiceRow#toRowData() as the order of the hanldling has changed. NiceRow is passed to AbstractStore, the Hidden PK is added to Nice Row, then converted to RowData for writing.