package electroblob.wizardry.client.audio;

import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.audio.PositionedSound;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

/** Abstract base class for sound loops associated with spells; see subclasses below for implementations. */
public abstract class SoundLoopSpell extends SoundLoop {

	private final Spell spell;

	public SoundLoopSpell(SoundEvent start, SoundEvent loop, SoundEvent end, ISoundFactory factory, Spell spell){
		super(start, loop, end, WizardrySounds.SPELLS, factory);
		this.spell = spell;
	}

	@Override
	public void update(){
		// This may be a bit overkill but I might as well put functionality in superclasses where possible
		if(stillCasting(spell)){
			// This can't called in the same tick as endLoop because otherwise, if the spell casting stops during the
			// same tick as the transition to the loop sound it will try and stop the loop immediately after it has
			// started - and for whatever reason, this causes the 'Channel null in method stop' error.
			super.update();
		}else{
			endLoop();
		}
	}

	protected abstract boolean stillCasting(Spell spell);

	/** Implements a sound loop for continuous spells cast by entities. */
	public static class SoundLoopSpellEntity extends SoundLoopSpell {

		private final EntityLivingBase source;

		public SoundLoopSpellEntity(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, EntityLivingBase source, float volume, float pitch){
			super(start, loop, end, (sound, category, repeat) -> new MovingSoundEntity<>(source, sound, category, volume, pitch, repeat), spell);
			this.source = source;
		}

		@Override
		protected boolean stillCasting(Spell spell){
			return WizardryUtilities.isCasting(source, spell);
		}
	}

	public static abstract class SoundLoopSpellPosition extends SoundLoopSpell {

		public SoundLoopSpellPosition (SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell,
									   double x, double y, double z, float sndVolume, float sndPitch){
			// Huh, I actually found a use for a non-static initialiser block - hence the double curly brackets...
			super(start, loop, end, (sound, category, r) -> new PositionedSound(sound, category){{
				// ...et voila, we can just set protected fields as we please using external variables
				this.xPosF = (float)x;
				this.yPosF = (float)y;
				this.zPosF = (float)z;
				this.repeat = r;
				this.volume = sndVolume;
				this.pitch = sndPitch;
			}}, spell);
		}
	}

	/** Implements a sound loop for continuous spells cast by dispensers. */
	public static class SoundLoopSpellDispenser extends SoundLoopSpellPosition {

		private final TileEntityDispenser source;

		public SoundLoopSpellDispenser(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, World world,
									   double x, double y, double z, float sndVolume, float sndPitch){
			super(start, loop, end, spell, x, y, z, sndVolume, sndPitch);

			TileEntity tileentity = world.getTileEntity(new BlockPos(x, y, z));

			if(tileentity instanceof TileEntityDispenser) this.source = (TileEntityDispenser)tileentity;
			else throw new NullPointerException(String.format("Playing continuous spell sound: no dispenser found at %s, %s, %s", x, y, z));
		}

		@Override
		protected boolean stillCasting(Spell spell){
			return DispenserCastingData.get(source).currentlyCasting() == spell;
		}

	}

	/** Implements a sound loop for continuous spells cast at a position (via commands). */
	public static class SoundLoopSpellPosTimed extends SoundLoopSpellPosition {

		private int timeLeft;

		public SoundLoopSpellPosTimed(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, int duration,
								double x, double y, double z, float sndVolume, float sndPitch){
			super(start, loop, end, spell, x, y, z, sndVolume, sndPitch);
			this.timeLeft = duration;
		}

		@Override
		public void update(){
			super.update();
			timeLeft--;
		}

		@Override
		protected boolean stillCasting(Spell spell){
			return timeLeft > 0;
		}
	}
}
