package net.minecraft.world.entity.npc;

import net.minecraft.world.entity.ai.sensing.NearestLivingEntitySensor;
import com.google.common.collect.ImmutableMap;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.world.entity.ai.memory.WalkTarget;
import net.minecraft.world.entity.ai.gossip.GossipType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.entity.animal.IronGolem;
import net.minecraft.world.phys.AABB;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.monster.Witch;
import net.minecraft.world.Difficulty;
import net.minecraft.world.entity.LightningBolt;
import net.minecraft.world.entity.AgableMob;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.core.Registry;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.server.MinecraftServer;
import net.minecraft.network.protocol.game.DebugPackets;
import java.util.Optional;
import java.util.List;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ExperienceOrb;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.nbt.ListTag;
import com.mojang.serialization.DataResult;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.nbt.Tag;
import java.util.function.Consumer;
import com.mojang.serialization.DynamicOps;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;
import java.util.Iterator;
import net.minecraft.world.item.trading.MerchantOffer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.item.ItemStack;
import net.minecraft.stats.Stats;
import net.minecraft.world.item.Items;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.raid.Raid;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ai.village.ReputationEventType;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import com.google.common.collect.ImmutableSet;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.entity.ai.behavior.Behavior;
import com.mojang.datafixers.util.Pair;
import net.minecraft.world.entity.ai.behavior.VillagerGoalPackages;
import net.minecraft.world.entity.schedule.Activity;
import net.minecraft.world.entity.schedule.Schedule;
import net.minecraft.server.level.ServerLevel;
import com.mojang.serialization.Dynamic;
import java.util.Collection;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.ai.village.poi.PoiType;
import java.util.function.BiPredicate;
import net.minecraft.core.GlobalPos;
import net.minecraft.world.entity.ai.sensing.Sensor;
import net.minecraft.world.entity.ai.sensing.SensorType;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import com.google.common.collect.ImmutableList;
import net.minecraft.world.entity.ai.gossip.GossipContainer;
import javax.annotation.Nullable;
import net.minecraft.world.entity.player.Player;
import java.util.Set;
import net.minecraft.world.item.Item;
import java.util.Map;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.world.entity.ReputationEventHandler;

public class Villager extends AbstractVillager implements ReputationEventHandler, VillagerDataHolder {
    private static final EntityDataAccessor<VillagerData> DATA_VILLAGER_DATA;
    public static final Map<Item, Integer> FOOD_POINTS;
    private static final Set<Item> WANTED_ITEMS;
    private int updateMerchantTimer;
    private boolean increaseProfessionLevelOnUpdate;
    @Nullable
    private Player lastTradedPlayer;
    private byte foodLevel;
    private final GossipContainer gossips;
    private long lastGossipTime;
    private long lastGossipDecayTime;
    private int villagerXp;
    private long lastRestockGameTime;
    private int numberOfRestocksToday;
    private long lastRestockCheckDayTime;
    private boolean assignProfessionWhenSpawned;
    private static final ImmutableList<MemoryModuleType<?>> MEMORY_TYPES;
    private static final ImmutableList<SensorType<? extends Sensor<? super Villager>>> SENSOR_TYPES;
    public static final Map<MemoryModuleType<GlobalPos>, BiPredicate<Villager, PoiType>> POI_MEMORIES;
    
    public Villager(final EntityType<? extends Villager> aoq, final Level bqb) {
        this(aoq, bqb, VillagerType.PLAINS);
    }
    
    public Villager(final EntityType<? extends Villager> aoq, final Level bqb, final VillagerType bdu) {
        super(aoq, bqb);
        this.gossips = new GossipContainer();
        ((GroundPathNavigation)this.getNavigation()).setCanOpenDoors(true);
        this.getNavigation().setCanFloat(true);
        this.setCanPickUpLoot(true);
        this.setVillagerData(this.getVillagerData().setType(bdu).setProfession(VillagerProfession.NONE));
    }
    
    @Override
    public Brain<Villager> getBrain() {
        return (Brain<Villager>)super.getBrain();
    }
    
    @Override
    protected Brain.Provider<Villager> brainProvider() {
        return Brain.<Villager>provider(Villager.MEMORY_TYPES, Villager.SENSOR_TYPES);
    }
    
    @Override
    protected Brain<?> makeBrain(final Dynamic<?> dynamic) {
        final Brain<Villager> apr3 = this.brainProvider().makeBrain(dynamic);
        this.registerBrainGoals(apr3);
        return apr3;
    }
    
    public void refreshBrain(final ServerLevel zd) {
        final Brain<Villager> apr3 = this.getBrain();
        apr3.stopAll(zd, this);
        this.brain = apr3.copyWithoutBehaviors();
        this.registerBrainGoals(this.getBrain());
    }
    
    private void registerBrainGoals(final Brain<Villager> apr) {
        final VillagerProfession bds3 = this.getVillagerData().getProfession();
        if (this.isBaby()) {
            apr.setSchedule(Schedule.VILLAGER_BABY);
            apr.addActivity(Activity.PLAY, VillagerGoalPackages.getPlayPackage(0.5f));
        }
        else {
            apr.setSchedule(Schedule.VILLAGER_DEFAULT);
            apr.addActivityWithConditions(Activity.WORK, VillagerGoalPackages.getWorkPackage(bds3, 0.5f), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT)));
        }
        apr.addActivity(Activity.CORE, VillagerGoalPackages.getCorePackage(bds3, 0.5f));
        apr.addActivityWithConditions(Activity.MEET, VillagerGoalPackages.getMeetPackage(bds3, 0.5f), ImmutableSet.of(Pair.of(MemoryModuleType.MEETING_POINT, MemoryStatus.VALUE_PRESENT)));
        apr.addActivity(Activity.REST, VillagerGoalPackages.getRestPackage(bds3, 0.5f));
        apr.addActivity(Activity.IDLE, VillagerGoalPackages.getIdlePackage(bds3, 0.5f));
        apr.addActivity(Activity.PANIC, VillagerGoalPackages.getPanicPackage(bds3, 0.5f));
        apr.addActivity(Activity.PRE_RAID, VillagerGoalPackages.getPreRaidPackage(bds3, 0.5f));
        apr.addActivity(Activity.RAID, VillagerGoalPackages.getRaidPackage(bds3, 0.5f));
        apr.addActivity(Activity.HIDE, VillagerGoalPackages.getHidePackage(bds3, 0.5f));
        apr.setCoreActivities(ImmutableSet.<Activity>of(Activity.CORE));
        apr.setDefaultActivity(Activity.IDLE);
        apr.setActiveActivityIfPossible(Activity.IDLE);
        apr.updateActivityFromSchedule(this.level.getDayTime(), this.level.getGameTime());
    }
    
    @Override
    protected void ageBoundaryReached() {
        super.ageBoundaryReached();
        if (this.level instanceof ServerLevel) {
            this.refreshBrain((ServerLevel)this.level);
        }
    }
    
    public static AttributeSupplier.Builder createAttributes() {
        return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.5).add(Attributes.FOLLOW_RANGE, 48.0);
    }
    
    public boolean assignProfessionWhenSpawned() {
        return this.assignProfessionWhenSpawned;
    }
    
    @Override
    protected void customServerAiStep() {
        this.level.getProfiler().push("villagerBrain");
        this.getBrain().tick((ServerLevel)this.level, this);
        this.level.getProfiler().pop();
        if (this.assignProfessionWhenSpawned) {
            this.assignProfessionWhenSpawned = false;
        }
        if (!this.isTrading() && this.updateMerchantTimer > 0) {
            --this.updateMerchantTimer;
            if (this.updateMerchantTimer <= 0) {
                if (this.increaseProfessionLevelOnUpdate) {
                    this.increaseMerchantCareer();
                    this.increaseProfessionLevelOnUpdate = false;
                }
                this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 200, 0));
            }
        }
        if (this.lastTradedPlayer != null && this.level instanceof ServerLevel) {
            ((ServerLevel)this.level).onReputationEvent(ReputationEventType.TRADE, this.lastTradedPlayer, this);
            this.level.broadcastEntityEvent(this, (byte)14);
            this.lastTradedPlayer = null;
        }
        if (!this.isNoAi() && this.random.nextInt(100) == 0) {
            final Raid bfh2 = ((ServerLevel)this.level).getRaidAt(this.blockPosition());
            if (bfh2 != null && bfh2.isActive() && !bfh2.isOver()) {
                this.level.broadcastEntityEvent(this, (byte)42);
            }
        }
        if (this.getVillagerData().getProfession() == VillagerProfession.NONE && this.isTrading()) {
            this.stopTrading();
        }
        super.customServerAiStep();
    }
    
    @Override
    public void tick() {
        super.tick();
        if (this.getUnhappyCounter() > 0) {
            this.setUnhappyCounter(this.getUnhappyCounter() - 1);
        }
        this.maybeDecayGossip();
    }
    
    public InteractionResult mobInteract(final Player bec, final InteractionHand anf) {
        final ItemStack bki4 = bec.getItemInHand(anf);
        if (bki4.getItem() == Items.VILLAGER_SPAWN_EGG || !this.isAlive() || this.isTrading() || this.isSleeping()) {
            return super.mobInteract(bec, anf);
        }
        if (this.isBaby()) {
            this.setUnhappy();
            return InteractionResult.sidedSuccess(this.level.isClientSide);
        }
        final boolean boolean5 = this.getOffers().isEmpty();
        if (anf == InteractionHand.MAIN_HAND) {
            if (boolean5 && !this.level.isClientSide) {
                this.setUnhappy();
            }
            bec.awardStat(Stats.TALKED_TO_VILLAGER);
        }
        if (boolean5) {
            return InteractionResult.sidedSuccess(this.level.isClientSide);
        }
        if (!this.level.isClientSide && !this.offers.isEmpty()) {
            this.startTrading(bec);
        }
        return InteractionResult.sidedSuccess(this.level.isClientSide);
    }
    
    private void setUnhappy() {
        this.setUnhappyCounter(40);
        if (!this.level.isClientSide()) {
            this.playSound(SoundEvents.VILLAGER_NO, this.getSoundVolume(), this.getVoicePitch());
        }
    }
    
    private void startTrading(final Player bec) {
        this.updateSpecialPrices(bec);
        this.setTradingPlayer(bec);
        this.openTradingScreen(bec, this.getDisplayName(), this.getVillagerData().getLevel());
    }
    
    @Override
    public void setTradingPlayer(@Nullable final Player bec) {
        final boolean boolean3 = this.getTradingPlayer() != null && bec == null;
        super.setTradingPlayer(bec);
        if (boolean3) {
            this.stopTrading();
        }
    }
    
    @Override
    protected void stopTrading() {
        super.stopTrading();
        this.resetSpecialPrices();
    }
    
    private void resetSpecialPrices() {
        for (final MerchantOffer boz3 : this.getOffers()) {
            boz3.resetSpecialPriceDiff();
        }
    }
    
    @Override
    public boolean canRestock() {
        return true;
    }
    
    public void restock() {
        this.updateDemand();
        for (final MerchantOffer boz3 : this.getOffers()) {
            boz3.resetUses();
        }
        this.lastRestockGameTime = this.level.getGameTime();
        ++this.numberOfRestocksToday;
    }
    
    private boolean needsToRestock() {
        for (final MerchantOffer boz3 : this.getOffers()) {
            if (boz3.needsRestock()) {
                return true;
            }
        }
        return false;
    }
    
    private boolean allowedToRestock() {
        return this.numberOfRestocksToday == 0 || (this.numberOfRestocksToday < 2 && this.level.getGameTime() > this.lastRestockGameTime + 2400L);
    }
    
    public boolean shouldRestock() {
        final long long2 = this.lastRestockGameTime + 12000L;
        final long long3 = this.level.getGameTime();
        boolean boolean6 = long3 > long2;
        final long long4 = this.level.getDayTime();
        if (this.lastRestockCheckDayTime > 0L) {
            final long long5 = this.lastRestockCheckDayTime / 24000L;
            final long long6 = long4 / 24000L;
            boolean6 |= (long6 > long5);
        }
        this.lastRestockCheckDayTime = long4;
        if (boolean6) {
            this.lastRestockGameTime = long3;
            this.resetNumberOfRestocks();
        }
        return this.allowedToRestock() && this.needsToRestock();
    }
    
    private void catchUpDemand() {
        final int integer2 = 2 - this.numberOfRestocksToday;
        if (integer2 > 0) {
            for (final MerchantOffer boz4 : this.getOffers()) {
                boz4.resetUses();
            }
        }
        for (int integer3 = 0; integer3 < integer2; ++integer3) {
            this.updateDemand();
        }
    }
    
    private void updateDemand() {
        for (final MerchantOffer boz3 : this.getOffers()) {
            boz3.updateDemand();
        }
    }
    
    private void updateSpecialPrices(final Player bec) {
        final int integer3 = this.getPlayerReputation(bec);
        if (integer3 != 0) {
            for (final MerchantOffer boz5 : this.getOffers()) {
                boz5.addToSpecialPriceDiff(-Mth.floor(integer3 * boz5.getPriceMultiplier()));
            }
        }
        if (bec.hasEffect(MobEffects.HERO_OF_THE_VILLAGE)) {
            final MobEffectInstance aog4 = bec.getEffect(MobEffects.HERO_OF_THE_VILLAGE);
            final int integer4 = aog4.getAmplifier();
            for (final MerchantOffer boz6 : this.getOffers()) {
                final double double8 = 0.3 + 0.0625 * integer4;
                final int integer5 = (int)Math.floor(double8 * boz6.getBaseCostA().getCount());
                boz6.addToSpecialPriceDiff(-Math.max(integer5, 1));
            }
        }
    }
    
    @Override
    protected void defineSynchedData() {
        super.defineSynchedData();
        this.entityData.<VillagerData>define(Villager.DATA_VILLAGER_DATA, new VillagerData(VillagerType.PLAINS, VillagerProfession.NONE, 1));
    }
    
    @Override
    public void addAdditionalSaveData(final CompoundTag le) {
        super.addAdditionalSaveData(le);
        VillagerData.CODEC.encodeStart((DynamicOps)NbtOps.INSTANCE, this.getVillagerData()).resultOrPartial((Consumer)Villager.LOGGER::error).ifPresent(lu -> le.put("VillagerData", lu));
        le.putByte("FoodLevel", this.foodLevel);
        le.put("Gossips", (Tag)this.gossips.store((com.mojang.serialization.DynamicOps<Object>)NbtOps.INSTANCE).getValue());
        le.putInt("Xp", this.villagerXp);
        le.putLong("LastRestock", this.lastRestockGameTime);
        le.putLong("LastGossipDecay", this.lastGossipDecayTime);
        le.putInt("RestocksToday", this.numberOfRestocksToday);
        if (this.assignProfessionWhenSpawned) {
            le.putBoolean("AssignProfessionWhenSpawned", true);
        }
    }
    
    @Override
    public void readAdditionalSaveData(final CompoundTag le) {
        super.readAdditionalSaveData(le);
        if (le.contains("VillagerData", 10)) {
            final DataResult<VillagerData> dataResult3 = (DataResult<VillagerData>)VillagerData.CODEC.parse(new Dynamic((DynamicOps)NbtOps.INSTANCE, le.get("VillagerData")));
            dataResult3.resultOrPartial((Consumer)Villager.LOGGER::error).ifPresent(this::setVillagerData);
        }
        if (le.contains("Offers", 10)) {
            this.offers = new MerchantOffers(le.getCompound("Offers"));
        }
        if (le.contains("FoodLevel", 1)) {
            this.foodLevel = le.getByte("FoodLevel");
        }
        final ListTag lk3 = le.getList("Gossips", 10);
        this.gossips.update(new Dynamic((DynamicOps)NbtOps.INSTANCE, lk3));
        if (le.contains("Xp", 3)) {
            this.villagerXp = le.getInt("Xp");
        }
        this.lastRestockGameTime = le.getLong("LastRestock");
        this.lastGossipDecayTime = le.getLong("LastGossipDecay");
        this.setCanPickUpLoot(true);
        if (this.level instanceof ServerLevel) {
            this.refreshBrain((ServerLevel)this.level);
        }
        this.numberOfRestocksToday = le.getInt("RestocksToday");
        if (le.contains("AssignProfessionWhenSpawned")) {
            this.assignProfessionWhenSpawned = le.getBoolean("AssignProfessionWhenSpawned");
        }
    }
    
    @Override
    public boolean removeWhenFarAway(final double double1) {
        return false;
    }
    
    @Nullable
    @Override
    protected SoundEvent getAmbientSound() {
        if (this.isSleeping()) {
            return null;
        }
        if (this.isTrading()) {
            return SoundEvents.VILLAGER_TRADE;
        }
        return SoundEvents.VILLAGER_AMBIENT;
    }
    
    @Override
    protected SoundEvent getHurtSound(final DamageSource anw) {
        return SoundEvents.VILLAGER_HURT;
    }
    
    @Override
    protected SoundEvent getDeathSound() {
        return SoundEvents.VILLAGER_DEATH;
    }
    
    public void playWorkSound() {
        final SoundEvent ack2 = this.getVillagerData().getProfession().getWorkSound();
        if (ack2 != null) {
            this.playSound(ack2, this.getSoundVolume(), this.getVoicePitch());
        }
    }
    
    public void setVillagerData(final VillagerData bdq) {
        final VillagerData bdq2 = this.getVillagerData();
        if (bdq2.getProfession() != bdq.getProfession()) {
            this.offers = null;
        }
        this.entityData.<VillagerData>set(Villager.DATA_VILLAGER_DATA, bdq);
    }
    
    @Override
    public VillagerData getVillagerData() {
        return this.entityData.<VillagerData>get(Villager.DATA_VILLAGER_DATA);
    }
    
    @Override
    protected void rewardTradeXp(final MerchantOffer boz) {
        int integer3 = 3 + this.random.nextInt(4);
        this.villagerXp += boz.getXp();
        this.lastTradedPlayer = this.getTradingPlayer();
        if (this.shouldIncreaseLevel()) {
            this.updateMerchantTimer = 40;
            this.increaseProfessionLevelOnUpdate = true;
            integer3 += 5;
        }
        if (boz.shouldRewardExp()) {
            this.level.addFreshEntity(new ExperienceOrb(this.level, this.getX(), this.getY() + 0.5, this.getZ(), integer3));
        }
    }
    
    @Override
    public void setLastHurtByMob(@Nullable final LivingEntity aoy) {
        if (aoy != null && this.level instanceof ServerLevel) {
            ((ServerLevel)this.level).onReputationEvent(ReputationEventType.VILLAGER_HURT, aoy, this);
            if (this.isAlive() && aoy instanceof Player) {
                this.level.broadcastEntityEvent(this, (byte)13);
            }
        }
        super.setLastHurtByMob(aoy);
    }
    
    @Override
    public void die(final DamageSource anw) {
        Villager.LOGGER.info("Villager {} died, message: '{}'", this, anw.getLocalizedDeathMessage(this).getString());
        final Entity aom3 = anw.getEntity();
        if (aom3 != null) {
            this.tellWitnessesThatIWasMurdered(aom3);
        }
        this.releasePoi(MemoryModuleType.HOME);
        this.releasePoi(MemoryModuleType.JOB_SITE);
        this.releasePoi(MemoryModuleType.MEETING_POINT);
        super.die(anw);
    }
    
    private void tellWitnessesThatIWasMurdered(final Entity aom) {
        if (!(this.level instanceof ServerLevel)) {
            return;
        }
        final Optional<List<LivingEntity>> optional3 = this.brain.<List<LivingEntity>>getMemory(MemoryModuleType.VISIBLE_LIVING_ENTITIES);
        if (!optional3.isPresent()) {
            return;
        }
        final ServerLevel zd4 = (ServerLevel)this.level;
        optional3.get().stream().filter(aoy -> aoy instanceof ReputationEventHandler).forEach(aoy -> zd4.onReputationEvent(ReputationEventType.VILLAGER_KILLED, aom, aoy));
    }
    
    public void releasePoi(final MemoryModuleType<GlobalPos> awp) {
        if (!(this.level instanceof ServerLevel)) {
            return;
        }
        final MinecraftServer minecraftServer3 = ((ServerLevel)this.level).getServer();
        final ServerLevel zd5;
        PoiManager axz6;
        Optional<PoiType> optional7;
        BiPredicate<Villager, PoiType> biPredicate8;
        this.brain.<GlobalPos>getMemory(awp).ifPresent(gc -> {
            zd5 = minecraftServer3.getLevel(gc.dimension());
            if (zd5 != null) {
                axz6 = zd5.getPoiManager();
                optional7 = axz6.getType(gc.pos());
                biPredicate8 = Villager.POI_MEMORIES.get(awp);
                if (optional7.isPresent() && biPredicate8.test(this, optional7.get())) {
                    axz6.release(gc.pos());
                    DebugPackets.sendPoiTicketCountPacket(zd5, gc.pos());
                }
            }
        });
    }
    
    @Override
    public boolean canBreed() {
        return this.foodLevel + this.countFoodPointsInInventory() >= 12 && this.getAge() == 0;
    }
    
    private boolean hungry() {
        return this.foodLevel < 12;
    }
    
    private void eatUntilFull() {
        if (!this.hungry() || this.countFoodPointsInInventory() == 0) {
            return;
        }
        for (int integer2 = 0; integer2 < this.getInventory().getContainerSize(); ++integer2) {
            final ItemStack bki3 = this.getInventory().getItem(integer2);
            if (!bki3.isEmpty()) {
                final Integer integer3 = Villager.FOOD_POINTS.get(bki3.getItem());
                if (integer3 != null) {
                    int integer5;
                    for (int integer4 = integer5 = bki3.getCount(); integer5 > 0; --integer5) {
                        this.foodLevel += (byte)integer3;
                        this.getInventory().removeItem(integer2, 1);
                        if (!this.hungry()) {
                            return;
                        }
                    }
                }
            }
        }
    }
    
    public int getPlayerReputation(final Player bec) {
        return this.gossips.getReputation(bec.getUUID(), awm -> true);
    }
    
    private void digestFood(final int integer) {
        this.foodLevel -= (byte)integer;
    }
    
    public void eatAndDigestFood() {
        this.eatUntilFull();
        this.digestFood(12);
    }
    
    public void setOffers(final MerchantOffers bpa) {
        this.offers = bpa;
    }
    
    private boolean shouldIncreaseLevel() {
        final int integer2 = this.getVillagerData().getLevel();
        return VillagerData.canLevelUp(integer2) && this.villagerXp >= VillagerData.getMaxXpPerLevel(integer2);
    }
    
    private void increaseMerchantCareer() {
        this.setVillagerData(this.getVillagerData().setLevel(this.getVillagerData().getLevel() + 1));
        this.updateTrades();
    }
    
    @Override
    protected Component getTypeName() {
        return new TranslatableComponent(this.getType().getDescriptionId() + '.' + Registry.VILLAGER_PROFESSION.getKey(this.getVillagerData().getProfession()).getPath());
    }
    
    @Override
    public void handleEntityEvent(final byte byte1) {
        if (byte1 == 12) {
            this.addParticlesAroundSelf(ParticleTypes.HEART);
        }
        else if (byte1 == 13) {
            this.addParticlesAroundSelf(ParticleTypes.ANGRY_VILLAGER);
        }
        else if (byte1 == 14) {
            this.addParticlesAroundSelf(ParticleTypes.HAPPY_VILLAGER);
        }
        else if (byte1 == 42) {
            this.addParticlesAroundSelf(ParticleTypes.SPLASH);
        }
        else {
            super.handleEntityEvent(byte1);
        }
    }
    
    @Nullable
    @Override
    public SpawnGroupData finalizeSpawn(final LevelAccessor bqc, final DifficultyInstance ane, final MobSpawnType apb, @Nullable final SpawnGroupData apo, @Nullable final CompoundTag le) {
        if (apb == MobSpawnType.BREEDING) {
            this.setVillagerData(this.getVillagerData().setProfession(VillagerProfession.NONE));
        }
        if (apb == MobSpawnType.COMMAND || apb == MobSpawnType.SPAWN_EGG || apb == MobSpawnType.SPAWNER || apb == MobSpawnType.DISPENSER) {
            this.setVillagerData(this.getVillagerData().setType(VillagerType.byBiome(bqc.getBiome(this.blockPosition()))));
        }
        if (apb == MobSpawnType.STRUCTURE) {
            this.assignProfessionWhenSpawned = true;
        }
        return super.finalizeSpawn(bqc, ane, apb, apo, le);
    }
    
    @Override
    public Villager getBreedOffspring(final AgableMob aok) {
        final double double4 = this.random.nextDouble();
        VillagerType bdu3;
        if (double4 < 0.5) {
            bdu3 = VillagerType.byBiome(this.level.getBiome(this.blockPosition()));
        }
        else if (double4 < 0.75) {
            bdu3 = this.getVillagerData().getType();
        }
        else {
            bdu3 = ((Villager)aok).getVillagerData().getType();
        }
        final Villager bdp6 = new Villager(EntityType.VILLAGER, this.level, bdu3);
        bdp6.finalizeSpawn(this.level, this.level.getCurrentDifficultyAt(bdp6.blockPosition()), MobSpawnType.BREEDING, null, null);
        return bdp6;
    }
    
    @Override
    public void thunderHit(final LightningBolt aox) {
        if (this.level.getDifficulty() != Difficulty.PEACEFUL) {
            Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, aox);
            final Witch bcr3 = EntityType.WITCH.create(this.level);
            bcr3.moveTo(this.getX(), this.getY(), this.getZ(), this.yRot, this.xRot);
            bcr3.finalizeSpawn(this.level, this.level.getCurrentDifficultyAt(bcr3.blockPosition()), MobSpawnType.CONVERSION, null, null);
            bcr3.setNoAi(this.isNoAi());
            if (this.hasCustomName()) {
                bcr3.setCustomName(this.getCustomName());
                bcr3.setCustomNameVisible(this.isCustomNameVisible());
            }
            bcr3.setPersistenceRequired();
            this.level.addFreshEntity(bcr3);
            this.remove();
        }
        else {
            super.thunderHit(aox);
        }
    }
    
    @Override
    protected void pickUpItem(final ItemEntity bbg) {
        final ItemStack bki3 = bbg.getItem();
        if (this.wantsToPickUp(bki3)) {
            final SimpleContainer anm4 = this.getInventory();
            final boolean boolean5 = anm4.canAddItem(bki3);
            if (!boolean5) {
                return;
            }
            this.onItemPickup(bbg);
            this.take(bbg, bki3.getCount());
            final ItemStack bki4 = anm4.addItem(bki3);
            if (bki4.isEmpty()) {
                bbg.remove();
            }
            else {
                bki3.setCount(bki4.getCount());
            }
        }
    }
    
    @Override
    public boolean wantsToPickUp(final ItemStack bki) {
        final Item bke3 = bki.getItem();
        return (Villager.WANTED_ITEMS.contains(bke3) || this.getVillagerData().getProfession().getRequestedItems().contains(bke3)) && this.getInventory().canAddItem(bki);
    }
    
    public boolean hasExcessFood() {
        return this.countFoodPointsInInventory() >= 24;
    }
    
    public boolean wantsMoreFood() {
        return this.countFoodPointsInInventory() < 12;
    }
    
    private int countFoodPointsInInventory() {
        final SimpleContainer anm2 = this.getInventory();
        return Villager.FOOD_POINTS.entrySet().stream().mapToInt(entry -> anm2.countItem(entry.getKey()) * (int)entry.getValue()).sum();
    }
    
    public boolean hasFarmSeeds() {
        return this.getInventory().hasAnyOf(ImmutableSet.<Item>of(Items.WHEAT_SEEDS, Items.POTATO, Items.CARROT, Items.BEETROOT_SEEDS));
    }
    
    @Override
    protected void updateTrades() {
        final VillagerData bdq2 = this.getVillagerData();
        final Int2ObjectMap<VillagerTrades.ItemListing[]> int2ObjectMap3 = VillagerTrades.TRADES.get(bdq2.getProfession());
        if (int2ObjectMap3 == null || int2ObjectMap3.isEmpty()) {
            return;
        }
        final VillagerTrades.ItemListing[] arr4 = (VillagerTrades.ItemListing[])int2ObjectMap3.get(bdq2.getLevel());
        if (arr4 == null) {
            return;
        }
        final MerchantOffers bpa5 = this.getOffers();
        this.addOffersFromItemListings(bpa5, arr4, 2);
    }
    
    public void gossip(final Villager bdp, final long long2) {
        if ((long2 >= this.lastGossipTime && long2 < this.lastGossipTime + 1200L) || (long2 >= bdp.lastGossipTime && long2 < bdp.lastGossipTime + 1200L)) {
            return;
        }
        this.gossips.transferFrom(bdp.gossips, this.random, 10);
        this.lastGossipTime = long2;
        this.spawnGolemIfNeeded(bdp.lastGossipTime = long2, 5);
    }
    
    private void maybeDecayGossip() {
        final long long2 = this.level.getGameTime();
        if (this.lastGossipDecayTime == 0L) {
            this.lastGossipDecayTime = long2;
            return;
        }
        if (long2 < this.lastGossipDecayTime + 24000L) {
            return;
        }
        this.gossips.decay();
        this.lastGossipDecayTime = long2;
    }
    
    public void spawnGolemIfNeeded(final long long1, final int integer) {
        if (!this.wantsToSpawnGolem(long1)) {
            return;
        }
        final AABB deg5 = this.getBoundingBox().inflate(10.0, 10.0, 10.0);
        final List<Villager> list6 = this.level.<Villager>getEntitiesOfClass(Villager.class, deg5);
        final List<Villager> list7 = list6.stream().filter(bdp -> bdp.wantsToSpawnGolem(long1)).limit(5L).collect(Collectors.toList());
        if (list7.size() < integer) {
            return;
        }
        final IronGolem ayt8 = this.trySpawnGolem();
        if (ayt8 == null) {
            return;
        }
        list6.forEach(bdp -> bdp.sawGolem(long1));
    }
    
    private void sawGolem(final long long1) {
        this.brain.<Long>setMemory(MemoryModuleType.GOLEM_LAST_SEEN_TIME, long1);
    }
    
    private boolean hasSeenGolemRecently(final long long1) {
        final Optional<Long> optional4 = this.brain.<Long>getMemory(MemoryModuleType.GOLEM_LAST_SEEN_TIME);
        if (!optional4.isPresent()) {
            return false;
        }
        final Long long2 = optional4.get();
        return long1 - long2 <= 600L;
    }
    
    public boolean wantsToSpawnGolem(final long long1) {
        return this.golemSpawnConditionsMet(this.level.getGameTime()) && !this.hasSeenGolemRecently(long1);
    }
    
    @Nullable
    private IronGolem trySpawnGolem() {
        final BlockPos fu2 = this.blockPosition();
        for (int integer3 = 0; integer3 < 10; ++integer3) {
            final double double4 = this.level.random.nextInt(16) - 8;
            final double double5 = this.level.random.nextInt(16) - 8;
            final BlockPos fu3 = this.findSpawnPositionForGolemInColumn(fu2, double4, double5);
            if (fu3 != null) {
                final IronGolem ayt9 = EntityType.IRON_GOLEM.create(this.level, null, null, null, fu3, MobSpawnType.MOB_SUMMONED, false, false);
                if (ayt9 != null) {
                    if (ayt9.checkSpawnRules(this.level, MobSpawnType.MOB_SUMMONED) && ayt9.checkSpawnObstruction(this.level)) {
                        this.level.addFreshEntity(ayt9);
                        return ayt9;
                    }
                    ayt9.remove();
                }
            }
        }
        return null;
    }
    
    @Nullable
    private BlockPos findSpawnPositionForGolemInColumn(final BlockPos fu, final double double2, final double double3) {
        final int integer7 = 6;
        BlockPos fu2 = fu.offset(double2, 6.0, double3);
        BlockState cfj9 = this.level.getBlockState(fu2);
        for (int integer8 = 6; integer8 >= -6; --integer8) {
            final BlockPos fu3 = fu2;
            final BlockState cfj10 = cfj9;
            fu2 = fu3.below();
            cfj9 = this.level.getBlockState(fu2);
            if ((cfj10.isAir() || cfj10.getMaterial().isLiquid()) && cfj9.getMaterial().isSolidBlocking()) {
                return fu3;
            }
        }
        return null;
    }
    
    @Override
    public void onReputationEventFrom(final ReputationEventType axw, final Entity aom) {
        if (axw == ReputationEventType.ZOMBIE_VILLAGER_CURED) {
            this.gossips.add(aom.getUUID(), GossipType.MAJOR_POSITIVE, 20);
            this.gossips.add(aom.getUUID(), GossipType.MINOR_POSITIVE, 25);
        }
        else if (axw == ReputationEventType.TRADE) {
            this.gossips.add(aom.getUUID(), GossipType.TRADING, 2);
        }
        else if (axw == ReputationEventType.VILLAGER_HURT) {
            this.gossips.add(aom.getUUID(), GossipType.MINOR_NEGATIVE, 25);
        }
        else if (axw == ReputationEventType.VILLAGER_KILLED) {
            this.gossips.add(aom.getUUID(), GossipType.MAJOR_NEGATIVE, 25);
        }
    }
    
    @Override
    public int getVillagerXp() {
        return this.villagerXp;
    }
    
    public void setVillagerXp(final int integer) {
        this.villagerXp = integer;
    }
    
    private void resetNumberOfRestocks() {
        this.catchUpDemand();
        this.numberOfRestocksToday = 0;
    }
    
    public GossipContainer getGossips() {
        return this.gossips;
    }
    
    public void setGossips(final Tag lu) {
        this.gossips.update(new Dynamic((DynamicOps)NbtOps.INSTANCE, lu));
    }
    
    @Override
    protected void sendDebugPackets() {
        super.sendDebugPackets();
        DebugPackets.sendEntityBrain(this);
    }
    
    @Override
    public void startSleeping(final BlockPos fu) {
        super.startSleeping(fu);
        this.brain.<Long>setMemory(MemoryModuleType.LAST_SLEPT, this.level.getGameTime());
        this.brain.<WalkTarget>eraseMemory(MemoryModuleType.WALK_TARGET);
        this.brain.<Long>eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
    }
    
    @Override
    public void stopSleeping() {
        super.stopSleeping();
        this.brain.<Long>setMemory(MemoryModuleType.LAST_WOKEN, this.level.getGameTime());
    }
    
    private boolean golemSpawnConditionsMet(final long long1) {
        final Optional<Long> optional4 = this.brain.<Long>getMemory(MemoryModuleType.LAST_SLEPT);
        return optional4.isPresent() && long1 - optional4.get() < 24000L;
    }
    
    static {
        DATA_VILLAGER_DATA = SynchedEntityData.<VillagerData>defineId(Villager.class, EntityDataSerializers.VILLAGER_DATA);
        FOOD_POINTS = ImmutableMap.<Item, Integer>of(Items.BREAD, Integer.valueOf(4), Items.POTATO, Integer.valueOf(1), Items.CARROT, Integer.valueOf(1), Items.BEETROOT, Integer.valueOf(1));
        WANTED_ITEMS = ImmutableSet.<Item>of(Items.BREAD, Items.POTATO, Items.CARROT, Items.WHEAT, Items.WHEAT_SEEDS, Items.BEETROOT, new Item[] { Items.BEETROOT_SEEDS });
        MEMORY_TYPES = ImmutableList.<MemoryModuleType<GlobalPos>>of(MemoryModuleType.HOME, MemoryModuleType.JOB_SITE, MemoryModuleType.POTENTIAL_JOB_SITE, MemoryModuleType.MEETING_POINT, (MemoryModuleType<GlobalPos>)MemoryModuleType.LIVING_ENTITIES, (MemoryModuleType<GlobalPos>)MemoryModuleType.VISIBLE_LIVING_ENTITIES, (MemoryModuleType<GlobalPos>)MemoryModuleType.VISIBLE_VILLAGER_BABIES, (MemoryModuleType<GlobalPos>)MemoryModuleType.NEAREST_PLAYERS, (MemoryModuleType<GlobalPos>)MemoryModuleType.NEAREST_VISIBLE_PLAYER, (MemoryModuleType<GlobalPos>)MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, (MemoryModuleType<GlobalPos>)MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, (MemoryModuleType<GlobalPos>)MemoryModuleType.WALK_TARGET, MemoryModuleType.LOOK_TARGET, MemoryModuleType.INTERACTION_TARGET, MemoryModuleType.BREED_TARGET, MemoryModuleType.PATH, MemoryModuleType.INTERACTABLE_DOORS, MemoryModuleType.OPENED_DOORS, MemoryModuleType.NEAREST_BED, MemoryModuleType.HURT_BY, MemoryModuleType.HURT_BY_ENTITY, MemoryModuleType.NEAREST_HOSTILE, MemoryModuleType.SECONDARY_JOB_SITE, MemoryModuleType.HIDING_PLACE, MemoryModuleType.HEARD_BELL_TIME, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.LAST_SLEPT, MemoryModuleType.LAST_WOKEN, MemoryModuleType.LAST_WORKED_AT_POI, MemoryModuleType.GOLEM_LAST_SEEN_TIME);
        SENSOR_TYPES = ImmutableList.<SensorType<NearestLivingEntitySensor>>of(SensorType.NEAREST_LIVING_ENTITIES, (SensorType<NearestLivingEntitySensor>)SensorType.NEAREST_PLAYERS, (SensorType<NearestLivingEntitySensor>)SensorType.NEAREST_ITEMS, (SensorType<NearestLivingEntitySensor>)SensorType.INTERACTABLE_DOORS, (SensorType<NearestLivingEntitySensor>)SensorType.NEAREST_BED, (SensorType<NearestLivingEntitySensor>)SensorType.HURT_BY, (SensorType<NearestLivingEntitySensor>)SensorType.VILLAGER_HOSTILES, (SensorType<NearestLivingEntitySensor>)SensorType.VILLAGER_BABIES, (SensorType<NearestLivingEntitySensor>)SensorType.SECONDARY_POIS, (SensorType<NearestLivingEntitySensor>)SensorType.GOLEM_LAST_SEEN);
        POI_MEMORIES = ImmutableMap.<MemoryModuleType<GlobalPos>, Object>of(MemoryModuleType.HOME, (bdp, ayc) -> ayc == PoiType.HOME, MemoryModuleType.JOB_SITE, (bdp, ayc) -> bdp.getVillagerData().getProfession().getJobPoiType() == ayc, MemoryModuleType.POTENTIAL_JOB_SITE, (bdp, ayc) -> PoiType.ALL_JOBS.test(ayc), MemoryModuleType.MEETING_POINT, (bdp, ayc) -> ayc == PoiType.MEETING);
    }
}
