package edu.nyu.bridge.util;

import static edu.nyu.bridge.gen.Bridge.Direction.EAST;
import static edu.nyu.bridge.gen.Bridge.Direction.NORTH;
import static edu.nyu.bridge.gen.Bridge.Direction.SOUTH;
import static edu.nyu.bridge.gen.Bridge.Direction.WEST;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import edu.nyu.bridge.gen.Bridge;
import edu.nyu.bridge.gen.Bridge.Direction;
import edu.nyu.cards.Hand;
import edu.nyu.cards.gen.Cards;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;

/** Utility methods for getting info related to {@link Direction}. */
public final class Directions {
  private Directions() {}

  private static final Map<Direction, ImmutableList<Direction>> DIRECTION_PLUS_FOUR =
      Maps.immutableEnumMap(
          ImmutableMap.of(
              NORTH,
              ImmutableList.of(NORTH, EAST, SOUTH, WEST),
              EAST,
              ImmutableList.of(EAST, SOUTH, WEST, NORTH),
              SOUTH,
              ImmutableList.of(SOUTH, WEST, NORTH, EAST),
              WEST,
              ImmutableList.of(WEST, NORTH, EAST, SOUTH)));

  private static final Map<Direction, ImmutableList<Direction>> OTHERS =
      Maps.immutableEnumMap(
          ImmutableMap.of(
              NORTH,
              ImmutableList.of(EAST, SOUTH, WEST),
              EAST,
              ImmutableList.of(SOUTH, WEST, NORTH),
              SOUTH,
              ImmutableList.of(WEST, NORTH, EAST),
              WEST,
              ImmutableList.of(NORTH, EAST, SOUTH)));

  public static <T> ImmutableMap<Direction, T> map(Function<Direction, T> func) {
    return Maps.<Direction, T>immutableEnumMap(
        Maps.<Direction, T>asMap(ImmutableSet.copyOf(Bridge.Direction.values()), func));
  }

  public static ImmutableList<Direction> iterateDirections(Direction start) {
    return DIRECTION_PLUS_FOUR.get(start);
  }

  public static ImmutableList<Direction> others(Direction start) {
    return OTHERS.get(start);
  }

  @Nullable
  public static Direction fromString(@Nullable String input) {
    if (input == null || input.length() == 0) {
      return null;
    }
    switch (input.charAt(0)) {
      case 'n':
      case 'N':
        return NORTH;
      case 'e':
      case 'E':
        return EAST;
      case 's':
      case 'S':
        return SOUTH;
      case 'w':
      case 'W':
        return WEST;
      default:
        throw new IllegalArgumentException("Expected a string started with n/e/s/w");
    }
  }

  public static Direction lho(Direction current) {
    switch (current) {
      case NORTH:
        return EAST;
      case EAST:
        return SOUTH;
      case SOUTH:
        return WEST;
      case WEST:
        return NORTH;
      default:
        throw new UnsupportedOperationException("Unexpected direction: " + current);
    }
  }

  public static Direction rho(Direction current) {
    switch (current) {
      case NORTH:
        return WEST;
      case EAST:
        return NORTH;
      case SOUTH:
        return EAST;
      case WEST:
        return SOUTH;
      default:
        throw new UnsupportedOperationException("Unexpected direction: " + current);
    }
  }

  public static Direction partner(Direction current) {
    switch (current) {
      case NORTH:
        return SOUTH;
      case EAST:
        return WEST;
      case SOUTH:
        return NORTH;
      case WEST:
        return EAST;
      default:
        throw new UnsupportedOperationException("Unexpected direction: " + current);
    }
  }

  public static int offset(Direction dealer, Direction player) {
    if (dealer == player) {
      return 0;
    }
    if (lho(dealer) == player) {
      return 1;
    }
    if (partner(dealer) == player) {
      return 2;
    }
    if (rho(dealer) == player) {
      return 3;
    }
    throw new UnsupportedOperationException(
        "Unexpected direction: " + player + " is not lho/partner/rho of dealer: " + dealer);
  }

  public static boolean isNS(Direction player) {
    return player == Direction.NORTH || player == Direction.SOUTH;
  }

  public static boolean opponents(Direction player, Direction other) {
    return isNS(player) != isNS(other);
  }

  public static final EnumMap<Direction, String> HUMAN_STRINGS =
      Maps.newEnumMap(
          ImmutableMap.of(
              Direction.NORTH,
              "North",
              Direction.EAST,
              "East",
              Direction.SOUTH,
              "South",
              Direction.WEST,
              "West"));

  public static String human(Direction dir) {
    return HUMAN_STRINGS.get(dir);
  }

  public static Direction add(Direction dealer, int auctionCount) {
    switch (auctionCount & 3) { // mod 4
      case 0:
        return dealer;
      case 1:
        return lho(dealer);
      case 2:
        return partner(dealer);
      case 3:
        return rho(dealer);
      default:
        throw new IllegalStateException("can't get here mod 4");
    }
  }

  public static <T> ImmutableList<T> fromNorth(Bridge.Direction dealer, T... hands) {
    int offset;
    switch (dealer) {
      case NORTH:
        offset = 0;
        break;
      case EAST:
        offset = 1;
        break;
      case SOUTH:
        offset = 2;
        break;
      case WEST:
        offset = 3;
        break;
      default:
        throw new IllegalArgumentException("unexpected value of dealer");
    }
    ImmutableList.Builder<T> result = ImmutableList.builder();
    for (int i = offset; i < hands.length; ++i) {
      result.add(hands[i]);
    }
    for (int i = 0; i < offset; ++i) {
      result.add(hands[i]);
    }
    return result.build();
  }
}

@@ -436,48 +436,8 @@
 et;%0A
-import com.google.common.collect.Lists;%0A
 impo
@@ -471,16 +471,16 @@
 t.Maps;%0A
+
 import e
@@ -553,67 +553,8 @@
 on;%0A
-import edu.nyu.cards.Hand;%0Aimport edu.nyu.cards.gen.Cards;%0A
 impo
@@ -579,31 +579,8 @@
 ap;%0A
-import java.util.List;%0A
 impo
@@ -5063,32 +5063,142 @@
 4%22);%0A    %7D%0A  %7D%0A%0A
+  /**%0A   * Takes an array of hands oriented from %7B@code NORTH%7D, and returns in the specified direction.%0A   */%0A
   public static 
@@ -5860,11 +5860,296 @@
 d();%0A  %7D
+%0A%0A  /** Takes an array of hands oriented as %7B@code direction%7D and re-orients as %7B@code NORTH%7D. */%0A  public static %3CT%3E ImmutableList%3CT%3E toNorth(Bridge.Direction direction, T... hands) %7B%0A    return isNS(direction) ? fromNorth(direction, hands) : fromNorth(partner(direction), hands);%0A  %7D
 %0A%7D%0A

add Directions.toNorth