package ice.library.entities.bullet;

import arc.func.Cons;
import arc.graphics.Color;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.Fill;
import arc.graphics.g2d.Lines;
import arc.math.Mathf;
import arc.math.Rand;
import arc.math.geom.Geometry;
import arc.math.geom.Position;
import arc.math.geom.Rect;
import arc.math.geom.Vec2;
import arc.struct.FloatSeq;
import arc.struct.Seq;
import arc.util.Tmp;
import ice.library.struct.Vec2Seq;
import mindustry.Vars;
import mindustry.content.Fx;
import mindustry.content.StatusEffects;
import mindustry.core.World;
import mindustry.entities.Effect;
import mindustry.entities.Lightning;
import mindustry.entities.Units;
import mindustry.entities.bullet.BulletType;
import mindustry.game.Team;
import mindustry.gen.Building;
import mindustry.gen.Bullet;
import mindustry.gen.Entityc;
import mindustry.gen.Healthc;
import mindustry.graphics.Layer;

import static arc.graphics.g2d.Draw.color;
import static arc.graphics.g2d.Lines.lineAngle;
import static arc.graphics.g2d.Lines.stroke;
import static arc.math.Angles.randLenVectors;

/**
 * 提供可以生成位置到位置闪电的方法。<p>
 * {@code Tmp} <b>var</b> 可用。<p>
 * 完全独立的课程。<p>
 *
 * @implNote 该方法实现<b>点到点的</b>随机闪电效果的生成，并通过{@link Cons}在<b>目标点</b>完成一定动作。<p>
 * @apiNote
 * <li> {@code hitPointMovement} {@link Cons} 用于在目标点运行特定动作。
 * <li> {@code WIDTH}：{@value WIDTH} 用于控制闪电的笔触。
 * <li> {@code RANGE_RAND}： {@value RANGE_RAND} 用于控制闪电每个部分的基本 xRand 范围。
 * <li> {@code ROT_DST}：{@value ROT_DST} 用于控制闪电每个部分的长度。<p>
 *
 * @see Position
 * @see Vec2
 * @see Geometry
 * @see Cons
 *
 * @author Yuria
 */
public class PosLightning {
	public static final BulletType hitter = new EffectBulletType(5f){{
		absorbable = true;
		collides = collidesAir = collidesGround = collidesTiles = true;
		status = StatusEffects.shocked;
		statusDuration = 10f;
		hittable = false;
	}};
	
	/**Spawns Nothing at the hit point.*/
	public static final Cons<Position> none = p -> {};
	/**Lighting Effect Lifetime.*/
	public static final float lifetime = Fx.chainLightning.lifetime;
	/**Lighting Effect Default Width, apply it manually.*/
	public static final float WIDTH = 2.5f;
	/**Lighting Effect X-Rand.*/
	public static final float RANGE_RAND = 5f;
	/**Lighting Effect Length Between Nodes.*/
	public static final float ROT_DST = Vars.tilesize * 0.6f;
	/**Used for range spawn, make the lightning more random and has smoother spacing.*/
	public static float trueHitChance = 1;
	
	
	/**(0, 1]*/
	public static void setHitChance(float f){
		trueHitChance = f;
	}
	
	/**Must Hit*/
	public static void setHitChanceDef(){
		trueHitChance = 1;
	}
	
	//Lightning's randX. Modify it if needed.
	private static float getBoltRandomRange() {return Mathf.random(1f, 7f); }
	
	
	public static final Effect posLightning = (new Effect(lifetime, 1200.0f, e -> {
		if(!(e.data instanceof Vec2Seq)) return;
		Vec2Seq lines = e.data();
		
		Draw.color(e.color, Color.white, e.fout() * 0.6f);
		
		Lines.stroke(e.rotation * e.fout());
		
		Fill.circle(lines.firstTmp().x, lines.firstTmp().y, Lines.getStroke() / 2f);
		
		for(int i = 0; i < lines.size() - 1; i++){
			Vec2 cur = lines.setVec2(i, Tmp.v1);
			Vec2 next = lines.setVec2(i + 1, Tmp.v2);
			
			Lines.line(cur.x, cur.y, next.x, next.y, false);
			Fill.circle(next.x, next.y, Lines.getStroke() / 2f);
		}
	})).layer(Layer.effect - 0.001f);
	
	private static Building furthest;
	private static final Rect rect = new Rect();
	private static final Rand rand = new Rand();
	private static final FloatSeq floatSeq = new FloatSeq();
	private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2(), tmp3 = new Vec2();
	//METHODS

	//create lightning to the enemies in range.
	
	//A radius create method that with a Bullet owner.
	public static void createRange(Bullet owner, float range, int maxHit, Color color, boolean createSubLightning, float width, int lightningNum, Cons<Position> hitPointMovement) {
		createRange(owner, owner, owner.team, range, maxHit, color, createSubLightning, 0, 0, width, lightningNum, hitPointMovement);
	}
	
	public static void createRange(Bullet owner, boolean hitAir, boolean hitGround, Position from, Team team, float range, int maxHit, Color color, boolean createSubLightning, float damage, int subLightningLength, float width, int lightningNum, Cons<Position> hitPointMovement) {
		Seq<Healthc> entities = new Seq<>();
		whetherAdd(entities, team, rect.setSize(range * 2f).setCenter(from.getX(), from.getY()), maxHit, hitGround, hitAir);
		for (Healthc p : entities)create(owner, team, from, p, color, createSubLightning, damage, subLightningLength, width, lightningNum, hitPointMovement);
	}
	
	
	public static void createRange(Bullet owner, Position from, Team team, float range, int maxHit, Color color, boolean createSubLightning, float damage, int subLightningLength, float width, int lightningNum, Cons<Position> hitPointMovement) {
		createRange(owner, owner == null || owner.type.collidesAir, owner == null || owner.type.collidesGround, from, team, range, maxHit, color, createSubLightning, damage, subLightningLength, width, lightningNum, hitPointMovement);
	}
	
	public static void createLength(Bullet owner, Team team, Position from, float length, float angle, Color color, boolean createSubLightning, float damage, int subLightningLength, float width, int lightningNum, Cons<Position> hitPointMovement){
		create(owner, team, from, tmp2.trns(angle, length).add(from), color, createSubLightning, damage, subLightningLength, width, lightningNum, hitPointMovement);
	}
	
	//A create method that with a Bullet owner.
	public static void create(Entityc owner, Team team, Position from, Position target, Color color, boolean createSubLightning, float damage, int subLightningLength, float lightningWidth, int lightningNum, Cons<Position> hitPointMovement) {
		if(!Mathf.chance(trueHitChance))return;
		Position sureTarget = findInterceptedPoint(from, target, team);
		hitPointMovement.get(sureTarget);

		if (damage > 0){
			if(createSubLightning){
				if(owner instanceof Bullet){
					Bullet b = (Bullet)owner;
					for(int i = 0; i < b.type.lightning; i++)Lightning.create(b, color, b.type.lightningDamage < 0.0F ? b.damage : b.type.lightningDamage, sureTarget.getX(), sureTarget.getY(), b.rotation() + Mathf.range(b.type.lightningCone / 2.0F) + b.type.lightningAngle, b.type.lightningLength + Mathf.random(b.type.lightningLengthRand));
				}
				else for(int i = 0; i < 3; i++)Lightning.create(team, color, damage, sureTarget.getX(), sureTarget.getY(), Mathf.random(360f), subLightningLength);
			}

            hitter.create(owner, team, sureTarget.getX(), sureTarget.getY(), 1).damage(damage);
		}
		
		createEffect(from, sureTarget, color, lightningNum, lightningWidth);
	}

	public static void createRandom(Bullet owner, Team team, Position from, float rand, Color color, boolean createSubLightning, float damage, int subLightningLength, float width, int lightningNum, Cons<Position> hitPointMovement){
		create(owner, team, from, tmp2.rnd(rand).scl(Mathf.random(1f)).add(from), color, createSubLightning, damage, subLightningLength, width, lightningNum, hitPointMovement);
	}
	
	public static void createRandom(Team team, Position from, float rand, Color color, boolean createSubLightning, float damage, int subLightningLength, float width, int lightningNum, Cons<Position> hitPointMovement){
		createRandom(null, team, from, rand, color, createSubLightning, damage, subLightningLength, width, lightningNum, hitPointMovement);
	}
	
	public static void createRandomRange(Team team, Position from, float rand, Color color, boolean createSubLightning, float damage, int subLightningLength, float width, int lightningNum, int generateNum, Cons<Position> hitPointMovement){
		createRandomRange(null, team, from, rand, color, createSubLightning, damage, subLightningLength, width, lightningNum, generateNum, hitPointMovement);
	}
	
	public static void createRandomRange(Bullet owner, float rand, Color color, boolean createSubLightning, float damage, float width, int lightningNum, int generateNum, Cons<Position> hitPointMovement){
		createRandomRange(owner, owner.team, owner, rand, color, createSubLightning, damage, owner.type.lightningLength + Mathf.random(owner.type.lightningLengthRand), width, lightningNum, generateNum, hitPointMovement);
	}
	
	public static void createRandomRange(Bullet owner, Team team, Position from, float rand, Color color, boolean createSubLightning, float damage, int subLightningLength, float width, int lightningNum, int generateNum, Cons<Position> hitPointMovement){
		for (int i = 0; i < generateNum; i++) {
			createRandom(owner, team, from, rand, color, createSubLightning, damage, subLightningLength, width, lightningNum, hitPointMovement);
		}
	}
	
	public static void createEffect(Position from, float length, float angle, Color color, int lightningNum, float width){
		if(Vars.headless)return;
		createEffect(from, tmp2.trns(angle, length).add(from), color, lightningNum, width);
	}
	
	public static void createEffect(Position from, Position to, Color color, int lightningNum, float width){
		if(Vars.headless)return;
		
		if(lightningNum < 1){
			Fx.chainLightning.at(from.getX(), from.getY(), 0, color, new Vec2().set(to));
		}else{
			float dst = from.dst(to);
			
			for(int i = 0; i < lightningNum; i++){
				float len = getBoltRandomRange();
				float randRange = len * RANGE_RAND;
				
				floatSeq.clear();
				FloatSeq randomArray = floatSeq;
				for(int num = 0; num < dst / (ROT_DST * len) + 1; num++){
					randomArray.add(Mathf.range(randRange) / (num * 0.025f + 1));
				}
				createBoltEffect(color, width, computeVectors(randomArray, from, to));
			}
		}
		
	}
	
	//计算正确的命中位置。
	public static Position findInterceptedPoint(Position from, Position target, Team fromTeam) {
		furthest = null;
		return Geometry.raycast(
			World.toTile(from.getX()),
			World.toTile(from.getY()),
			World.toTile(target.getX()),
			World.toTile(target.getY()),
			(x, y) -> (furthest = Vars.world.build(x, y)) != null && furthest.team() != fromTeam && furthest.block.insulated
		) && furthest != null ? furthest : target;
	}
	
	//Add proper unit into the to hit Seq.
	private static void whetherAdd(Seq<Healthc> points, Team team, Rect selectRect, int maxHit, boolean targetGround, boolean targetAir) {
		Units.nearbyEnemies(team, selectRect, unit -> {
			if(unit.checkTarget(targetAir, targetGround))points.add(unit);
		});
		
		if(targetGround){
			selectRect.getCenter(tmp3);
			Units.nearbyBuildings(tmp3.x, tmp3.y, selectRect.getHeight() / 2, b -> {
				if(b.team != team && b.isValid())points.add(b);
			});
		}
		
		points.shuffle();
		points.truncate(maxHit);
	}

	//create lightning effect.
	public static void createBoltEffect(Color color, float width, Vec2Seq vets) {
			vets.each(((x, y) -> {
				if(Mathf.chance(0.0855)){
					lightningSpark.at(x, y, rand.random(2f + width, 4f + width), color);
				};
			}));
		posLightning.at((vets.firstTmp().x + vets.peekTmp().x) / 2f, (vets.firstTmp().y + vets.peekTmp().y) / 2f, width, color, vets);
	}
	
	private static Vec2Seq computeVectors(FloatSeq randomVec, Position from, Position to){
		int param = randomVec.size;
		float angle = from.angleTo(to);
		
		Vec2Seq lines = new Vec2Seq(param);
		tmp1.trns(angle, from.dst(to) / (param - 1));
		
		lines.add(from);
		for (int i = 1; i < param - 2; i ++)lines.add(tmp3.trns(angle - 90, randomVec.get(i)).add(tmp1, i).add(from.getX(), from.getY()));
		lines.add(to);
		
		return lines;
	}
	private static Effect lightningSpark = new Effect(Fx.chainLightning.lifetime, e -> {
		color(Color.white, e.color, e.fin() + 0.25f);

		stroke(0.65f + e.fout());

		randLenVectors(e.id, 3, e.fin() * e.rotation + 6f, (x, y) -> lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), e.fout() * 4 + 2f));

		Fill.circle(e.x, e.y, 2.5f * e.fout());
	});
}
