/**
 * Copyright 2015 Palantir Technologies
 *
 * Licensed under the BSD-3 License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://opensource.org/licenses/BSD-3-Clause
 *
 * 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.palantir.atlasdb.table.description.render;

import static com.palantir.atlasdb.table.description.render.ComponentRenderers.TypeName;
import static com.palantir.atlasdb.table.description.render.ComponentRenderers.varName;

import com.palantir.atlasdb.protos.generated.TableMetadataPersistence.ValueByteOrder;
import com.palantir.atlasdb.table.description.ColumnValueDescription;
import com.palantir.atlasdb.table.description.DynamicColumnDescription;
import com.palantir.atlasdb.table.description.NameComponentDescription;
import com.palantir.atlasdb.table.description.NameMetadataDescription;

public class DynamicColumnValueRenderer extends Renderer {
    private final NameMetadataDescription col;
    private final ColumnValueDescription val;
    private final String Column;
    private final String ColumnValue;
    private final String Value;

    public DynamicColumnValueRenderer(Renderer parent, String tableName, DynamicColumnDescription dynamicCol) {
        super(parent);
        this.col = dynamicCol.getColumnNameDesc();
        this.val = dynamicCol.getValue();
        this.Column = tableName + "Column";
        this.ColumnValue = tableName + "ColumnValue";
        this.Value = val.getJavaObjectTypeName();
    }

    @Override
    protected void run() {
        javaDoc();
        line("public static final class ", ColumnValue, " implements ColumnValue<", Value, "> {"); {
            fields();
            line();
            staticFactories();
            line();
            constructors();
            line();
            getColumnName();
            line();
            getValue();
            line();
            persistColumnName();
            line();
            persistValue();
            line();
            hydrateValue();
            line();
            getColumnNameFun();
            line();
            getValueFun();
        } line("}");
    }

    private void javaDoc() {
        line("/**");
        line(" * <pre>");
        line(" * Column name description {", "");
        for (NameComponentDescription comp : col.getRowParts()) {
            boolean descending = comp.getOrder() == ValueByteOrder.DESCENDING;
            line(" *   {@literal ", descending ? "@Descending " : "",  TypeName(comp), " ", varName(comp), "};");
        }
        line(" * }", "");
        line(" * Column value description {", "");
        line(" *   type: ", val.getJavaObjectTypeName(), ";");
        if (val.getProtoDescriptor() != null) {
            String protoDescription = val.getProtoDescriptor().toProto().toString();
            for (String line : protoDescription.split("\n")) {
                line(" *   ", line, "");
            }
        }
        line(" * }", "");
        line(" * </pre>");
        line(" */");
    }

    private void fields() {
        line("private final ", Column, " columnName;");
        line("private final ", Value, " value;");
    }

    private void staticFactories() {
        line("public static ", ColumnValue, " of(", Column, " columnName, ", Value, " value) {"); {
            line("return new ", ColumnValue, "(columnName, value);");
        } line("}");
    }

    private void constructors() {
        line("private ", ColumnValue, "(", Column, " columnName, ", Value, " value) {"); {
            line("this.columnName = columnName;");
            line("this.value = value;");
        } line("}");
    }

    private void getColumnName() {
        line("public ", Column, " getColumnName() {"); {
            line("return columnName;");
        } line("}");
    }

    private void getValue() {
        line("@Override");
        line("public ", Value, " getValue() {"); {
            line("return value;");
        } line("}");
    }

    private void persistColumnName() {
        line("@Override");
        line("public byte[] persistColumnName() {"); {
            line("return columnName.persistToBytes();");
        } line("}");
    }

    private void persistValue() {
        line("@Override");
        line("public byte[] persistValue() {"); {
            switch (val.getFormat()) {
            case PERSISTABLE:
                line("byte[] bytes = value.persistToBytes();");
                break;
            case PROTO:
                line("byte[] bytes = value.toByteArray();");
                break;
            case PERSISTER:
                line("byte[] bytes = ", val.getPersistCode("value"), ";");
                break;
            case VALUE_TYPE:
                line("byte[] bytes = ", val.getValueType().getPersistCode("value"), ";");
                break;
            default:
                throw new UnsupportedOperationException("Unsupported value type: " + val.getFormat());
            }
            line("return CompressionUtils.compress(bytes, Compression.", val.getCompression().name(), ");");
        } line("}");
    }

    private void hydrateValue() {
        line("public static ", Value, " hydrateValue(byte[] bytes) {"); {
            line("bytes = CompressionUtils.decompress(bytes, Compression.", val.getCompression().name(), ");");
            switch (val.getFormat()) {
            case PERSISTABLE:
                line("return ", Value, ".BYTES_HYDRATOR.hydrateFromBytes(bytes);");
                break;
            case PROTO:
                line("try {"); {
                    line("return ", Value, ".parseFrom(bytes);");
                } line("} catch (InvalidProtocolBufferException e) {"); {
                    line("throw Throwables.throwUncheckedException(e);");
                } line("}");
                break;
            case PERSISTER:
                line("return of(", val.getHydrateCode("bytes"), ");");
                break;
            case VALUE_TYPE:
                line("return ", val.getValueType().getHydrateCode("bytes", "0"), ";");
                break;
            default:
                throw new UnsupportedOperationException("Unsupported value type: " + val.getFormat());
            }
        } line("}");
    }

    private void getColumnNameFun() {
        line("public static Function<", ColumnValue, ", ", Column, "> getColumnNameFun() {"); {
            line("return new Function<", ColumnValue, ", ", Column, ">() {"); {
                line("@Override");
                line("public ", Column, " apply(", ColumnValue, " columnValue) {"); {
                    line("return columnValue.getColumnName();");
                } line("}");
            } line("};");
        } line("}");
    }

    private void getValueFun() {
        line("public static Function<", ColumnValue, ", ", Value, "> getValueFun() {"); {
            line("return new Function<", ColumnValue, ", ", Value, ">() {"); {
                line("@Override");
                line("public ", Value, " apply(", ColumnValue, " columnValue) {"); {
                    line("return columnValue.getValue();");
                } line("}");
            } line("};");
        } line("}");
    }
}

@@ -6207,19 +6207,16 @@
 %22return 
-of(
 %22, val.g
@@ -6236,25 +6236,24 @@
 (%22bytes%22), %22
-)
 ;%22);%0A       

Fix codegen for dynamic columns with PERSISTERs