001package net.tnemc.item.platform;
002/*
003 * The New Item Library
004 * Copyright (C) 2022 - 2025 Daniel "creatorfromhell" Vidmar
005 *
006 * This program is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020
021import net.kyori.adventure.text.Component;
022import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
023import net.tnemc.item.AbstractItemStack;
024import net.tnemc.item.component.SerialComponent;
025import net.tnemc.item.component.helper.effect.ApplyEffectsComponentEffect;
026import net.tnemc.item.component.helper.effect.ComponentEffect;
027import net.tnemc.item.component.helper.effect.PlaySoundComponentEffect;
028import net.tnemc.item.component.helper.effect.RemoveEffectsComponentEffect;
029import net.tnemc.item.component.helper.effect.TeleportRandomlyComponentEffect;
030import net.tnemc.item.persistent.PersistentDataType;
031import net.tnemc.item.persistent.impl.PersistentBool;
032import net.tnemc.item.persistent.impl.PersistentByte;
033import net.tnemc.item.persistent.impl.PersistentByteArray;
034import net.tnemc.item.persistent.impl.PersistentDouble;
035import net.tnemc.item.persistent.impl.PersistentFloat;
036import net.tnemc.item.persistent.impl.PersistentInt;
037import net.tnemc.item.persistent.impl.PersistentIntArray;
038import net.tnemc.item.persistent.impl.PersistentLong;
039import net.tnemc.item.persistent.impl.PersistentLongArray;
040import net.tnemc.item.persistent.impl.PersistentShort;
041import net.tnemc.item.persistent.impl.PersistentString;
042import net.tnemc.item.platform.applier.ItemApplicator;
043import net.tnemc.item.platform.check.ItemCheck;
044import net.tnemc.item.platform.check.LocaleItemCheck;
045import net.tnemc.item.platform.conversion.PlatformConverter;
046import net.tnemc.item.platform.serialize.ItemSerializer;
047import net.tnemc.item.providers.CalculationsProvider;
048import net.tnemc.item.providers.ItemProvider;
049import org.jetbrains.annotations.NotNull;
050import org.json.simple.JSONObject;
051
052import java.util.Arrays;
053import java.util.HashMap;
054import java.util.List;
055import java.util.Map;
056import java.util.Optional;
057import java.util.concurrent.ConcurrentHashMap;
058
059/**
060 * ItemPlatform
061 *
062 * @param <I> The implementation's instance of {@link AbstractItemStack}
063 * @param <S> The implementation's instance of item stacks.
064 * @param <U> The implementation's instace of inventories.
065 *
066 * @author creatorfromhell
067 * @since 0.2.0.0
068 */
069public abstract class ItemPlatform<I extends AbstractItemStack<S>, S, U> {
070
071  protected final Map<String, Class<? extends PersistentDataType<?>>> classes = new ConcurrentHashMap<>();
072
073  protected final Map<String, ItemProvider<S>> itemProviders = new ConcurrentHashMap<>();
074
075  protected final Map<String, ItemCheck<S>> checks = new HashMap<>();
076  protected final Map<String, LocaleItemCheck<S>> localeChecks = new HashMap<>();
077  protected final Map<String, ItemApplicator<I, S>> applicators = new HashMap<>();
078  protected final Map<String, ItemSerializer<I, S>> serializers = new HashMap<>();
079
080  protected final Map<String, Class<? extends ComponentEffect>> effects = new HashMap<>();
081
082  protected final PlatformConverter converter;
083
084  public ItemPlatform() {
085
086    this(new PlatformConverter());
087  }
088
089  public ItemPlatform(final PlatformConverter converter) {
090
091    this.converter = converter;
092
093    addPersistentDataType("bool", PersistentBool.class);
094    addPersistentDataType("byte", PersistentByte.class);
095    addPersistentDataType("byte-array", PersistentByteArray.class);
096    addPersistentDataType("double", PersistentDouble.class);
097    addPersistentDataType("float", PersistentFloat.class);
098    addPersistentDataType("int", PersistentInt.class);
099    addPersistentDataType("int-array", PersistentIntArray.class);
100    addPersistentDataType("long", PersistentLong.class);
101    addPersistentDataType("long-array", PersistentLongArray.class);
102    addPersistentDataType("short", PersistentShort.class);
103    addPersistentDataType("string", PersistentString.class);
104
105    addEffect(new ApplyEffectsComponentEffect());
106    addEffect(new PlaySoundComponentEffect());
107    addEffect(new RemoveEffectsComponentEffect());
108    addEffect(new TeleportRandomlyComponentEffect());
109  }
110
111  /**
112   * Creates a new stack based on the given material.
113   *
114   * @param material The material used for creating the stack.
115   * @return The newly created stack.
116   * @since 0.2.0.0
117   */
118  public abstract I createStack(final String material);
119
120  /**
121   * @return the version that is being used currently
122   * @since 0.2.0.0
123   */
124  public abstract String version();
125
126  /**
127   * Adds default configurations or settings to be used by the implementing class.
128   * @since 0.2.0.0
129   */
130  public abstract void addDefaults();
131
132  /**
133   * Retrieves the platform converter associated with the current item platform.
134   *
135   * @return The {@link PlatformConverter} instance used by the item platform.
136   * @since 0.2.0.0
137   */
138  public PlatformConverter converter() {
139
140    return converter;
141  }
142
143  /**
144   * Retrieves the default provider for the item stack comparison.
145   *
146   * @return the default provider for the item stack comparison.
147   * @since 0.2.0.0
148   */
149  public abstract @NotNull ItemProvider<S> defaultProvider();
150
151  /**
152   * Retrieves the identifier of the default provider for the item stack comparison.
153   *
154   * @return The identifier of the default provider for the item stack comparison.
155   * @since 0.2.0.0
156   */
157  public abstract @NotNull String defaultProviderIdentifier();
158
159  /**
160   * Provides access to the calculations provider for performing various platform-specific operations.
161   *
162   * @return An instance of {@link CalculationsProvider} that handles calculations related to the item platform.
163   * @since 0.2.0.0
164   */
165  public abstract CalculationsProvider<I, S, U> calculations();
166
167  /**
168   * Checks if any of the registered item providers are applicable to the given serialized item and item.
169   *
170   * @param serialized The serialized item stack to check against.
171   * @param item The item to check for applicability.
172   * @return True if an item provider is found that applies to the serialized item and item, otherwise false.
173   * @since 0.2.0.0
174   */
175  public boolean providerApplies(final AbstractItemStack<? extends S> serialized, final S item) {
176
177    for(final ItemProvider<S> provider : itemProviders.values()) {
178
179      if(provider.identifier().equalsIgnoreCase(defaultProvider().identifier())) {
180        continue;
181      }
182
183      if(provider.appliesTo(serialized, item)) {
184        return true;
185      }
186    }
187    return false;
188  }
189
190  /**
191   * Retrieves the item provider for the given itemProvider name, or returns the default provider if not found.
192   *
193   * @param itemProvider The name of the ItemProvider to retrieve.
194   * @return The ItemProvider associated with the itemProvider name, or the default provider if not found.
195   * @since 0.2.0.0
196   */
197  public ItemProvider<S> provider(final String itemProvider) {
198
199    return itemProviders.getOrDefault(itemProvider, defaultProvider());
200  }
201
202  /**
203   * Adds an ItemProvider to the ItemPlatform.
204   *
205   * @param provider The ItemProvider to add to the platform
206   * @since 0.2.0.0
207   */
208  public void addItemProvider(final ItemProvider<S> provider) {
209
210    itemProviders.put(provider.identifier(), provider);
211  }
212
213  /**
214   * Adds a persistent data type to the item platform.
215   *
216   * @param identifier The identifier for the persistent data type.
217   * @param type       The class representing the persistent data type.
218   * @since 0.2.0.0
219   */
220  public void addPersistentDataType(final String identifier, @NotNull final Class<? extends PersistentDataType<?>> type) {
221
222    classes.put(identifier, type);
223  }
224
225  public Map<String, Class<? extends PersistentDataType<?>>> getClasses() {
226
227    return classes;
228  }
229
230  /**
231   * Used to add an object that is capable of being dual/tri purpose as a check, applicator and/or
232   * deserializer.
233   *
234   * @param object the object to add.
235   * @since 0.2.0.0
236   */
237  public void addMulti(@NotNull final Object object) {
238
239    if(object instanceof final ItemCheck<?> check) {
240
241      try {
242
243        if(!check.enabled(version())) {
244
245          return;
246        }
247
248        if(check instanceof final LocaleItemCheck<?> localeItemCheck) {
249
250          localeChecks.put(localeItemCheck.identifier(), (LocaleItemCheck<S>)localeItemCheck);
251        } else {
252
253          checks.put(check.identifier(), (ItemCheck<S>)check);
254        }
255      } catch(final Exception ignore) {
256        //Just in case it passes the instance check, but the Generic is
257        //incorrect for w.e reason, we want to fail safely.
258      }
259    }
260
261    if(object instanceof final ItemApplicator<?, ?> applicator) {
262
263      try {
264
265        if(!applicator.enabled(version())) {
266
267          return;
268        }
269
270        applicators.put(applicator.identifier(), (ItemApplicator<I, S>)applicator);
271      } catch(final Exception ignore) {
272        //Just in case it passes the instance check, but the Generic is
273        //incorrect for w.e reason, we want to fail safely.
274      }
275    }
276
277    if(object instanceof final ItemSerializer<?, ?> serializer) {
278
279      try {
280
281        if(!serializer.enabled(version())) {
282
283          return;
284        }
285
286        serializers.put(serializer.identifier(), (ItemSerializer<I, S>)serializer);
287      } catch(final Exception ignore) {
288        //Just in case it passes the instance check, but the Generic is
289        //incorrect for w.e reason, we want to fail safely.
290      }
291    }
292  }
293
294  /**
295   * Converts the given locale stack to an instance of {@link AbstractItemStack}
296   *
297   * @param locale the locale to convert
298   * @return the converted locale of type I
299   * @since 0.2.0.0
300   */
301  public abstract I locale(final S locale);
302
303  /**
304   * @param check the {@link ItemCheck check} to add.
305   * @since 0.2.0.0
306   */
307  public void addCheck(@NotNull final ItemCheck<S> check) {
308
309    checks.put(check.identifier(), check);
310  }
311
312  /**
313   * @param applicator the applicator to add
314   * @since 0.2.0.0
315   */
316  public void addApplicator(@NotNull final ItemApplicator<I, S> applicator) {
317
318    applicators.put(applicator.identifier(), applicator);
319  }
320
321  /**
322   * @param serializer the deserializer to add
323   * @since 0.2.0.0
324   */
325  public void addSerializer(@NotNull final ItemSerializer<I, S> serializer) {
326
327    serializers.put(serializer.identifier(), serializer);
328  }
329
330  /**
331   * Adds a ReviveEffect to the reviveEffects map.
332   *
333   * @param effect The ReviveEffect instance to add. Must not be null.
334   * @since 0.2.0.0
335   */
336  public void addEffect(@NotNull final ComponentEffect effect) {
337
338    // Add the effect's class to the map using its type as the key
339    effects.put(effect.getType(), effect.getClass());
340  }
341
342  /**
343   * Used to check if two locale stacks are comparable.
344   * How they are performed:
345   * - if a locale check applies, the check result is returned, true is the default return.
346   *
347   * @param original       the original stack
348   * @param check          the stack to use for the check
349   * @param disabledChecks the {@link ItemCheck#identifier()} check identifiers that should be
350   *                       disabled for the check.
351   *
352   * @return True if the check passes, otherwise false.
353   * @since 0.2.0.0
354   */
355  public boolean check(@NotNull final S original, @NotNull final S check, @NotNull final String... disabledChecks) {
356
357    final List<String> disabled = Arrays.asList(disabledChecks);
358
359    for(final LocaleItemCheck<S> localeCheck : localeChecks.values()) {
360
361      if(disabled.contains(localeCheck.identifier())) {
362        continue;
363      }
364
365      if(!localeCheck.enabled(version())) {
366        continue;
367      }
368
369      if(!localeCheck.applies(original, check)) {
370        continue;
371      }
372      return localeCheck.check(original, check);
373    }
374    return true;
375  }
376
377  /**
378   * Used to check if two locale stacks are comparable based on a specific order of checks.
379   *
380   * @param original the original stack
381   * @param check    the stack to use for the check
382   * @param order    the order of the checks to run for the comparison
383   *
384   * @return True if the check passes, otherwise false.
385   * @since 0.2.0.0
386   */
387  public boolean checkOrder(@NotNull final S original, @NotNull final S check, @NotNull final String... order) {
388
389    for(final String id : order) {
390
391      if(!checks.containsKey(id)) {
392        continue;
393      }
394
395
396      final LocaleItemCheck<S> localeCheck = localeChecks.get(id);
397      if(!localeCheck.enabled(version())) {
398        continue;
399      }
400
401      if(!localeCheck.applies(original, check)) {
402        continue;
403      }
404      return localeCheck.check(original, check);
405    }
406    return true;
407  }
408
409  /**
410   * Used to check if two serialized stacks are comparable.
411   *
412   * @param original       the original stack
413   * @param check          the stack to use for the check
414   * @param disabledChecks the {@link ItemCheck#identifier()} check identifiers that should be
415   *                       disabled for the check.
416   *
417   * @return True if the check passes, otherwise false.
418   * @since 0.2.0.0
419   */
420  public boolean check(@NotNull final I original, @NotNull final I check, final String... disabledChecks) {
421
422    final List<String> disabled = Arrays.asList(disabledChecks);
423    for(final ItemCheck<S> checkItem : checks.values()) {
424
425      System.out.println("Check: " + checkItem.identifier());
426
427      if(disabled.contains(checkItem.identifier())) {
428        continue;
429      }
430
431      if(!checkItem.enabled(version())) {
432        continue;
433      }
434
435      if(!checkItem.applies(original, check)) {
436        continue;
437      }
438
439      if(!checkItem.check(original, check)) {
440
441        System.out.println("Failed check: " + checkItem.identifier());
442        return false;
443      }
444    }
445    return true;
446  }
447
448  /**
449   * Used to check if two serialized stacks are comparable based on a specific order of checks.
450   *
451   * @param original the original stack
452   * @param check    the stack to use for the check
453   * @param order    the order of the checks to run for the comparison
454   *
455   * @return True if the check passes, otherwise false.
456   * @since 0.2.0.0
457   */
458  public boolean checkOrder(@NotNull final I original, @NotNull final I check, @NotNull final String... order) {
459
460    for(final String id : order) {
461
462      if(!checks.containsKey(id)) {
463        continue;
464      }
465
466      final ItemCheck<S> checkItem = checks.get(id);
467
468      if(!checkItem.enabled(version())) {
469        continue;
470      }
471
472      if(!checkItem.applies(original, check)) {
473        continue;
474      }
475
476      if(!checkItem.check(original, check)) {
477        return false;
478      }
479    }
480    return true;
481  }
482
483  /**
484   * Applies all enabled applicators to the given item.
485   *
486   * @param serialized the serialized item stack to use
487   * @param item       the locale itemstack object to apply the applications to
488   *
489   * @return the updated item stack after applying the applicators
490   * @since 0.2.0.0
491   */
492  public S apply(@NotNull final I serialized, @NotNull S item) {
493
494    for(final ItemApplicator<I, S> applicator : applicators.values()) {
495
496      System.out.println("Try applicator: " + applicator.identifier());
497
498      if(applicator.enabled(version())) {
499
500        System.out.println("Applicator ready to apply");
501
502        item = applicator.apply(serialized, item);
503      }
504    }
505    return item;
506  }
507
508  /**
509   * Applies all enabled serializers to the given item.
510   *
511   * @param item       the item that we should use to serialize.
512   * @param serialized the serialized item stack we should use to apply this serializer to
513   *
514   * @return the updated serialized item.
515   * @since 0.2.0.0
516   */
517  public I serializer(@NotNull final S item, @NotNull I serialized) {
518
519    for(final ItemSerializer<I, S> serializer : serializers.values()) {
520      if(serializer.enabled(version())) {
521
522        System.out.println("Serializer ready to apply: " + serializer.identifier());
523
524        if(serializer instanceof final SerialComponent<I,S> component) {
525
526          System.out.println("Serializer is component");
527          if(!component.appliesTo(item)) {
528
529            System.out.println("Serializer doesn't apply");
530
531            continue;
532          }
533        }
534
535        serialized = serializer.serialize(item, serialized);
536      }
537    }
538    return serialized;
539  }
540
541  /**
542   * Initializes and returns an AbstractItemStack object by deserializing the provided JSON object.
543   *
544   * @param object the JSON object to deserialize
545   *
546   * @return an initialized AbstractItemStack object
547   * @since 0.2.0.0
548   */
549  public abstract Optional<I> initSerialized(final JSONObject object);
550
551  public static String componentString(@NotNull final Component component) {
552
553    return PlainTextComponentSerializer.plainText().serialize(component);
554  }
555
556  public Map<String, Class<? extends ComponentEffect>> effects() {
557
558    return effects;
559  }
560}