package com.spbsu.crawl.bl.crawlSystemView;
import com.spbsu.crawl.bl.Mob;
import com.spbsu.crawl.bl.map.Position;
import com.spbsu.crawl.bl.map.PositionManager;
import com.spbsu.crawl.data.impl.MonsterInfoMessage;
import com.spbsu.crawl.data.impl.UpdateMapCellMessage;
import com.spbsu.crawl.data.impl.system.EmptyFieldsDefault;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.set.hash.TIntHashSet;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class MobsView extends Subscribable.Stub<MobsListener> {
private final TIntObjectHashMap<CrawlMob> mobs = new TIntObjectHashMap<>();
//this damn game could use diff even for different monster types
private final Map<Position, MonsterInfoMessage> lastMonsterStates = new HashMap<>();
private final Updater updater = new Updater();
private final PositionManager positionManager;
public MobsView(final PositionManager positionManager) {
this.positionManager = positionManager;
}
public Updater updater() {
return updater;
}
class Updater {
private void merge(final MonsterInfoMessage message, final MonsterInfoMessage previous) {
if (EmptyFieldsDefault.isEmpty(message.attitude())) {
message.setAttitude(previous.attitude());
}
if (EmptyFieldsDefault.isEmpty(message.baseType())) {
message.setBaseType(previous.baseType());
}
if (EmptyFieldsDefault.isEmpty(message.id())) {
message.setId(previous.id());
}
if (EmptyFieldsDefault.isEmpty(message.monsterStats())) {
message.setMonsterStats(previous.monsterStats());
}
if (EmptyFieldsDefault.isEmpty(message.monsterType())) {
message.setType(previous.monsterType());
}
if (EmptyFieldsDefault.isEmpty(message.name())) {
message.setName(previous.name());
}
if (EmptyFieldsDefault.isEmpty(message.plural())) {
message.setPlural(previous.plural());
}
if (EmptyFieldsDefault.isEmpty(message.threatLevel())) {
message.setThreatLevel(previous.threatLevel());
}
}
private void updateCellView(@NotNull final Position position,
@NotNull final MonsterInfoMessage message,
@NotNull final Map<Position, MonsterInfoMessage> previousMonsterStates) {
final MonsterInfoMessage previousInfo;
if (mobs.containsKey(message.id())) {
final Position previousPosition = mobs.get(message.id()).position();
previousInfo = previousMonsterStates.get(previousPosition);
} else {
previousInfo = previousMonsterStates.getOrDefault(position, null);
}
if (previousInfo != null) {
merge(message, previousInfo);
}
lastMonsterStates.put(position, message);
}
private void updateCellsView(final List<UpdateMapCellMessage> cellMessages) {
//we really need copy of previous map.
// Even more - we need immutable monsterInfoMessage for previous state, so we merge to incoming message, instead of message in map
final Map<Position, MonsterInfoMessage> previous = new HashMap<>(lastMonsterStates);
for (final UpdateMapCellMessage cellMessage : cellMessages) {
final Position position = positionManager.getOrCreate(cellMessage.x(), cellMessage.y());
if (cellMessage.getMonsterInfoMessage() == null) {
lastMonsterStates.remove(position);
} else if (!cellMessage.getMonsterInfoMessage().isEmpty()) {
updateCellView(position, cellMessage.getMonsterInfoMessage(), previous);
}
}
}
private void updateMobs() {
final TIntHashSet lostMobIds = new TIntHashSet();
for (int id : mobs.keys()) {
lostMobIds.add(id);
}
for (final Map.Entry<Position, MonsterInfoMessage> entry : lastMonsterStates.entrySet()) {
final Position position = entry.getKey();
final MonsterInfoMessage monsterInfoMessage = entry.getValue();
if (mobs.containsKey(monsterInfoMessage.id())) {
final CrawlMob mob = mobs.get(monsterInfoMessage.id());
lostMobIds.remove(monsterInfoMessage.id());
if (position != mob.position) {
mob.move(position);
}
} else {
final CrawlMob mob = createMob(position, monsterInfoMessage);
mobs.put(monsterInfoMessage.id(), mob);
listeners().forEach(mobListener -> mobListener.observeMonster(mob));
}
}
//if we lost mob, we'll never see him again with the same id
lostMobIds.forEach(id -> {
final Mob lostMob = mobs.get(id);
mobs.remove(id);
listeners().forEach(mobListener -> mobListener.lostMonster(lostMob));
return true;
});
}
private CrawlMob createMob(final Position position,
final MonsterInfoMessage monsterInfo) {
return new CrawlMob(position,
monsterInfo.name(),
monsterInfo.monsterType(),
monsterInfo.monsterStats().averageHealthPoints(),
monsterInfo.threatLevel());
}
void update(final List<UpdateMapCellMessage> cellMessages) {
updateCellsView(cellMessages);
updateMobs();
}
void clear() {
mobs.clear();
lastMonsterStates.clear();
}
}
public interface CrawlMobListener {
void movedTo(final Position position);
}
static class CrawlMob extends Subscribable.Stub<CrawlMobListener> implements Mob {
private Position position;
private final String name;
private final int monsterType;
private final int averageHealthPoints;
private final int dangerLevel;
public CrawlMob(final Position position,
final String name,
final int monsterType,
final int hp,
final int danger) {
this.position = position;
this.name = name;
this.monsterType = monsterType;
this.averageHealthPoints = hp;
this.dangerLevel = danger;
}
@Override
public Position position() {
return position;
}
@Override
public Stream<Action> actions() {
return Stream.empty();
}
private void move(final Position newPosition) {
position = newPosition;
listeners().forEach(listener -> listener.movedTo(newPosition));
}
public int monsterType() {
return monsterType;
}
public String name() {
return name;
}
public int averageHealthPoints() {
return averageHealthPoints;
}
public int dangerLevel() {
return dangerLevel;
}
@Override
public String toString() {
return name;
}
}
}