package com.infinityraider.agricraft.api.v1.plant;

import com.infinityraider.agricraft.api.v1.AgriApi;
import com.infinityraider.agricraft.api.v1.crop.IAgriCrop;
import com.infinityraider.agricraft.api.v1.crop.IAgriGrowthStage;
import com.infinityraider.agricraft.api.v1.fertilizer.IAgriFertilizer;
import com.infinityraider.agricraft.api.v1.genetics.IAgriGene;
import com.infinityraider.agricraft.api.v1.genetics.IAllele;
import com.infinityraider.agricraft.api.v1.util.IAgriRegisterable;
import com.infinityraider.agricraft.api.v1.requirement.IAgriGrowthRequirement;
import com.infinityraider.agricraft.api.v1.stat.IAgriStatsMap;

import java.util.*;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;

/**
 * This interface is used both for you to read the AgriCraft CropPlants as well as coding your own.
 * If you register your own ICropPlant object, it will be wrapped by the api. Meaning if you query
 * the ICropPlant object you registered, it will return a different object.
 */
public interface IAgriPlant extends IAgriRegisterable<IAgriPlant>, IAgriGrowable.WithSeed, IAllele<IAgriPlant> {
    /**
     * Creates a new Ingredient object for this plant with default genes
     * @return the ingredient
     */
    default AgriPlantIngredient toIngredient() {
        return new AgriPlantIngredient(this);
    }

    /**
     * Creates a new ItemStack object holding one seed item for this plant with default genes
     * @return the ItemStack
     */
    default ItemStack toItemStack() {
        return this.toItemStack(1);
    }

    /**
     * Creates a new ItemStack object holding the specified amount of seed items for this plant with default genes
     * @param amount the desired stack size
     * @return the ItemStack
     */
    default ItemStack toItemStack(int amount) {
        return AgriApi.plantToSeedStack(this, amount);
    }

    /**
     * @return a text component representing the name of this plant for use in tooltips, agricultural journal, genome, etc.
     */
    @Nonnull
    MutableComponent getPlantName();

    /**
     * @return a text component representing the name of the seed of this plant for use in tooltips, agricultural journal, etc.
     */
    @Nonnull
    Component getSeedName();

    /**
     * @return the tier of the plant, the higher the number, the higher the tier
     */
    int getTier();

    /**
     * Determines if the plant is affected by fertilizers. If false, this setting will prevent any
     * fertilizer from being used on the plant.
     *
     * @return If the plant can be fertilized.
     */
    boolean isFertilizable(IAgriGrowthStage growthStage, IAgriFertilizer fertilizer);

    /**
     * Retrieves the spread chance for a given plant. The spread chance is a normalized p-value that
     * represents the chance of the plant to overtake a neighboring crop each tick.
     *
     * @return The spread chance of the plant.
     */
    double getSpreadChance(IAgriGrowthStage growthStage);

    /**
     * Retrieves the base growth chance of the given plant. The growth bonus (from
     * {@link #getGrowthChanceBonus(IAgriGrowthStage)}) is then multiplied by the plant's growth stat
     * and added to this value as to get the actual growth chance of
     * the plant.
     *
     * @return The base growth chance of the plant.
     */
    double getGrowthChanceBase(IAgriGrowthStage growthStage);

    /**
     * Retrieves the growth bonus, or the added p-value for the plant to grow per growth stat level.
     * The growth bonus is multiplied by the plant's growth stat and added to the plant's base growth chance
     * (from {@link #getGrowthChanceBase(IAgriGrowthStage)}) as to get the actual growth chance of the plant.
     *
     * @return The growth bonus of the plant.
     */
    double getGrowthChanceBonus(IAgriGrowthStage growthStage);

    /**
     * Retrieves the base seed drop chance of the given plant. The seed drop chance bonus (from
     * {@link #getSeedDropChanceBonus(IAgriGrowthStage)}) is then multiplied by the plant's growth stage and added
     * to this value as to get the actual seed drop chance of the plant.
     *
     * @return The base seed drop chance of the plant.
     */
    double getSeedDropChanceBase(IAgriGrowthStage growthStage);

    /**
     * Retrieves the seed drop chance bonus, or the added p-value for the plant to drop its seed per
     * growth stage. The seed drop bonus is multiplied by the plant's growth stage and added to the
     * plant's base seed drop chance (from {@link #getSeedDropChanceBase(IAgriGrowthStage)}) as to get the actual
     * seed drop chance of the plant.
     *
     * @return The seed drop bonus of the plant.
     */
    double getSeedDropChanceBonus(IAgriGrowthStage growthStage);

    /**
     * Determines the Growth Stage to which the plant returns after being harvested
     *
     * @return the IAgriGrowthStage after harvesting
     */
    @Nonnull
    IAgriGrowthStage getGrowthStageAfterHarvest();

    /**
     * Determines the original BlockState corresponding to a the GrowthStage of this plant on Crop Sticks,
     * if the plant had been planted in the world instead.
     * Can return an empty Optional in case this is a crop sticks only plant.
     *
     * @param stage the growth stage of the plant
     * @return Optional containing the corresponding block state, or empty.
     */
    Optional<BlockState> asBlockState(IAgriGrowthStage stage);

    /**
     * Fetches the user-friendly plant description for use in the Seed Journal. Notice, any
     * localization of this information is left for the implementer to handle.
     *
     * @return Information about the plant to be displayed in the Seed Journal.
     */
    @Nonnull
    MutableComponent getInformation();

    /**
     * Fetches the user-friendly plant description for use in tooltips. Notice, any
     * localization of this information is left for the implementer to handle.
     *
     * @param consumer consumer accepting information about the plant to be displayed in tooltips.
     */
    void addTooltip(Consumer<Component> consumer);

    /**
     * Checks if an ItemStack is considered a seed item for this plant.
     *
     * @return if the stack represents a seed for this item.
     */
    @Nonnull
    boolean isSeedItem(ItemStack stack);

    /**
     * Gets the growth requirements for this plant, this is used to check if the plant can be planted
     * or grow in certain locations.
     * <p>
     * If you don't want to create your own class for this, you can use
     * APIv2.getGrowthRequirementBuilder() to get a Builder object to build IGrowthRequirements If
     * you just want to have vanilla crop behaviour, you can use APIv2.getDefaultGrowthRequirement()
     * to get a growth requirement with default behaviour
     *
     * @return a set containing all growth conditions for the given stage
     */
    @Nonnull
    IAgriGrowthRequirement getGrowthRequirement(IAgriGrowthStage stage);

    /**
     * Retrieves all possible products of harvesting this plant. Note, this function is to be used
     * for informational purposes only, i.e. for display in the seed journal and JEI. As such, this
     * method is not required as to provide the complete set of all items that this plant could
     * actually drop on a harvest event, but rather is required to list all the products that should
     * show up in the journal. Consequently, if you wish for your plant to produce a hidden
     * easter-egg style product, it should not be listed here!
     *
     * @param products a consumer for collecting all the possible plant products that should be
     * listed.
     */
    void getAllPossibleProducts(@Nonnull Consumer<ItemStack> products);

    /**
     * Retrieves the products that would be produced upon harvesting the given plant, with the given stats.
     * This method will always be called on harvest events, including any time that the containing crop is broken.
     * As such, it is important as to check the actual passed growth value of the plant,
     * given that the plant is not guaranteed to be mature when this method is called.
     *
     * Unless the seed is directly a fruit, it should not be added to the products here,
     * as AgriCraft will take care of this internally
     *
     * @param products a consumer for collecting all the possible plant harvest products that should be dropped.
     * @param growthStage the growth stage
     * @param stats the stats associated with this instance of the plant.
     * @param rand a random for use in rng.
     */
    void getHarvestProducts(@Nonnull Consumer<ItemStack> products, @Nonnull IAgriGrowthStage growthStage, @Nonnull IAgriStatsMap stats, @Nonnull Random rand);

    /**
     * Retrieves all possible products of clipping this plant. Note, this function is to be used
     * for informational purposes only, i.e. for display in the seed journal and JEI. As such, this
     * method is not required as to provide the complete set of all items that this plant could
     * actually drop on a clip event, but rather is required to list all the products that should
     * show up in the journal. Consequently, if you wish for your plant to produce a hidden
     * easter-egg style product, it should not be listed here!
     *
     * @param products a consumer for collecting all the possible clip products that should be listed.
     */
    void getAllPossibleClipProducts(@Nonnull Consumer<ItemStack> products);

    /**
     * Retrieves the products that would be produced upon clipping the given plant, with the given stats.
     * This method will always be called on clip events.
     * As such, it is important as to check the actual passed growth value of the plant,
     * given that the plant is not guaranteed to be mature when this method is called.
     *
     * @param products a consumer for collecting all the possible plant clip products that should be dropped.
     * @param clipper the ItemStack holding the clipper
     * @param growthStage the growth stage
     * @param stats the stats associated with this instance of the plant.
     * @param rand a random for use in rng.
     */
    void getClipProducts(@Nonnull Consumer<ItemStack> products, @Nonnull ItemStack clipper, @Nonnull IAgriGrowthStage growthStage,
                         @Nonnull IAgriStatsMap stats, @Nonnull Random rand);

    /**
     * Checks if this plant allows to be cloned (spreading from a single parent)
     * @param stage the growth stage of the plant
     * @return true if this plant can single spread
     */
    boolean allowsCloning(IAgriGrowthStage stage);

    /**
     * Spawn custom particles if the current growth stage allow it.
     *
     * @param crop the crop on which this is planted.
     * @param rand a random for use in rng.
     */
    void spawnParticles(@Nonnull IAgriCrop crop, Random rand);

    /**
     * Checks if a plant can be harvested at the given growth stage
     * @param stage the growth stage
     * @param entity the entity who wants to harvest the plant (can be null in case it isn't harvested by an entity)
     * @return true if the plant can be harvested
     */
    default boolean allowsHarvest(@Nonnull IAgriGrowthStage stage, @Nullable LivingEntity entity) {
        return stage.isMature();
    }

    /**
     * Checks if a plant can be clipped at the given growth stage
     * @param stage the growth stage
     * @param clipper the ItemStack holding the clipper
     * @param entity the entity who wants to clip the plant (can be null in case it isn't harvested by an entity)
     * @return true if the plant can be harvested
     */
    default boolean allowsClipping(@Nonnull IAgriGrowthStage stage, @Nonnull ItemStack clipper, @Nullable LivingEntity entity) {
        return stage.isMature();
    }

    /**
     * Returns the light level this plant emits at the given crop
     * In vanilla Agricraft this is used by the pickle crop and glowstone resource crop
     * @param crop the crop on which this is planted
     * @return the light level emitted
     */
    default int getBrightness(@Nonnull IAgriCrop crop) {
        return 0;
    }

    /**
     * Returns the redstone power this plant outputs at the given crop
     * In vanilla Agricraft this is only used by the redstone resource crop
     * @param crop the crop on which this is planted
     * @return the redstone power emitted (both strong and weak)
     */
    default int getRedstonePower(@Nonnull IAgriCrop crop) {
        return 0;
    }

    /**
     * Callback for custom actions right after this plant has been planted on crop sticks,
     * does nothing by default, but can be overridden for special behaviours
     * @param crop the crop on which this plant was planted
     * @param entity the entity who planted the plant (can be null in case it wasn't planted by an entity)
     */
    default void onPlanted(@Nonnull IAgriCrop crop, @Nullable LivingEntity entity) {}

    /**
     * Callback for custom actions right after this plant has been spawned on crop sticks due to a mutation or spread,
     * does nothing by default, but can be overridden for special behaviours
     * @param crop the crop on which this plant spawned
     */
    default void onSpawned(@Nonnull IAgriCrop crop) {}

    /**
     * Callback for custom actions right after a successful growth increment,
     * does nothing by default, but can be overridden for special behaviours
     * @param crop the crop on which this plant is planted
     */
    default void onGrowth(@Nonnull IAgriCrop crop) {}

    /**
     * Callback for custom actions right after a plant is removed (for instance by being killed by weeds),
     * does nothing by default, but can be overridden for special behaviours
     * @param crop the crop on which this plant was planted
     */
    default void onRemoved(@Nonnull IAgriCrop crop) {}

    /**
     * Callback for custom actions right after a successful harvest of this plant,
     * does nothing by default, but can be overridden for special behaviours
     * @param crop the crop on which this plant is planted
     * @param entity the entity who harvested the plant (can be null in case it wasn't planted by an entity)
     */
    default void onHarvest(@Nonnull IAgriCrop crop, @Nullable LivingEntity entity) {}

    /**
     * Callback for custom actions right after a successful clipping of this plant,
     * does nothing by default, but can be overridden for special behaviours
     * @param crop the crop on which this plant is planted
     * @param clipper the ItemStack holding the clipper item
     * @param entity the entity who clipped the plant (can be null in case it wasn't clipped by an entity)
     */
    default void onClipped(@Nonnull IAgriCrop crop, @Nonnull ItemStack clipper, @Nullable LivingEntity entity) {}

    /**
     * Callback for custom actions right after crop sticks holding this plant have been broken,
     * does nothing by default, but can be overridden for special behaviours
     * @param crop the crop on which this plant was planted
     * @param entity the entity who broke the crop sticks (can be null in case it wasn't broken by an entity)
     */
    default void onBroken(@Nonnull IAgriCrop crop, @Nullable LivingEntity entity) {}

    /**
     * Callback for custom actions when an entity collides with a crop where this plant is planted
     * does nothing by default, but can be overridden for special behaviours
     * @param crop the crop on which this plant was planted
     * @param entity the entity which collided
     */
    default void onEntityCollision(@Nonnull IAgriCrop crop, Entity entity) {}

    /**
     * Callback for custom actions before a crop is right clicked,
     * does nothing by default, but can be overridden for special behaviours.
     * Runs before default right click behaviour.
     *
     * @param crop the crop on which this plant was planted
     * @param stack the stack with which was right clicked on the crop
     * @param entity the entity which used the item, can be null if usage happens through automation
     * @return an empty optional to allow continuation of default right click behaviour,
     * an optional containing an action result to pass to the right click chain, prevents default behaviour
     */
    default Optional<InteractionResult> onRightClickPre(@Nonnull IAgriCrop crop, @Nonnull ItemStack stack, @Nullable Entity entity) {
        return Optional.empty();
    }

    /**
     * Callback for custom actions after a crop is right clicked,
     * does nothing by default, but can be overridden for special behaviours.
     * Runs after default right click behaviour.
     *
     * @param crop the crop on which this plant was planted
     * @param stack the stack with which was right clicked on the crop
     * @param entity the entity which used the item, can be null if usage happens through automation
     * @return an optional containing an action result to pass to the right click chain, or empty to continue the default chain
     */
    default Optional<InteractionResult> onRightClickPost(@Nonnull IAgriCrop crop, @Nonnull ItemStack stack, @Nullable Entity entity) {
        return Optional.empty();
    }

    /**
     * Method to check if this is an actual plant, or the default, non-null no_plant object
     *
     * Internal, do not override, used by AgriCraft to determine the default, no plant implementation
     *
     * @return true
     */
    default boolean isPlant() {
        return true;
    }



    /* ------------------------------------------------------------------- */
    /* Default IAllele<IAgriPlant> method implementations, do not override */
    /* ------------------------------------------------------------------- */

    @Override
    default IAgriGene<IAgriPlant> gene() {
        return AgriApi.getGeneRegistry().getPlantGene();
    }

    @Override
    default IAgriPlant trait() {
        return this;
    }

    @Override
    default boolean isDominant(IAllele<IAgriPlant> other) {
        // If the plants are equal, it doesn't matter which one is dominant and we can simply return true
        if(!this.isPlant()) {
            return false;
        }
        if(!other.trait().isPlant()) {
            return true;
        }
        if(this.equals(other)) {
            return true;
        }
        // Fetch complexity of both plants
        int a = AgriApi.getMutationRegistry().complexity(this);
        int b = AgriApi.getMutationRegistry().complexity(other.trait());
        if(a == b) {
            // Equal complexity, therefore we use an arbitrary definition for dominance, which we will base on the plant id
            return this.getId().compareTo(other.trait().getId()) < 0;
        }
        // Having more difficult obtain plants be dominant will be more challenging to deal with than having them recessive
        return a > b;
    }

    @Override
    default int comparatorValue() {
        // We don't care about species when comparing genomes
        return 0;
    }


    @Nonnull
    @Override
    default CompoundTag writeToNBT() {
        CompoundTag tag = new CompoundTag();
        tag.putString("agri_plant", this.getId());
        return tag;
    }

    @Override
    default MutableComponent getTooltip() {
        return this.getPlantName();
    }
}
