package com.opencredo.concursus.cassandra.events;

import com.datastax.driver.core.Row;
import com.datastax.driver.core.exceptions.DriverException;
import com.opencredo.concursus.data.tuples.Tuple;
import com.opencredo.concursus.data.tuples.TupleSchema;
import com.opencredo.concursus.domain.common.AggregateId;
import com.opencredo.concursus.domain.common.VersionedName;
import com.opencredo.concursus.domain.events.Event;
import com.opencredo.concursus.domain.events.EventType;
import com.opencredo.concursus.domain.events.matching.EventTypeMatcher;
import com.opencredo.concursus.domain.time.StreamTimestamp;
import org.springframework.cassandra.core.RowCallbackHandler;

import java.lang.reflect.Type;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;

final class EventTranslator implements RowCallbackHandler {

    private static final int AGGREGATE_TYPE = 0;
    private static final int AGGREGATE_ID = 1;
    private static final int EVENT_TIMESTAMP = 2;
    private static final int STREAM_ID = 3;
    private static final int PROCESSING_ID = 4;
    private static final int EVENT_NAME = 5;
    private static final int EVENT_VERSION = 6;
    private static final int PARAMETERS = 7;
    private static final int CHARACTERISTICS = 8;

    static EventTranslator using(EventTypeMatcher matcher, BiFunction<String, Type, Object> deserialiser, Consumer<Event> eventCollector) {
        return new EventTranslator(matcher, deserialiser, eventCollector);
    }

    private final EventTypeMatcher matcher;
    private final BiFunction<String, Type, Object> deserialiser;
    private final Consumer<Event> eventCollector;

    private EventTranslator(EventTypeMatcher matcher, BiFunction<String, Type, Object> deserialiser, Consumer<Event> eventCollector) {
        this.matcher = matcher;
        this.deserialiser = deserialiser;
        this.eventCollector = eventCollector;
    }

    @Override
    public void processRow(Row row) throws DriverException {
        String aggregateType = row.getString(AGGREGATE_TYPE);
        String name = row.getString(EVENT_NAME);
        String version = row.getString(EVENT_VERSION);

        VersionedName versionedName = VersionedName.of(name, version);
        EventType eventType = EventType.of(aggregateType, versionedName);

        matcher.match(eventType).ifPresent(tupleSchema -> {
            createEvent(row, aggregateType, versionedName, tupleSchema);
        });
    }

    private void createEvent(Row row, String aggregateType, VersionedName versionedName, TupleSchema tupleSchema) {
        Map<String, String> parameterData = row.getMap(PARAMETERS, String.class, String.class);
        Tuple parameters = tupleSchema.deserialise(deserialiser, parameterData);

        Event event = Event.of(
                AggregateId.of(aggregateType, row.getString(AGGREGATE_ID)),
                StreamTimestamp.of(row.getString(STREAM_ID), row.getDate(EVENT_TIMESTAMP).toInstant()),
                row.getUUID(PROCESSING_ID),
                versionedName,
                parameters,
                row.getInt(CHARACTERISTICS));

        eventCollector.accept(event);
    }

}