001/************************ PROJECT ZIXI ************************/
002/* Copyright (c) 2026 StuyPulse Robotics. All rights reserved.*/
003/* This work is licensed under the terms of the MIT license.  */
004/**************************************************************/
005package com.stuypulse.robot.util.talonfx.logged;
006
007import org.wpilib.units.Measure;
008import org.wpilib.util.Color;
009import org.wpilib.util.WPISerializable;
010import org.wpilib.util.struct.StructSerializable;
011
012import java.util.Arrays;
013import java.util.Objects;
014import org.littletonrobotics.junction.LogTable;
015
016/**
017 *
018 *
019 * <h2>Log Table Utility</h2>
020 *
021 * <p>Utility methods for reading and writing dynamically typed values to and from a {@code
022 * LogTable}.
023 *
024 * <p>This centralizes type handling without having to duplicate the same if else logic in multiple
025 * places.
026 *
027 * @author Faizaan (https://github.com/Faizaan-J)
028 */
029public final class LogTableUtil {
030    private LogTableUtil() {}
031
032    /**
033     * Puts a value into a {@code LogTable} with the given key, automatically handling the type of
034     * the value.
035     *
036     * @param table the {@code LogTable} to put the value into
037     * @param key The key where the value will be stored.
038     * @param value The value to store in the {@code LogTable}. If it's not a supported type, it
039     *     will be converted to a string.
040     */
041    public static void put(LogTable table, String key, Object value) {
042        Objects.requireNonNull(table, "LogTable cannot be null");
043        Objects.requireNonNull(key, "Logging key cannot be null");
044        switch (value) {
045            case Object _ when value.getClass().isArray() -> putArray(table, key, value);
046            case Measure<?> measure -> table.putMeasure(key, measure);
047            case WPISerializable serializable -> table.put(key, serializable);
048            case Color color -> table.put(key, color);
049            case Boolean b -> table.put(key, b);
050            case Double d -> table.put(key, d);
051            case Integer i -> table.put(key, i);
052            case Long l -> table.put(key, l);
053            case String s -> table.put(key, s);
054            case Enum<?> enumerator -> putEnum(table, key, enumerator);
055            case Record record -> table.put(key, record);
056            case null -> table.put(key, "null");
057            default -> table.put(key, value.toString());
058        }
059    }
060
061    /**
062     * Puts an array value into a {@code LogTable}, dispatching to the corresponding array overload.
063     * Falls back to a {@code String[]} for element types with no array overload such as a {@code
064     * Measure} or boxed wrapper types.
065     */
066    private static void putArray(LogTable table, String key, Object array) {
067        switch (array) {
068            case boolean[] bools -> table.put(key, bools);
069            case int[] ints -> table.put(key, ints);
070            case long[] longs -> table.put(key, longs);
071            case float[] floats -> table.put(key, floats);
072            case double[] doubles -> table.put(key, doubles);
073            case String[] strings -> table.put(key, strings);
074            case byte[] bytes -> table.put(key, bytes);
075            case Object enums when enums instanceof Enum<?>[] ->
076                    putEnumArray(table, key, (Enum<?>[]) enums);
077            case StructSerializable[] serializables -> table.put(key, serializables);
078            case Record[] records -> table.put(key, records);
079            case Object[] objects ->
080                    table.put(
081                            key,
082                            Arrays.stream(objects).map(String::valueOf).toArray(String[]::new));
083            case null, default ->
084                    throw new IllegalArgumentException(
085                            "Unsupported or invalid array type: "
086                                    + (array == null ? "null" : array.getClass().getName()));
087        }
088    }
089
090    /**
091     * Gets a value from a {@code LogTable} with the given key, automatically handling the type of
092     * the value based on the provided default value.
093     *
094     * @param <T> The type of the value.
095     * @param table The {@code LogTable} to get the value from.
096     * @param key The key where the valid is stored.
097     * @param defaultValue The default value to return if the key does not exist in the {@code
098     *     LogTable} .
099     * @return The value from the {@code LogTable} if it exists, otherwise the default value.
100     */
101    @SuppressWarnings({"unchecked"})
102    public static <T> T get(LogTable table, String key, T defaultValue) {
103        Objects.requireNonNull(table, "LogTable cannot be null");
104        Objects.requireNonNull(key, "Logging key cannot be null");
105        return (T)
106                switch (defaultValue) {
107                    case Object _ when defaultValue.getClass().isArray() ->
108                            getArray(table, key, defaultValue);
109                    case Measure<?> measure -> table.getMeasure(key, measure);
110                    case WPISerializable serializable -> table.get(key, serializable);
111                    case Color color -> table.get(key, color);
112                    case Boolean b -> Boolean.valueOf(table.get(key, b));
113                    case Double d -> Double.valueOf(table.get(key, d));
114                    case Integer i -> Integer.valueOf(table.get(key, i));
115                    case Long l -> Long.valueOf(table.get(key, l));
116                    case String s -> table.get(key, s);
117                    case Object enumerator when enumerator instanceof Enum<?> ->
118                            getEnum(table, key, (Enum<?>) enumerator);
119                    case Record record -> table.get(key, record);
120                    case null, default -> defaultValue;
121                };
122    }
123
124    /**
125     * Gets an array value from a {@code LogTable}, dispatching to the corresponding array overload.
126     * Types with no array overload such as {@code Measure} or boxed wrapper types aren't
127     * replayable, so the default array is returned back.
128     */
129    private static Object getArray(LogTable table, String key, Object defaultArray) {
130        return switch (defaultArray) {
131            case boolean[] bools -> table.get(key, bools);
132            case int[] ints -> table.get(key, ints);
133            case long[] longs -> table.get(key, longs);
134            case float[] floats -> table.get(key, floats);
135            case double[] doubles -> table.get(key, doubles);
136            case String[] strings -> table.get(key, strings);
137            case byte[] bytes -> table.get(key, bytes);
138            case Object enums when enums instanceof Enum<?>[] ->
139                    getEnumArray(table, key, (Enum<?>[]) enums);
140            case StructSerializable[] serializables -> table.get(key, serializables);
141            case Record[] records -> table.get(key, records);
142            case Object[] objects ->
143                    table.get(
144                            key,
145                            Arrays.stream(objects).map(String::valueOf).toArray(String[]::new));
146            case null, default -> defaultArray;
147        };
148    }
149
150    @SuppressWarnings("unchecked")
151    private static <E extends Enum<E>> void putEnum(LogTable table, String key, Enum<?> value) {
152        table.put(key, (E) value);
153    }
154
155    @SuppressWarnings("unchecked")
156    private static <E extends Enum<E>> E getEnum(LogTable table, String key, Enum<?> defaultValue) {
157        return table.get(key, (E) defaultValue);
158    }
159
160    @SuppressWarnings("unchecked")
161    private static <E extends Enum<E>> void putEnumArray(
162            LogTable table, String key, Enum<?>[] value) {
163        table.put(key, (E[]) value);
164    }
165
166    @SuppressWarnings("unchecked")
167    private static <E extends Enum<E>> E[] getEnumArray(
168            LogTable table, String key, Enum<?>[] defaultValue) {
169        return table.get(key, (E[]) defaultValue);
170    }
171
172    /**
173     * Returns true if the value is a supported type for logging, false otherwise.
174     *
175     * @param value The value to check for logging support.
176     * @return true if the value is a supported type for logging, false otherwise.
177     */
178    public static boolean isSupportedType(Object value) {
179        if (value == null) {
180            return false;
181        }
182
183        if (value.getClass().isArray()) {
184            return isSupportedArrayType(value);
185        }
186
187        return switch (value) {
188            case Measure<?> _ -> true;
189            case WPISerializable _ -> true;
190            case Color _ -> true;
191            case Boolean _ -> true;
192            case Double _ -> true;
193            case Integer _ -> true;
194            case Long _ -> true;
195            case String _ -> true;
196            case Enum<?> _ -> true;
197            case Record _ -> true;
198            case null, default -> false;
199        };
200    }
201
202    private static boolean isSupportedArrayType(Object value) {
203        return switch (value) {
204            case boolean[] _ -> true;
205            case int[] _ -> true;
206            case long[] _ -> true;
207            case float[] _ -> true;
208            case double[] _ -> true;
209            case byte[] _ -> true;
210            case String[] _ -> true;
211            case Object _ when value instanceof Enum<?>[] _ -> true;
212            case StructSerializable[] _ -> true;
213            case Object[] array -> array.getClass().getComponentType().isRecord();
214            case null, default -> false;
215        };
216    }
217}