/*
 * 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.ignite.internal.table;

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.BitSet;
import java.util.Iterator;
import java.util.UUID;
import org.apache.ignite.binary.BinaryObject;
import org.apache.ignite.internal.schema.SchemaDescriptor;
import org.apache.ignite.internal.schema.row.Row;
import org.apache.ignite.table.Tuple;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * Adapter provides Tuple access methods and mutability for a Row.
 *
 * <p>Once adapter get created, it delegates all data access methods to the {@link #row}.
 *
 * <p>Mutability. Underlying Row is immutable serialized object and can't be mutated directly. Because of this fact, first call of {@link
 * #set(String, Object)} method implies the Row conversion to a Tuple.
 *
 * <p>After the first mutation the {@link #tuple} becomes a full copy of {@link #row}, all data access methods delegate to a {@link #tuple}
 * ,and the {@link #row} one is no longer useful. The adapter acts as simple schema-less tuple {@link Tuple} and {@link #schema()} return
 * null.
 *
 * <p>Serialization. Row access methods implicitly require a context (schema) for a binary data reading, The context may be huge comparing
 * to a row data, and its serialization is unwanted. So, Row firstly is converted to Tuple.
 *
 * <p>Because of after that the adapter will act as underlying tuple {@link Tuple}, the adapter will be substituted unconditionally with
 * the tuple itself during deserialization.
 *
 * @see Tuple
 * @see #unmarshalRow()
 * @see #writeReplace()
 */
public class MutableRowTupleAdapter extends AbstractRowTupleAdapter implements Serializable {
    // Default constructor and serialVersionUID not needed because, actually,
    // this object never get serialized, it's unconditionally substituted during serialization.

    /** Tuple with overwritten data. */
    protected Tuple tuple;

    /**
     * Creates mutable wrapper over row.
     *
     * @param row Row.
     */
    public MutableRowTupleAdapter(@NotNull Row row) {
        super(row);
    }

    /** {@inheritDoc} */
    @Override
    public @Nullable SchemaDescriptor schema() {
        return tuple != null ? null : super.schema();
    }

    /** {@inheritDoc} */
    @Override
    public int columnCount() {
        return tuple != null ? tuple.columnCount() : super.columnCount();
    }

    /** {@inheritDoc} */
    @Override
    public String columnName(int columnIndex) {
        return tuple != null ? tuple.columnName(columnIndex) : super.columnName(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public int columnIndex(@NotNull String columnName) {
        return tuple != null ? tuple.columnIndex(columnName) : super.columnIndex(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public <T> T valueOrDefault(@NotNull String columnName, T defaultValue) {
        return tuple != null ? tuple.valueOrDefault(columnName, defaultValue) : super.valueOrDefault(columnName, defaultValue);
    }

    /** {@inheritDoc} */
    @Override
    public <T> T value(@NotNull String columnName) {
        return tuple != null ? tuple.value(columnName) : super.value(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public <T> T value(int columnIndex) {
        return tuple != null ? tuple.value(columnIndex) : super.value(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public BinaryObject binaryObjectValue(@NotNull String columnName) {
        return tuple != null ? tuple.binaryObjectValue(columnName) : super.binaryObjectValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public BinaryObject binaryObjectValue(int columnIndex) {
        return super.binaryObjectValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public byte byteValue(@NotNull String columnName) {
        return tuple != null ? tuple.byteValue(columnName) : super.byteValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public byte byteValue(int columnIndex) {
        return tuple != null ? tuple.byteValue(columnIndex) : super.byteValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public short shortValue(@NotNull String columnName) {
        return tuple != null ? tuple.shortValue(columnName) : super.shortValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public short shortValue(int columnIndex) {
        return tuple != null ? tuple.shortValue(columnIndex) : super.shortValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public int intValue(@NotNull String columnName) {
        return tuple != null ? tuple.intValue(columnName) : super.intValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public int intValue(int columnIndex) {
        return tuple != null ? tuple.intValue(columnIndex) : super.intValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public long longValue(@NotNull String columnName) {
        return tuple != null ? tuple.longValue(columnName) : super.longValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public long longValue(int columnIndex) {
        return tuple != null ? tuple.longValue(columnIndex) : super.longValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public float floatValue(@NotNull String columnName) {
        return tuple != null ? tuple.floatValue(columnName) : super.floatValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public float floatValue(int columnIndex) {
        return tuple != null ? tuple.floatValue(columnIndex) : super.floatValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public double doubleValue(@NotNull String columnName) {
        return tuple != null ? tuple.doubleValue(columnName) : super.doubleValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public double doubleValue(int columnIndex) {
        return tuple != null ? tuple.doubleValue(columnIndex) : super.doubleValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public String stringValue(@NotNull String columnName) {
        return tuple != null ? tuple.stringValue(columnName) : super.stringValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public String stringValue(int columnIndex) {
        return tuple != null ? tuple.stringValue(columnIndex) : super.stringValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public UUID uuidValue(@NotNull String columnName) {
        return tuple != null ? tuple.uuidValue(columnName) : super.uuidValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public UUID uuidValue(int columnIndex) {
        return tuple != null ? tuple.uuidValue(columnIndex) : super.uuidValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public BitSet bitmaskValue(@NotNull String columnName) {
        return tuple != null ? tuple.bitmaskValue(columnName) : super.bitmaskValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public BitSet bitmaskValue(int columnIndex) {
        return tuple != null ? tuple.bitmaskValue(columnIndex) : super.bitmaskValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public LocalDate dateValue(String columnName) {
        return tuple != null ? tuple.dateValue(columnName) : super.dateValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public LocalDate dateValue(int columnIndex) {
        return tuple != null ? tuple.dateValue(columnIndex) : super.dateValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public LocalTime timeValue(String columnName) {
        return tuple != null ? tuple.timeValue(columnName) : super.timeValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public LocalTime timeValue(int columnIndex) {
        return tuple != null ? tuple.timeValue(columnIndex) : super.timeValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public LocalDateTime datetimeValue(String columnName) {
        return tuple != null ? tuple.datetimeValue(columnName) : super.datetimeValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public LocalDateTime datetimeValue(int columnIndex) {
        return tuple != null ? tuple.datetimeValue(columnIndex) : super.datetimeValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public Instant timestampValue(String columnName) {
        return tuple != null ? tuple.timestampValue(columnName) : super.timestampValue(columnName);
    }

    /** {@inheritDoc} */
    @Override
    public Instant timestampValue(int columnIndex) {
        return tuple != null ? tuple.timestampValue(columnIndex) : super.timestampValue(columnIndex);
    }

    /** {@inheritDoc} */
    @Override
    public @NotNull Iterator<Object> iterator() {
        return tuple != null ? tuple.iterator() : super.iterator();
    }

    /** {@inheritDoc} */
    @Override
    public Tuple set(@NotNull String columnName, Object value) {
        unmarshalRow();

        tuple.set(columnName, value);

        return this;
    }

    /**
     * Converts immutable row to mutable tuple.
     */
    private void unmarshalRow() {
        if (tuple == null) {
            tuple = Tuple.create(this);

            row = null;
        }
    }

    /**
     * Overrides default serialization flow. Serialized {@code tuple} instead of {@code this}, as wrapper make no sense after tuple is fully
     * materialized.
     *
     * <p>Note: The method has protected modifier and subclass access to this method follows java accessibility rules. Thus, class
     * successors may obtain similar behaviour.
     *
     * @return Tuple to serialize.
     * @throws ObjectStreamException If failed.
     */
    protected Object writeReplace() throws ObjectStreamException {
        unmarshalRow();

        return tuple;
    }
}
