/*
 *  Copyright 2018 Data Artisans GmbH
 *
 *  Licensed 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 com.dataartisans.streamingledger.runtime.serial;

import org.apache.flink.api.common.state.MapState;
import org.apache.flink.api.java.functions.KeySelector;

import com.dataartisans.streamingledger.sdk.api.AccessType;
import com.dataartisans.streamingledger.sdk.api.StateAccess;
import com.dataartisans.streamingledger.sdk.api.StateAccessException;
import com.dataartisans.streamingledger.sdk.api.StateNotReadableException;
import com.dataartisans.streamingledger.sdk.api.StateNotWritableException;
import com.dataartisans.streamingledger.sdk.api.StreamingLedger.StateAccessSpec;

import static java.util.Objects.requireNonNull;

final class SerialStateAccess<InT, K, V> implements StateAccess<V> {
    private final StateAccessSpec<InT, K, V> spec;
    private final MapState<K, V> state;

    private final KeySelector<InT, K> keySelector;
    private final boolean writeOnly;
    private final boolean readOnly;

    private K key;
    private V value;
    private boolean changed;

    SerialStateAccess(StateAccessSpec<InT, K, V> spec, MapState<K, V> state) {
        this.spec = requireNonNull(spec);
        this.state = requireNonNull(state);
        this.keySelector = requireNonNull(spec.keyAccess);
        this.writeOnly = spec.accessType == AccessType.WRITE;
        this.readOnly = spec.accessType == AccessType.READ;
    }

    @Override
    public V read() throws StateAccessException {
        if (writeOnly) {
            throw new StateNotReadableException(this);
        }
        return value;
    }

    @Override
    public void write(V newValue) throws StateAccessException {
        if (readOnly) {
            throw new StateNotWritableException(this);
        }
        this.value = newValue;
        this.changed = true;
    }

    @Override
    public void delete() throws StateAccessException {
        if (readOnly) {
            throw new StateNotWritableException(this);
        }
        this.value = null;
        this.changed = true;
    }

    @Override
    public String getStateName() {
        return spec.state.getName();
    }

    @Override
    public String getStateAccessName() {
        return spec.bindName;
    }

    // -----------------------------------------------------
    // For internal use by SerialTransactor
    // -----------------------------------------------------

    void prepare(InT input) throws Exception {
        final K key = keySelector.getKey(input);
        this.key = key;
        this.changed = false;
        if (!writeOnly) {
            this.value = state.get(key);
        }
    }

    void commit(boolean wasAborted) throws Exception {
        if (!changed || wasAborted) {
            return;
        }
        if (value == null) {
            state.remove(key);
        }
        else {
            state.put(key, value);
        }
    }
}