/**
 *  Organism II
 *
 *     Hanhua Feng
 *     Lyuba Golub
 *     Adam Rosenzweig
 */
package Organisms2.g4;

abstract class Entity {
    final static int magic = (int)(Math.random()*255.0);   // the magic code 

    final int es;            // the energy consumed if staying put
    final int ev;            // the energy consumed if moving or reproducing
    final int eu;            // the energy per unit of food
    final int em;            // the maximum energy of an entity
    final int fk;            // the maximum food units in a cell

    final static int WEST = 0;
    final static int EAST = 1;
    final static int NORTH = 2;
    final static int SOUTH = 3;

    final static int STAY = 0;
    final static int MOVE = 4;
    final static int CLONE = 8;

    int _w = 1024, _h = 1024;// the width and height of the board (estimated)
    int _x = 0, _y = 0;      // the relative coordinate
    int _attr;               // the key for the child

    int _food;               // the food left in the current cell
    int _energy;             // the remaining energy of the entity
    boolean[] _hasfood = new boolean[4]; // is there any food in some direction?
    int[] _entity = new int[]{ -1, -1, -1, -1 }; // the entity at some direction.
    int[] _entity_hist = new int[4];  // the history of having an entity
    int[] _noentity_hist = new int[4]; // the history of having an entity
    int _last_move = -1;     // the last move

    int _age;                // the age of this entity
    int _year;               // the current year

    /** constructor.  
     * @param param the observable parameters, in this order:
     *      s, v, u, M, K.  Parameter s is always 1.
     */
    public Entity( int[] param ) {
        es = param[0];
        ev = param[1];
        eu = param[2];
        em = param[3];
        fk = param[4];
    }

    /** the amount of food in the current cell
     */
    public final int food() {
        return _food;
    }

    /** the amount of energy held by this entity
     */
    public final int energy() {
        return _energy;
    }

    /** can this entity eat food?  One can override this function
     */
    public boolean hungry() {
        return _energy <= em - eu;
    }

    /** whether food is present in an adjacent cell of certain direction
     */
    public final boolean hasFood( int direction ) {
        return _hasfood[ direction ];
    }

    /** whether an entity is present in the specified adjacent cell
     */
    public final boolean hasEntity( int direction ) {
        return _entity[ direction ] >= 0;
    }

    /** enscryption
     */
    public int decrypt( int x ) {
        if ( x == -1 )
            return -1;
        return x ^ magic;
    }

    /** descryption
     */
    public int encrypt( int x ) {
        if ( x == -1 )
            return -1;

        return x ^ magic;
    }

    /** encoding x in [0.0,1.0] in nbits
     */
    public static int encode( double x, int nbit ) {
        int n = 1<<nbit;
        int ret = (int) Math.floor( x * n );
        if ( ret < 0 )
            return 0;
        if ( ret >= n )
            return n-1;
        return ret;
    }

    /** decode a value in [0.0,1.0] from an nbit code
     */
    public static double decode( int i, int nbit ) {
        return (i+0.5)/(double)(1<<nbit);
    }

    /** map [0,infty] to [0,1]
     */
    public static double enscale( double x, double x0 ) {
        return 1.0 - Math.exp( - x/x0 );
    }

    /** map [0,1] to [0,infty]
     */
    public static double descale( double x, double x0 ) {
        return - x0 * Math.log( 1.0 - x );
    }

    /** whether an entity is the same as this
     */
    public boolean sameEntity( int direction ) {
        int en = _entity[ direction ];
        if ( en < 0 )
            return false;

        if ( _recv_size[direction] > 0 )
            return true;

        int code = decrypt( en );
        if ( code == 6 || code == 5 )
            return true;

        // System.out.println( "Enemy Entity found:" + code + " orginal:" + en );
        return false;
    }

    /** the id of the entity: 0-255, -1 means no entity present
     */
    public final int entity( int direction ) {
        return _entity[ direction ];
    }

    /** the age of this entity
     */
    public final int age() {
        return _age;
    }

    /** the current year
     */
    public final int year() {
        return _year;
    }

    /** set current year
     */
    public final void setYear( int year ) {
        _year = year;
    }

    /** the x coordinate of the entity
     */
    public final int x() {
        return _x;
    }

    /** the y coordinate of the entity
     */
    public final int y() {
        return _y;
    }

    /** the width of the board
     */
    public final int width() {
        return _w;
    }

    /** the height of the board
     */
    public final int height() {
        return _h;
    }

    /** reset the current position as the origin of the coordinate system
     */
    public final void reset() {
        _x = _y = 0;
    }

    /** set the cell of (x,y) as the new origin
     */
    public final void sethome( int x, int y ) {
        _x = -x;
        _y = -y;
    }

    /** set the board width and height
     */
    public final void setdimension( int w, int h ) {
        _w = w;
        _h = h;
    }

    /** communication functions
     *
     * protocol: (decrypted code)
     *   EAST:  the first frame sent to west
     *   WEST:  the first frame sent to east
     *   NORTH: the first frame sent to north
     *   SOUTH: the first frame sent to south
     *   4:     unused
     *   5:     request a message
     *   6:     identity
     *   7:     the first frame sent to all directions
     *
     *   [3-bit frame #] [5-bit data]:  frame 1-7, contains 32 bit integer.
     */
    boolean _send_ask = false;     // sending a request?

    int _send_direction;       // which direction to send: -1 all directions
    int[] _send_buffer = new int[8];   // the buffer to send
    int _send_size = -1;        // the first byte not yet sent

    int _recv_direction;       // which direction are we listen, -1 not waiting
    int[][] _recv_buffer = new int[4][8];    // recv buffers
    int[] _recv_size = new int[] { -1, -1, -1, -1 };   // the next byte to receive

    // sending something?
    boolean sending() {
        return _send_size >= 0;
    }

    // waiting for something?
    boolean listening() {
        return _recv_direction >= 0 && _recv_size[_recv_direction] >= 0;
    }

    // try to get something from the adjacent cell
    private void _listen( int d ) {
        // descrypt the incoming message
        int code = decrypt( _entity[d] );

        // if no entity at that direction, reset the buffer
        if ( code < 0 )
        {
            _recv_size[d] = -1;

            // if the connection is dropped
            if ( d == _recv_direction )
                _recv_direction = -1;

            return;
        }

        // if a request code(5) is received, request for sending something
        if ( code == 5 )
        {
/*
            System.out.println( "Entity #" + this 
                                + ": got a request from direction " + d );
*/
            send( request( d ), d );
        }

        // check the frame number
        if ( ( code >> 5 ) == _recv_size[d] )
        {
            // the correct frame number
            _recv_buffer[d][ _recv_size[d]++ ] = code;
        }
        else if ( code == 7 || code == d )
        {
            // a new message is coming
            _recv_size[d] = 0;
            _recv_buffer[d][ _recv_size[d]++ ] = code;
        }

        // if the full message is received, notify the user
        if ( _recv_size[d] >= 8 )
        {
            // bit 32-35 must be zero
            if ( ( _recv_buffer[d][7] & 0x1C ) == 0 )
            {
                int value = ( _recv_buffer[d][1] & 0x1f )
                    | ( _recv_buffer[d][2] & 0x1f ) << 5
                    | ( _recv_buffer[d][3] & 0x1f ) << 10
                    | ( _recv_buffer[d][4] & 0x1f ) << 15
                    | ( _recv_buffer[d][5] & 0x1f ) << 20
                    | ( _recv_buffer[d][6] & 0x1f ) << 25
                    | ( _recv_buffer[d][7] & 0x1f ) << 30;
/*
                System.out.println( 
                    "Entity " + this + ": get value from direction "
                    + d + ": " + value );
*/                

                notify( value, d );
            }

            // if this message is what we are expecting
            if ( d == _recv_direction )
                _recv_direction = -1;

            _recv_size[d] = -1;
        }
    }

    // are other entities saying something?
    private void _listen() {
        _listen( EAST );
        _listen( WEST );
        _listen( SOUTH );
        _listen( NORTH );
    }

    /* we are asking to say something
     */
    public int talk() {
        int ret = 6;   // identity is default to be 6

        // if nothing is being sent, send my identity.
        if ( _send_size < 0 )
        {
            if ( _send_ask )
            {
/*
                System.out.println( 
                    "Entity " + this
                    + ": Sending to direction " + _send_direction + " a request" );
*/                
                _send_ask = false;
                return encrypt( 5 );
            }
            return encrypt( ret );
        }

        // if we have something to send and there is someone listening
        if ( ( _send_direction >= 0 && _entity[_send_direction] >= 0 )
             || ( _send_direction < 0 
                  && ( _entity[EAST] >= 0 || _entity[WEST] >= 0
                       || _entity[NORTH] >= 0 || _entity[SOUTH] >= 0 ) ) )
        {
            ret = _send_buffer[_send_size++];
/*
            System.out.println( 
                "Entity " + this
                + ": Sending to direction " + _send_direction
                + " frame #" + (_send_size-1) + ": " + ret );
*/
            if ( _send_size >= 8 )
                _send_size = -1;
        }
        else  // else abort the message
        {
            // System.out.println( "Entity " + this + ": abort" );

            _send_size = -1;
        }
/*
        if ( ret == 5 )
        {
            System.out.println( "I am sending a request?" );
        }
*/
        return encrypt( ret );
    }

    /** try to send a request
     */
    void ask() {
        _send_ask = true;
    }

    /** try to send out a 32 bit value
     */ 
    void send( int value, int direction ) {
/*
        System.out.println( 
            "Entity " + this
            + ": requested to send to direction " + _send_direction
            + ": " + value );
*/
        // if we are sending something else, do nothing
        if ( _send_size >= 0 )
            return;

        // fill the sending buffer
        _send_buffer[0] = turnBack( direction ) & 7;
        _send_buffer[1] = 0x20 | ( value & 0x1f );
        _send_buffer[2] = 0x40 | ( (value>>5) & 0x1f );
        _send_buffer[3] = 0x60 | ( (value>>10) & 0x1f );
        _send_buffer[4] = 0x80 | ( (value>>15) & 0x1f );
        _send_buffer[5] = 0xA0 | ( (value>>20) & 0x1f );
        _send_buffer[6] = 0xC0 | ( (value>>25) & 0x1f );
        _send_buffer[7] = 0xE0 | ( (value>>30) & 0x1f );

        // no byte has been send yet.
        _send_size = 0;
    }

    /** set the key that would be transferred to the descendant
     */
    public void setCloneAttr( int attr ) {
        _attr = attr;
    }

    /** get the current key setting
     */
    public int cloneAttr() {
        return _attr;
    }

    /** is somebody on our way?
     */
    public int actionBlockedBy( int action ) {
        if ( ( action & MOVE ) != 0 || ( action & CLONE ) != 0 )
            return _entity[ action & 3 ];

        return -1;
    }

    /** an entity at d is requesting a message
     */
    public int request( int d ) {
        return 0;
    }

    /** an entity at d sent a message
     */
    public void notify( int value, int d ) {
    }


    /** override this function to specify actions
     */
    abstract public int action();

    void updateHistory( int d ) {
        if ( sameEntity( d ) )
        {
            _entity_hist[d]++;
            _noentity_hist[d] = 0;
        }
        else
        {
            _entity_hist[d] = 0;
            _noentity_hist[d]++;
        }
    }

    void updateHistory() {
        if ( ( _last_move & MOVE ) != 0 )
        {
            _entity_hist[EAST] = _noentity_hist[EAST] = 0;
            _entity_hist[WEST] = _noentity_hist[WEST] = 0;
            _entity_hist[SOUTH] = _noentity_hist[SOUTH] = 0;
            _entity_hist[NORTH] = _noentity_hist[NORTH] = 0;
            if ( _last_move >= 0 )
                _noentity_hist[turnBack(_last_move)]++;
        }

        updateHistory( EAST );
        updateHistory( WEST );
        updateHistory( SOUTH );
        updateHistory( NORTH );
    }

    /** number of rounds that there is no same entity at the direction
     */
    public int roundNoSameEntity( int d ) {
        return _noentity_hist[d];
    }

    /** number of rounds that there is same entity at the direction
     */
    public int roundSameEntity( int d) {
        return _entity_hist[d];
    }

    /** the main move function
     */
    public int move( boolean[] foodpresent, int[] enemypresent, 
                     int foodleft, int energyleft  ) {

        // setting up observed environment
        _food = foodleft;
        _energy = energyleft;
        System.arraycopy( foodpresent, 1, _hasfood, 0, 4 );
        System.arraycopy( enemypresent, 1, _entity, 0, 4 );

        updateHistory();

        // calendar ...
        _age++;
        _year++;

        // check whether we are sending and receiving data
        // _listen();

        // call actions
        int ret = action();

        // if ( sending() || listening() )
        //    return STAY;

        // check the validity of an action
        if ( actionBlockedBy( ret ) >= 0 )
            return STAY;

        // calculate the new coordinates
        if ( ( ret & MOVE ) != 0 )
        {
            switch ( ret & 0x3 )
            {
            case EAST: 
                _x++;
                break;
            case WEST:
                _x--;
                break;
            case NORTH:
                _y--;
                break;
            case SOUTH:
                _y++;
                break;
            }
        }

        _last_move = ret;
        return ret;
    }

    /** the Manhattan distance to the origin.
     */
    public int distance() {
        return Math.abs(_x) + Math.abs(_y);
    }

    /** the Manhattan distance to some place
     */
    public int distance( int x, int y ) {
        return Math.abs(x-_x) + Math.abs(y-_y);
    }

    /** is this entity at origin
     */
    public boolean athome() {
        return _x == 0 && _y == 0;
    }

    /** find a way to go home
     */
    public int gohome() {
        if ( _x > 0 )
            return WEST | MOVE;
        if ( _x < 0 )
            return EAST | MOVE ;
        if ( _y > 0 )
            return NORTH | MOVE;
        if ( _y < 0 )
            return SOUTH | MOVE;
        return STAY;
    }

    /** the reversed direction of d
     */
    public int turnBack( int d ) {
        switch ( d & 0x3 )
        {
        case EAST:
            return WEST;
        case WEST:
            return EAST;
        case NORTH:
            return SOUTH;
        case SOUTH:
            return NORTH;
        }

        // remove stupid error message
        throw new RuntimeException( "Java compiler error" );
    }

    /** the left direction of d
     */
    public int turnLeft( int d ) {
        switch ( d & 0x3 ) 
        {
        case EAST:
            return NORTH;
        case WEST:
            return SOUTH;
        case NORTH:
            return WEST;
        case SOUTH:
            return EAST;
        }

        throw new RuntimeException( "Java compiler error" );
    }

    /** the right direction of d
     */
    public int turnRight( int d ) {
        switch ( d & 0x3 ) 
        {
        case EAST:
            return SOUTH;
        case WEST:
            return NORTH;
        case NORTH:
            return EAST;
        case SOUTH:
            return WEST;
        }

        throw new RuntimeException( "Java compiler error" );
    }
}
