package ru.yandex.webmaster3.storage.util.ydb.querybuilder.typesafe;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.yandex.ydb.table.values.ListType;
import com.yandex.ydb.table.values.ListValue;
import com.yandex.ydb.table.values.StructType;
import com.yandex.ydb.table.values.StructValue;
import com.yandex.ydb.table.values.Type;
import com.yandex.ydb.table.values.Value;
import org.apache.commons.lang3.tuple.Pair;

import ru.yandex.webmaster3.storage.util.ydb.query.DbFieldInsertAssignment;


/**
 * ishalaru
 * 11.08.2020
 **/
public class ValueDataMapper<T> {
    protected final List<Pair<Field, Function<T, Value>>> list;

    ValueDataMapper(Pair<Field, Function<T, Value>>... mappers) {
        list = List.of(mappers);
    }

    public List<Pair<String, Type>> getTypes() {
        return list.stream().map(e -> Pair.of(e.getLeft().getName(), e.getLeft().getType())).collect(Collectors.toList());
    }

    private StructValue get(T obj, StructType type) {
        Map<String, Value> result = new HashMap<>(list.size());
        for (Pair<Field, Function<T, Value>> item : list) {
            result.put(item.getKey().getName(), item.getValue().apply(obj));
        }
        return type.newValue(result);
    }

    private StructType getStruct() {
        String names[] = new String[list.size()];
        Type types[] = new Type[list.size()];
        int i = 0;
        for (Pair<Field, Function<T, Value>> a : list) {
            names[i] = a.getLeft().getName();
            types[i++] = a.getLeft().getType();
        }
        return StructType.ofOwn(names, types);
    }

    public ListValue convert(Collection<T> list) {
        StructType type = getStruct();
        ListType listType = ListType.of(type);
        return listType.newValue(list.stream().map(e -> this.get(e, type)).collect(Collectors.toList()));
    }

    public List<DbFieldInsertAssignment> toInsert(T item) {
        List<DbFieldInsertAssignment> result = new ArrayList<>();
        for (Pair<Field, Function<T, Value>> a : list) {
            Field f = a.getLeft();
            result.add(new DbFieldInsertAssignment(f.getName(), f.getType(), a.getRight().apply(item)));
        }
        return result;
    }

    // Pair.of(F.EXAMPLE, r -> F.EXAMPLE.get(r.getExample()))
    @SafeVarargs
    public static <T> ValueDataMapper<T> create(Pair<Field, Function<T, Value>>... mappers) {
        return new ValueDataMapper<>(mappers);
    }

    // Pair.of(F.EXAMPLE, r -> r.getExample())
    @SafeVarargs
    public static <T> ValueDataMapper<T> create2(Pair<Field, Function<T, ?>>... mappers) {
        Pair<Field, Function<T, Value>>[] arr = new Pair[mappers.length];
        for (int i = 0; i < mappers.length; i++) {
            final var mapper = mappers[i];
            final var field = mapper.getKey();
            final var func = mapper.getValue();
            arr[i] = Pair.of(field, x -> field.get(func.apply(x)));
        }
        return new ValueDataMapper<>(arr);
    }
}
