package org.gbcpainter.game.model.grid;
import org.gbcpainter.env.GraphicsEnv;
import org.gbcpainter.loaders.textures.TextureNotFoundException;
import org.gbcpainter.game.view.AbstractResourceDrawable;
import org.gbcpainter.game.view.animations.JunctionColoringAnimation;
import org.gbcpainter.geom.PERPENDICULAR_DIRECTION;
import org.gbcpainter.loaders.textures.TextureLoader;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.*;
/**
* Implementation of the {@link org.gbcpainter.game.model.grid.Junction} interface
*
* @author Lorenzo Pellegrini
*/
public class JunctionImpl extends AbstractResourceDrawable implements Junction {
@NonNls
private static final String RESOURCE_NAME_PREFIX = "Junction_";
@NonNls
private static final String RESOURCE_HALF_NAME_PREFIX = "Junction_half_";
private static final double HALF_ANIMATION_THRESHOLD = 0.10;
@NonNls
private static final char RESOURCE_UNSELECTED_FLAG = 'X';
@NonNls
private static final char RESOURCE_COLORED = 'C';
@NonNls
private static final Map<PERPENDICULAR_DIRECTION, Map.Entry<Character, Integer>> NAME_DIRECTIONS_MAPPING;
@NotNull
private final Collection<JunctionColoringAnimation> coloringAnimations = new LinkedList<>();
@NotNull
private final String resourceNameNotColored;
@NotNull
private final String resourceNameColored;
@NotNull
private final Set<PERPENDICULAR_DIRECTION> allowedDirections;
@NotNull
private final Point position;
private boolean previousColor = false;
static {
NAME_DIRECTIONS_MAPPING = new EnumMap<>( PERPENDICULAR_DIRECTION.class );
NAME_DIRECTIONS_MAPPING.put( PERPENDICULAR_DIRECTION.UP, new AbstractMap.SimpleImmutableEntry<>( 'U', 0 ) );
NAME_DIRECTIONS_MAPPING.put( PERPENDICULAR_DIRECTION.DOWN, new AbstractMap.SimpleImmutableEntry<>( 'D', 1 ) );
NAME_DIRECTIONS_MAPPING.put( PERPENDICULAR_DIRECTION.LEFT, new AbstractMap.SimpleImmutableEntry<>( 'L', 2 ) );
NAME_DIRECTIONS_MAPPING.put( PERPENDICULAR_DIRECTION.RIGHT, new AbstractMap.SimpleImmutableEntry<>( 'R', 3 ) );
}
private boolean colored = false;
/**
* Creates a non colored junction with the given position and the relative position of the connected pipes
*
* @param junctionPosition The position of the junction
* @param directions The directions of the connected pipes
*
* @throws Exception If an exception during texture loading
* @throws org.gbcpainter.loaders.textures.InvalidTextureException If the base textures haven't the same size of the base point
*/
public JunctionImpl(@NotNull Point junctionPosition, @NotNull Set<PERPENDICULAR_DIRECTION>
directions) throws Exception {
super();
this.position = new Point( junctionPosition );
int directionsCount = 0;
this.allowedDirections = EnumSet.copyOf( directions );
if ( directions.contains( PERPENDICULAR_DIRECTION.UP ) ) {
directionsCount++;
}
if ( directions.contains( PERPENDICULAR_DIRECTION.DOWN ) ) {
directionsCount++;
}
if ( directions.contains( PERPENDICULAR_DIRECTION.LEFT ) ) {
directionsCount++;
}
if ( directions.contains( PERPENDICULAR_DIRECTION.RIGHT ) ) {
directionsCount++;
}
final Collection<String> halfJunctionsResources;
if ( directionsCount < 2 ) {
//A junction with only 1 direction is not allowed!
throw new IllegalArgumentException( "Junction has only " + directionsCount +
"directions");
} else if ( directionsCount == 2 ) {
//2 Directions: junction can be colored from any of these directions
halfJunctionsResources = new HashSet<>( 2 );
final Deque<PERPENDICULAR_DIRECTION> allDirections = new LinkedList<>( directions );
halfJunctionsResources.add( RESOURCE_HALF_NAME_PREFIX + getHalfResourceNameByDirections( directions, allDirections.removeFirst() ) );
halfJunctionsResources.add( RESOURCE_HALF_NAME_PREFIX + getHalfResourceNameByDirections( directions, allDirections.removeFirst() ) );
} else {
/*
3 or 4 Directions: if 3, junction can be colored from 3 of these but decolored from
the perpendicular direction (this is the same texture as coloring from the
lacking direction), so we need all half-coloring textures
*/
halfJunctionsResources = new HashSet<>( 4 );
halfJunctionsResources.add( RESOURCE_HALF_NAME_PREFIX + getHalfResourceNameByDirections( directions, PERPENDICULAR_DIRECTION.DOWN ) );
halfJunctionsResources.add( RESOURCE_HALF_NAME_PREFIX + getHalfResourceNameByDirections( directions, PERPENDICULAR_DIRECTION.UP ) );
halfJunctionsResources.add( RESOURCE_HALF_NAME_PREFIX + getHalfResourceNameByDirections( directions, PERPENDICULAR_DIRECTION.LEFT ) );
halfJunctionsResources.add( RESOURCE_HALF_NAME_PREFIX + getHalfResourceNameByDirections( directions, PERPENDICULAR_DIRECTION.RIGHT ) );
}
final String resourceFromDirection = getResourceNameByDirections( directions );
this.resourceNameNotColored = RESOURCE_NAME_PREFIX +
RESOURCE_UNSELECTED_FLAG +
resourceFromDirection;
this.resourceNameColored = RESOURCE_NAME_PREFIX +
RESOURCE_COLORED +
resourceFromDirection;
final TextureLoader loader = GraphicsEnv.getInstance().getTextureLoader();
loader.loadTexture( this.resourceNameNotColored, true );
loader.loadTexture( this.resourceNameColored, true );
for (String halfJunctionResource : halfJunctionsResources) {
loader.loadTexture( halfJunctionResource, true );
}
}
@NonNls
@NotNull
private static String getResourceNameByDirections( @NotNull Set<PERPENDICULAR_DIRECTION> directions ) {
@NonNls
char[] result = { RESOURCE_UNSELECTED_FLAG, RESOURCE_UNSELECTED_FLAG, RESOURCE_UNSELECTED_FLAG, RESOURCE_UNSELECTED_FLAG };
for (PERPENDICULAR_DIRECTION actualDirection : directions) {
result[NAME_DIRECTIONS_MAPPING.get( actualDirection ).getValue()] = NAME_DIRECTIONS_MAPPING.get( actualDirection ).getKey();
}
return new String( result );
}
@NonNls
@NotNull
private static String getHalfResourceNameByDirections( @NotNull Set<PERPENDICULAR_DIRECTION> directions, @NotNull PERPENDICULAR_DIRECTION coloringDirection ) {
@NonNls
char[] result = { RESOURCE_UNSELECTED_FLAG, RESOURCE_UNSELECTED_FLAG, RESOURCE_UNSELECTED_FLAG, RESOURCE_UNSELECTED_FLAG };
for (PERPENDICULAR_DIRECTION actualDirection : directions) {
result[NAME_DIRECTIONS_MAPPING.get( actualDirection ).getValue()] = NAME_DIRECTIONS_MAPPING.get( actualDirection ).getKey();
}
final Character colorPart = NAME_DIRECTIONS_MAPPING.get( coloringDirection ).getKey();
final StringBuilder builder = new StringBuilder();
builder.append( colorPart );
builder.append( result );
return builder.toString();
}
@NotNull
@Override
public Set<PERPENDICULAR_DIRECTION> getAvailableDirections() {
return EnumSet.copyOf( this.allowedDirections );
}
/**
* Returns true if the point and the junction position are the same
*
* @param position A point in the game grid
*
* @return true if the point is at the junction position
*/
@Override
public boolean contains( @NotNull final Point position ) {
return this.getPosition().equals( position );
}
@Override
public synchronized boolean isColored() {
return this.colored;
}
@Override
public void setColored( @NotNull final Point point, final boolean colored )
throws IllegalArgumentException {
if(this.contains( point )) {
this.setColored( colored );
} else {
throw new IllegalArgumentException( "Point is the junction position: " + point
+ ", expected: " + this.getPosition() );
}
}
/**
* Checks if a point is colored
*
* @param where The point to check
*
* @return true is colored
*
* @throws IllegalArgumentException If the point is not inside the grid element
*/
@Override
public boolean isColoredAt( @NotNull final Point where ) throws IllegalArgumentException {
if(this.contains( where )) {
return isColored( );
} else {
throw new IllegalArgumentException( "Point is the junction position: " + where
+ ", expected: " + getPosition() );
}
}
/**
* Returns the junction position
* <p/>
* This should be a constant
*
* @return The junction position
*/
@NotNull
@Override
public Point getPosition() {
return new Point( this.position );
}
/**
* Sets the color flag of the junction
*
* @param colored true if colored, false if not colored
*/
@Override
public synchronized void setColored( final boolean colored ) throws TextureNotFoundException {
this.colored = colored;
final TextureLoader loader = GraphicsEnv.getInstance().getTextureLoader();
final String requestedTexture;
if ( colored ) {
requestedTexture = this.resourceNameColored;
} else {
requestedTexture = this.resourceNameNotColored;
}
Image texture = loader.getTexture( requestedTexture, true );
if ( texture == null ) {
try {
texture = loader.loadTexture( requestedTexture, true );
} catch ( Exception e ) {
throw new TextureNotFoundException( "Can't find texture " + requestedTexture, e );
}
}
setDrawTexture( texture );
}
@Override
public synchronized void applyAnimations( @NotNull final Collection<JunctionColoringAnimation> animations ) {
this.coloringAnimations.clear();
this.coloringAnimations.addAll( animations );
}
@Override
public synchronized void clearAnimations() {
this.coloringAnimations.clear();
previousColor = isColored();
}
@Override
@NonNls
public String toString() {
return "JunctionImpl{" +
"coloringAnimations=" + coloringAnimations +
", resourceNameNotColored='" + resourceNameNotColored + '\'' +
", resourceNameColored='" + resourceNameColored + '\'' +
", allowedDirections=" + allowedDirections +
", previousColor=" + previousColor +
", colored=" + colored +
"} " + super.toString();
}
@Nullable
@Override
protected synchronized String getResourceName() {
boolean animationsTerminated = true;
if ( ! this.coloringAnimations.isEmpty() ) {
JunctionColoringAnimation bestAnimation = coloringAnimations.iterator().next();
double higherNonFullPercentage = bestAnimation.getPercentageDoneAnimation();
for (JunctionColoringAnimation coloringAnimation : this.coloringAnimations) {
final double animationPercentage = coloringAnimation.getPercentageDoneAnimation();
if ( animationPercentage > higherNonFullPercentage && ( animationPercentage != 1.0 || higherNonFullPercentage == 0.0 ) ) {
higherNonFullPercentage = animationPercentage;
bestAnimation = coloringAnimation;
}
if ( ! coloringAnimation.isTerminated() ) {
animationsTerminated = false;
}
}
if ( animationsTerminated ) {
this.coloringAnimations.clear();
} else if ( higherNonFullPercentage >= HALF_ANIMATION_THRESHOLD && previousColor != bestAnimation.isColoring() ) {
@NotNull
@NonNls
final String suffixString;
final PERPENDICULAR_DIRECTION animationDirection = bestAnimation.getDirection();
Set<PERPENDICULAR_DIRECTION> directions = getAvailableDirections();
if ( bestAnimation.isColoring() ) {
suffixString = getHalfResourceNameByDirections( directions, animationDirection.getOpposite() );
} else {
Deque<PERPENDICULAR_DIRECTION> junctionDirections = new LinkedList<>( directions );
if ( junctionDirections.size() == 2 ) {
junctionDirections.remove( animationDirection.getOpposite() );
final PERPENDICULAR_DIRECTION other = junctionDirections.getFirst();
suffixString = getHalfResourceNameByDirections( directions, other );
} else {
suffixString = getHalfResourceNameByDirections( directions, animationDirection.getOpposite() );
}
}
return RESOURCE_HALF_NAME_PREFIX + suffixString;
} else {
return previousColor ? resourceNameColored : resourceNameNotColored;
}
}
return ( isColored() ) ? resourceNameColored : resourceNameNotColored;
}
}