// ============================================================================
// File:               $File$
//
// Project:            
//
// Purpose:            
//
// Author:             Rammi
//
// Copyright Notice:   (c) 2008  Rammi (rammi@caff.de)
//
// Latest change:      $Date$
//
// History:	       $Log$
//=============================================================================
package de.caff.asteroid.rammi;

import de.caff.asteroid.*;
import static de.caff.asteroid.FrameInfo.SHOOTING_DIRECTIONS;
import de.caff.asteroid.server.DatagramListener;
import de.caff.asteroid.server.DatagramSender;
import de.caff.util.Tools;

import java.awt.*;
import java.net.DatagramPacket;
import java.util.LinkedList;

/**
 */
public class ShootingDirectionFixer
  implements FramePreparer,
             DatagramListener,
             PingKeyProvider,
             GameData
{
  private static class KeyInfo
  {
    private final int key;
    private final int ping;

    public KeyInfo(DatagramPacket packet)
    {
      key = Tools.byteToUnsigned(packet.getData()[KEY_MASK_INDEX]);
      ping = Tools.byteToUnsigned(packet.getData()[KEY_PING_INDEX]);
    }

    public int getKey()
    {
      return key;
    }

    public boolean hasLeftPressed()
    {
      return (key & BUTTON_LEFT) != 0;
    }

    public boolean hasRightPressed()
    {
      return (key & BUTTON_RIGHT) != 0;
    }

    public int getPing()
    {
      return ping;
    }
  }

  private KeyInfo[] pendingKeys;
  private PingKeyProvider pinky;

  public ShootingDirectionFixer()
  {
    pendingKeys = new KeyInfo[256];
    pinky = this;
  }

  public ShootingDirectionFixer(PingKeyProvider pinky)
  {
    this.pinky = pinky;
  }

  /**
   * Called when an incoming datagram was received.
   * <p/>
   * This method is called in the communication thread.
   *
   * @param packet packet received
   * @param sender datagram sender used for answers
   */
  public void datagramReceived(DatagramPacket packet, DatagramSender sender)
  {
    // nothing
  }

  /**
   * Called when an outgoing datagram was sent.
   * <p/>
   * This method is called in the communication thread.
   *
   * @param packet sent packet
   */
  public void datagramSent(DatagramPacket packet)
  {
    KeyInfo info = new KeyInfo(packet);
    pendingKeys[info.getPing()] = info;
  }

  /**
   * Prepare the frame(s).
   *
   * @param frameInfos the collected frame infos
   */
  public void prepareFrames(LinkedList<FrameInfo> frameInfos)
  {
    if (frameInfos.size() > 2) {
      FrameInfo currentInfo = frameInfos.getLast();
      FrameInfo lastInfo    = frameInfos.get(frameInfos.size() - 2);
      SpaceShip spaceShip = currentInfo.getSpaceShip();
      if (spaceShip == null  &&  lastInfo.getSpaceShip() != null) {
        // keys wouldn't give effect if ship isn't there
        lastInfo.setNextShootingDirectionLowLevel(lastInfo.getShootingDirectionLowLevel());
      }
      currentInfo.inheritShootingDirectionsFrom(lastInfo);
      if (currentInfo.getPing() != NO_PING  &&  currentInfo.getPing() != lastInfo.getPing()) {
        if (spaceShip != null) {
          if (!currentInfo.getShootingDirection().getShipDirection().equals(spaceShip.getDirection())) {
            fixShootingDirection(currentInfo);
          }
          // key has an effect
          int keys = pinky.getKeysForPing(currentInfo.getIndex(), currentInfo.getPing());
          if ((keys & BUTTON_LEFT) != 0) {
            currentInfo.turnNextShootingDirectionLeft();
          }
          if ((keys & BUTTON_RIGHT) != 0) {
            currentInfo.turnNextShootingDirectionRight();
          }
        }
      }
    }
  }

  private static void fixShootingDirection(FrameInfo info)
  {
    Point shipDir = info.getSpaceShip().getDirection();
    int shootDir = Tools.byteToUnsigned(info.getShootingDirectionLowLevel());
    if (false) {
      System.err.println(String.format("Frame %d: Fixing: shipDir=%s, dir=%s", info.getIndex(), shipDir, SHOOTING_DIRECTIONS[shootDir]));
    }
    for (int i = 1;  i <= SHOOTING_DIRECTIONS.length/2;  ++i) {
      int test = (shootDir + i) % SHOOTING_DIRECTIONS.length;
      if (SHOOTING_DIRECTIONS[test].getShipDirection().equals(shipDir)) {
        info.setBothShootingDirectionsLowLevel((byte)test);
        break;
      }
      test = (shootDir + SHOOTING_DIRECTIONS.length - i) % SHOOTING_DIRECTIONS.length;
      if (SHOOTING_DIRECTIONS[test].getShipDirection().equals(shipDir)) {
        info.setBothShootingDirectionsLowLevel((byte)test);
        break;
      }
    }
  }

  /**
   * Get the buttons sent with a given ping, looking backwards from a given frame.
   *
   * @param frameNr frame counter
   * @param ping    ping
   * @return buttons sent with a given ping
   */
  public int getKeysForPing(int frameNr, int ping)
  {
    if (pendingKeys[ping] != null) {
      KeyInfo keyInfo = pendingKeys[ping];
      return keyInfo.getKey();
    }
    return NO_BUTTON;
  }

  /**
   * Get the timestamp when a given ping was send, looking backwards from a given frame.
   *
   * @param frameNr frame counter
   * @param ping    ping
   * @return timestamp when the ping was sent, or <code>0L</code> if the info is not longer available
   */
  public long getTimestampForPing(int frameNr, int ping)
  {
    // not used
    return 0;
  }

  /**
   * Is information about the given ping still known, looking backwards from a given frame?
   *
   * @param frameNr frame counter
   * @param ping    ping
   * @return the answer
   */
  public boolean isStillKnown(int frameNr, int ping)
  {
    return true;
  }
}
