package com.infinityraider.agricraft.impl.v1.irrigation;

import com.infinityraider.agricraft.reference.AgriNBT;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;

import java.util.function.Consumer;

public class IrrigationNetworkLayer {
    private final double min;
    private final double max;
    private final int volume;

    public IrrigationNetworkLayer(CompoundTag tag) {
        this.min = tag.contains(AgriNBT.Y1) ? tag.getDouble(AgriNBT.Y1) : 0;
        this.max = tag.contains(AgriNBT.Y2) ? tag.getDouble(AgriNBT.Y2) : 0;
        this.volume = tag.contains(AgriNBT.LEVEL) ? tag.getInt(AgriNBT.LEVEL) : 0;
    }

    public IrrigationNetworkLayer(double min, double max, int volume) {
        this.min = min;
        this.max = max;
        this.volume = volume;
    }

    public double getMin() {
        return this.min;
    }

    public double getMax() {
        return this.max;
    }

    public int getVolume() {
        return this.volume;
    }

    public int getCapacity(double height) {
        if(height <= this.getMin()) {
            return 0;
        }
        if(height >= this.getMax()) {
            return this.getVolume();
        }
        return (int) (this.getVolume() * (height - this.getMin()) / (this.getMax() - this.getMin()));
    }

    public double getHeight(int content) {
        if(content <= 0) {
            return this.getMin();
        }
        if(content >= this.getVolume()) {
            return this.getMax();
        }
        double f = (content + 0.0D)/this.getVolume();
        return Mth.lerp(f, this.getMin(), this.getMax());
    }

    public CompoundTag writeToTag() {
        CompoundTag tag = new CompoundTag();
        tag.putDouble(AgriNBT.Y1, this.getMin());
        tag.putDouble(AgriNBT.Y2, this.getMax());
        tag.putInt(AgriNBT.LEVEL, this.getVolume());
        return tag;
    }

    public void split(double level, Consumer<IrrigationNetworkLayer> consumer) {
        if(level <= this.getMin() || level >= this.getMax()) {
            consumer.accept(this);
        } else {
            double f = (level - this.getMin())/(this.getMax() - this.getMin());
            IrrigationNetworkLayer lower = new IrrigationNetworkLayer(
                    this.getMin(), level, (int) f * this.getVolume());
            IrrigationNetworkLayer upper = new IrrigationNetworkLayer(
                    level, this.getMax(), this.getVolume() - lower.getVolume());
            if(lower.getVolume() > 0) {
                consumer.accept(lower);
            }
            if(upper.getVolume() > 0) {
                consumer.accept(upper);
            }
        }
    }

    public boolean merge(IrrigationNetworkLayer other, Consumer<IrrigationNetworkLayer> consumer) {
        if(this.getMin() != other.getMin() || this.getMax() != other.getMax()) {
            return false;
        }
        consumer.accept(new IrrigationNetworkLayer(this.getMin(), this.getMax(), this.getVolume() + other.getVolume()));
        return true;
    }

    public void distinct(IrrigationNetworkLayer other, Consumer<IrrigationNetworkLayer> consumer) {
        if(this.merge(other, consumer)) {
            // The layers have identical limits, the merging was successful
            return;
        }
        if(this.getMax() <= other.getMin() || this.getMin() >= other.getMax()) {
            // The layers do not overlap, pass them to the consumer
            consumer.accept(this);
            consumer.accept(other);
        } else {
            // The layers overlap
            if(this.getMin() < other.getMin()) {
                if(this.getMax() > other.getMax()) {
                    // this range envelopes the other range
                    this.envelope(other, consumer);
                } else {
                    // overlap from below
                    this.overlapFromBelow(other, consumer);
                }
            } else {
                if(this.getMax() > other.getMax()) {
                    // overlap from above
                    this.overlapFromAbove(other, consumer);
                } else {
                    // the other range envelopes this range
                    other.envelope(this, consumer);
                }
            }
        }
    }

    private void envelope(IrrigationNetworkLayer other, Consumer<IrrigationNetworkLayer> consumer) {
        this.split(other.getMin(), (split1) -> {
            if(split1.getMax() > other.getMax()) {
                split1.split(other.getMax(), (split2) -> {
                    if(split2.getMax() > other.getMax()) {
                        consumer.accept(split2);
                    } else {
                        other.merge(split2, consumer);
                    }
                });
            } else {
                consumer.accept(split1);
            }
        });
    }

    private void overlapFromAbove(IrrigationNetworkLayer other, Consumer<IrrigationNetworkLayer> consumer) {
        other.overlapFromBelow(this, consumer);
    }

    private void overlapFromBelow(IrrigationNetworkLayer other, Consumer<IrrigationNetworkLayer> consumer) {
        this.split(other.getMin(), (split1) -> {
            if(split1.getMax() > other.getMin()) {
                other.split(split1.getMax(), (split2) -> {
                    if(split2.getMax() > this.getMax()) {
                        consumer.accept(split2);
                    } else {
                        split1.merge(split2, consumer);
                    }
                });
            } else {
                consumer.accept(split1);
            }
        });
    }
}
