/**
 * 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.sql.pg;

import static com.foundationdb.sql.pg.PostgresJsonCompiler.JsonResultColumn;

import com.foundationdb.qp.operator.Cursor;
import com.foundationdb.qp.row.Row;
import com.foundationdb.server.Quote;
import com.foundationdb.server.types.FormatOptions;
import com.foundationdb.server.types.TInstance;
import com.foundationdb.server.types.aksql.aktypes.AkResultSet;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.sql.types.DataTypeDescriptor;
import com.foundationdb.sql.types.TypeId;
import com.foundationdb.util.AkibanAppender;

import java.util.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class PostgresJsonOutputter extends PostgresOutputter<Row>
{
    private List<JsonResultColumn> resultColumns;
    private PostgresType valueType;
    
    public PostgresJsonOutputter(PostgresQueryContext context, 
                                 PostgresDMLStatement statement,
                                 List<JsonResultColumn> resultColumns,
                                 PostgresType valueType) {
        super(context, statement);
        this.resultColumns = resultColumns;
        this.valueType = valueType;
    }

    @Override
    public void beforeData() throws IOException {
        if (context.getServer().getOutputFormat() == PostgresServerSession.OutputFormat.JSON_WITH_META_DATA)
            outputMetaData();
    }
    
    @Override
    public void output(Row row) throws IOException {
        messenger.beginMessage(PostgresMessages.DATA_ROW_TYPE.code());
        messenger.writeShort(1);
        encoder.reset();
        outputRow(row, resultColumns);
        ByteArrayOutputStream bytes = encoder.getByteStream();
        messenger.writeInt(bytes.size());
        messenger.writeByteStream(bytes);
        messenger.sendMessage();
    }

    protected void outputRow(Row row, List<JsonResultColumn> resultColumns)
            throws IOException {
        encoder.appendString("{");
        AkibanAppender appender = encoder.getAppender();
        int ncols = resultColumns.size();
        for (int i = 0; i < ncols; i++) {
            JsonResultColumn resultColumn = resultColumns.get(i);
            encoder.appendString((i == 0) ? "\"" : ",\"");
            Quote.DOUBLE_QUOTE.append(appender, resultColumn.getName());
            encoder.appendString("\":");
            ValueSource value = row.value(i);
            TInstance columnTInstance = resultColumn.getType();
            if (columnTInstance.typeClass() instanceof AkResultSet) {
                outputNestedResultSet((Cursor)value.getObject(),
                                      resultColumn.getNestedResultColumns());
            }
            else {
                FormatOptions options = context.getServer().getFormatOptions();
                columnTInstance.formatAsJson(value, appender, options);
            }
        }
        encoder.appendString("}");
    }

    protected void outputNestedResultSet(Cursor cursor, 
                                         List<JsonResultColumn> resultColumns) 
            throws IOException {
        encoder.appendString("[");
        try {
            Row row;
            boolean first = true;
            while ((row = cursor.next()) != null) {
                if (first)
                    first = false;
                else
                    encoder.appendString(",");
                outputRow(row, resultColumns);
            }
        }
        finally {
            cursor.close();
        }
        encoder.appendString("]");
    }

    public void outputMetaData() throws IOException {
        messenger.beginMessage(PostgresMessages.DATA_ROW_TYPE.code());
        messenger.writeShort(1);
        encoder.reset();
        outputMetaData(resultColumns);
        ByteArrayOutputStream bytes = encoder.getByteStream();
        messenger.writeInt(bytes.size());
        messenger.writeByteStream(bytes);
        messenger.sendMessage();
    }

    public void outputMetaData(List<JsonResultColumn> resultColumns) throws IOException {
        AkibanAppender appender = encoder.getAppender();
        encoder.appendString("[");
        boolean first = true;
        for (JsonResultColumn resultColumn : resultColumns) {
            if (first)
                first = false;
            else
                encoder.appendString(",");
            encoder.appendString("{\"name\":\"");
            Quote.DOUBLE_QUOTE.append(appender, resultColumn.getName());
            encoder.appendString("\"");
            if (resultColumn.getNestedResultColumns() != null) {
                encoder.appendString(",\"columns\":");
                outputMetaData(resultColumn.getNestedResultColumns());
            }
            else {
                if (resultColumn.getPostgresType() != null) {
                    encoder.appendString(",\"oid\":");
                    encoder.getWriter().print(resultColumn.getPostgresType().getOid());
                }
                if (resultColumn.getSqlType() != null) {
                    DataTypeDescriptor type = resultColumn.getSqlType();
                    encoder.appendString(",\"type\":\"");
                    Quote.DOUBLE_QUOTE.append(appender, type.toString());
                    encoder.appendString("\"");
                    TypeId typeId = type.getTypeId();
                    if (typeId.isDecimalTypeId()) {
                        encoder.appendString(",\"precision\":");
                        encoder.getWriter().print(type.getPrecision());
                        encoder.appendString(",\"scale\":");
                        encoder.getWriter().print(type.getScale());
                    }
                    else if (typeId.variableLength()) {
                        encoder.appendString(",\"length\":");
                        encoder.getWriter().print(type.getMaximumWidth());
                    }
                }
            }
            encoder.appendString("}");
        }
        encoder.appendString("]");
    }
}

@@ -4211,24 +4211,66 @@
   finally %7B%0A
+            if (!cursor.isClosed()) %7B%0A    
             
@@ -4281,24 +4281,38 @@
 or.close();%0A
+            %7D%0A
         %7D%0A  

Check that cursor is not closed before closing