package io.wealthwizards.appsynctesting.utils;

import com.github.f4b6a3.ulid.UlidCreator;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.wealthwizards.appsynctesting.AppSyncState;
import io.wealthwizards.appsynctesting.utils.dynamodb.DynamoDB;
import io.wealthwizards.appsynctesting.utils.transform.Transform;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;

import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;

/* $util.<name> utilities must be exposed as public fields
 * so that the velocity public field uberspector can pick
 * these up as introspectable and expose them within the
 * velocity templates.
 **/
public final class Util extends StatefulUtil {
    public final Time time;
    public final DynamoDB dynamodb;
    public final List list;
    public final Map map;
    public final Str str;
    public final Math math;
    public final Transform transform;
    public final Rds rds;
    public final Http http;
    public final Xml xml;

    public Util(AppSyncState state, Time time, DynamoDB dynamodb, List list, Map map, Str str, Math math, Transform transform, Rds rds, Http http, Xml xml) {
        super(state);

        this.time = time;
        this.dynamodb = dynamodb;
        this.list = list;
        this.map = map;
        this.str = str;
        this.math = math;
        this.transform = transform;
        this.rds = rds;
        this.http = http;
        this.xml = xml;
    }

    public void qr() { }

    public void qr(Object object) { }

    public void quiet() { }

    public void quiet(Object object) { }

    public String escapeJavaScript(String string) {
        return StringEscapeUtils.escapeEcmaScript(string);
    }

    public String urlEncode(String string) {
        return URLEncoder.encode(string, StandardCharsets.UTF_8);
    }

    public String urlDecode(String string) {
        return URLDecoder.decode(string, StandardCharsets.UTF_8);
    }

    public String base64Encode(byte[] bytes) {
        return Base64.getEncoder().encodeToString(bytes);
    }

    public String base64Encode(String string) {
        return base64Encode(string.getBytes(StandardCharsets.UTF_8));
    }

    public String base64Decode(String string) {
        return new String(Base64.getDecoder().decode(string), StandardCharsets.UTF_8);
    }

    public Object parseJson(String string) {
        var gson = new GsonBuilder()
                .serializeNulls()
                .create();

        return gson.fromJson(string, Object.class);
    }

    public Object toJson(Object object) {
        var gson = new GsonBuilder()
                .serializeNulls()
                .create();

        return gson.toJson(object);
    }

    public String autoId() {
        return UUID.randomUUID().toString();
    }

    public String autoUlid() {
        return UlidCreator.getUlid().toString();
    }

    public boolean isNull(Object maybeNull) {
        return maybeNull == null;
    }

    public boolean isNullOrEmpty(String maybeNullOrEmpty) {
        return maybeNullOrEmpty == null || StringUtils.isEmpty(maybeNullOrEmpty);
    }

    public boolean isNullOrBlank(String maybeNullOrBlank) {
        return maybeNullOrBlank == null || StringUtils.isBlank(maybeNullOrBlank);
    }

    public Object defaultIfNull(Object fallback, Object maybeNull) {
        if (isNull(maybeNull)) {
            return fallback;
        }

        return maybeNull;
    }

    public Object defaultIfNullOrEmpty(String fallback, String maybeNullOrEmpty) {
        if (isNullOrEmpty(maybeNullOrEmpty)) {
            return fallback;
        }

        return maybeNullOrEmpty;
    }

    public Object defaultIfNullOrBlank(String fallback, String maybeNullOrBlank) {
        if (isNullOrBlank(maybeNullOrBlank)) {
            return fallback;
        }

        return maybeNullOrBlank;
    }

    public boolean isString(Object maybeString) {
        return maybeString instanceof String;
    }

    public boolean isNumber(Object maybeNumber) {
        return maybeNumber instanceof Number;
    }

    public boolean isBoolean(Object maybeBoolean) {
        return maybeBoolean instanceof Boolean;
    }

    public boolean isList(Object maybeList) {
        return maybeList instanceof Collection;
    }

    public boolean isMap(Object maybeMap) {
        return maybeMap instanceof java.util.Map;
    }

    public String typeOf(Object object) {
        // pattern matching is Java 16 :'(
        if (isNull(object)) {
            return "Null";
        } else if (isNumber(object)) {
            return "Number";
        } else if (isString(object)) {
            return "String";
        } else if (isMap(object)) {
            return "Map";
        } else if (isList(object)) {
            return "List";
        } else if (isBoolean(object)) {
            return "Boolean";
        }

        return "Object";
    }

    public boolean matches(String regex, String value) {
        if (value == null) {
            return false;
        }

        return Pattern.compile(regex)
                .matcher(value)
                .matches();
    }
}
