/*
 * 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.flink.table.data.binary;

import org.apache.flink.annotation.Internal;
import org.apache.flink.api.common.typeutils.TypeSerializer;
import org.apache.flink.core.memory.MemorySegment;
import org.apache.flink.table.data.RawValueData;
import org.apache.flink.util.WrappingRuntimeException;

import java.io.IOException;

/**
 * An abstract implementation fo {@link BinaryFormat} which is lazily serialized into binary or
 * lazily deserialized into Java object.
 *
 * <p>The reason why we introduce this data structure is in order to save (de)serialization in
 * nested function calls. Consider the following function call chain:
 *
 * <pre>UDF0(input) -> UDF1(result0) -> UDF2(result1) -> UDF3(result2)</pre>
 *
 * <p>Such nested calls, if the return values of UDFs are Java object format, it will result in
 * multiple conversions between Java object and binary format:
 *
 * <pre>
 * converterToBinary(UDF0(converterToJavaObject(input))) ->
 *   converterToBinary(UDF1(converterToJavaObject(result0))) ->
 *     converterToBinary(UDF2(converterToJavaObject(result1))) ->
 *       ...
 * </pre>
 *
 * <p>So we introduced {@link LazyBinaryFormat} to avoid the redundant cost, it has three forms:
 *
 * <ul>
 *   <li>Binary form
 *   <li>Java object form
 *   <li>Binary and Java object both exist
 * </ul>
 *
 * <p>It can lazy the conversions as much as possible. It will be converted into required form only
 * when it is needed.
 */
@Internal
public abstract class LazyBinaryFormat<T> implements BinaryFormat {

    T javaObject;
    BinarySection binarySection;

    public LazyBinaryFormat() {
        this(null, null);
    }

    public LazyBinaryFormat(MemorySegment[] segments, int offset, int sizeInBytes, T javaObject) {
        this(javaObject, new BinarySection(segments, offset, sizeInBytes));
    }

    public LazyBinaryFormat(MemorySegment[] segments, int offset, int sizeInBytes) {
        this(null, new BinarySection(segments, offset, sizeInBytes));
    }

    public LazyBinaryFormat(T javaObject) {
        this(javaObject, null);
    }

    public LazyBinaryFormat(T javaObject, BinarySection binarySection) {
        this.javaObject = javaObject;
        this.binarySection = binarySection;
    }

    public T getJavaObject() {
        return javaObject;
    }

    public BinarySection getBinarySection() {
        return binarySection;
    }

    /** Must be public as it is used during code generation. */
    public void setJavaObject(T javaObject) {
        this.javaObject = javaObject;
    }

    @Override
    public MemorySegment[] getSegments() {
        if (binarySection == null) {
            throw new IllegalStateException("Lazy Binary Format was not materialized");
        }
        return binarySection.segments;
    }

    @Override
    public int getOffset() {
        if (binarySection == null) {
            throw new IllegalStateException("Lazy Binary Format was not materialized");
        }
        return binarySection.offset;
    }

    @Override
    public int getSizeInBytes() {
        if (binarySection == null) {
            throw new IllegalStateException("Lazy Binary Format was not materialized");
        }
        return binarySection.sizeInBytes;
    }

    /** Ensure we have materialized binary format. */
    public final void ensureMaterialized(TypeSerializer<T> serializer) {
        if (binarySection == null) {
            try {
                this.binarySection = materialize(serializer);
            } catch (IOException e) {
                throw new WrappingRuntimeException(e);
            }
        }
    }

    /**
     * Materialize java object to binary format. Inherited classes need to hold the information they
     * need. (For example, {@link RawValueData} needs javaObjectSerializer).
     */
    protected abstract BinarySection materialize(TypeSerializer<T> serializer) throws IOException;
}
