/**
 ** Person.java
 **
 ** Copyright 2011 by Sarah Wise, Mark Coletti, Andrew Crooks, and
 ** George Mason University.
 **
 ** Licensed under the Academic Free License version 3.0
 **
 ** See the file "LICENSE" for more information
 **
 ** $Id$
 **/
package sim.app.geo.schellingpolygon;

import java.util.ArrayList;
import sim.engine.SimState;
import sim.engine.Steppable;



public class Person implements Steppable
    {

    String color;
    double preference;
    Polygon region;
    double personalThreshold = .5;
    int numMoves = 0;



    /**
     * Constructor function
     * @param c
     */
    public Person(String c)
        {
        color = c;
        }



    /**
     * Moves the Person to the given Polygon
     * @param p - the Polygon to which the Person should move
     */
    public void updateLocation(Polygon p)
        {

        // leave old tile, if previously was on a tile
        if (region != null)
            {
            region.residents.remove(this);
            region.soc = "UNOCCUPIED";
            }

        // go to new tile
        region = p;
        region.residents.add(this);
        region.soc = color;

        numMoves++; // increment the number of times moved
        }



    /**
     *
     * @param poly the proposed location
     * @return whether the given Polygon is an acceptable location for the Person,
     * based on the Person's personalThreshold
     */
    boolean acceptable(Polygon poly)
        {

        // decide if Person is unhappy with surroundings
        double unlike = 0, total = 0.;
        for (Polygon p : poly.neighbors)
            {

            if (p.soc.equals("UNOCCUPIED")) // empty spaces don't count
                {
                continue;
                }
            if (!p.soc.equals(color)) // is this neighbor an unlike neighbor?
                {
                unlike++;
                }
            total++; // total count of neighbors
            }
        double percentUnlike = unlike / Math.max(total, 1); // don't divide by 0!

        // if unhappy, return false
        if (percentUnlike >= personalThreshold)
            {
            return false;
            } else // if happy, return true
            {
            return true;
            }
        }



    /**
     * @param ps the list of Polygons open to the Person
     * @return the closest available Polygon that meets the Person's needs, if such
     * a Polygon exists. If no such Polygon exists, returns null.
     */
    Polygon bestMove(ArrayList<Polygon> ps)
        {

        Polygon result = null;
        double bestDist = Double.MAX_VALUE;

        // go through all polygons and determine the best move to make
        for (Polygon p : ps)
            {

            if (!p.soc.equals("UNOCCUPIED"))
                {
                continue; // not available
                } else if (p.geometry.getCentroid().distance(
                        region.geometry.getCentroid()) >= bestDist) // distance between centroids
                //else if( p.geometry.distance( region.geometry ) >= bestDist)
                // distance between region borders
                {
                continue; // we already have a better option
                } else if (!acceptable(p))
                {
                continue; // not an acceptable neighborhood
                } else
                { // otherwise it's an acceptable region and the closest region yet
                result = p;
                bestDist = p.geometry.distance(region.geometry);
                }
            }

        return result;
        }



    /**
     * Determines whether the Person's current location is acceptable. If not, attempts
     * to move the Person to a better location.
     */
    @Override
    public void step(SimState state)
        {

        if (!acceptable(region))
            { // the current location is unacceptable

//            System.out.println("unacceptable!");

            // try to find and move to a better location
            Polygon potentialNew = bestMove(((PolySchelling) state).polys);

            if (potentialNew != null) // a better location was found
                {
                updateLocation(potentialNew);
                } else // no better location was found. Stay in place.
                {
//                System.out.println("...but immobile");
                }
            }

        }

    }

//else if( p.geometry.distance( region.geometry ) >= bestDist)
/**
 ** Person.java
 **
 ** Copyright 2011 by Sarah Wise, Mark Coletti, Andrew Crooks, and
 ** George Mason University.
 **
 ** Licensed under the Academic Free License version 3.0
 **
 ** See the file "LICENSE" for more information
 **
 ** $Id$
 **/
package sim.app.geo.schellingpolygon;

import java.util.ArrayList;
import sim.engine.SimState;
import sim.engine.Steppable;



public class Person implements Steppable
    {

    String color;
    double preference;
    Polygon region;
    double personalThreshold = .5;
    int numMoves = 0;



    /**
     * Constructor function
     * @param c
     */
    public Person(String c)
        {
        color = c;
        }



    /**
     * Moves the Person to the given Polygon
     * @param p - the Polygon to which the Person should move
     */
    public void updateLocation(Polygon p)
        {

        // leave old tile, if previously was on a tile
        if (region != null)
            {
            region.residents.remove(this);
            region.soc = "UNOCCUPIED";
            }

        // go to new tile
        region = p;
        region.residents.add(this);
        region.soc = color;

        numMoves++; // increment the number of times moved
        }



    /**
     *
     * @param poly the proposed location
     * @return whether the given Polygon is an acceptable location for the Person,
     * based on the Person's personalThreshold
     */
    boolean acceptable(Polygon poly)
        {

        // decide if Person is unhappy with surroundings
        double unlike = 0, total = 0.;
        for (Polygon p : poly.neighbors)
            {

            if (p.soc.equals("UNOCCUPIED")) // empty spaces don't count
                {
                continue;
                }
            if (!p.soc.equals(color)) // is this neighbor an unlike neighbor?
                {
                unlike++;
                }
            total++; // total count of neighbors
            }
        double percentUnlike = unlike / Math.max(total, 1); // don't divide by 0!

        // if unhappy, return false
        if (percentUnlike >= personalThreshold)
            {
            return false;
            } else // if happy, return true
            {
            return true;
            }
        }



    /**
     * @param ps the list of Polygons open to the Person
     * @return the closest available Polygon that meets the Person's needs, if such
     * a Polygon exists. If no such Polygon exists, returns null.
     */
    Polygon bestMove(ArrayList<Polygon> ps)
        {

        Polygon result = null;
        double bestDist = Double.MAX_VALUE;

        // go through all polygons and determine the best move to make
        for (Polygon p : ps)
            {

            if (!p.soc.equals("UNOCCUPIED"))
                {
                continue; // not available
                } else if (p.geometry.getCentroid().distance(
                        region.geometry.getCentroid()) >= bestDist) // distance between centroids
                //else if(<SUF>
                // distance between region borders
                {
                continue; // we already have a better option
                } else if (!acceptable(p))
                {
                continue; // not an acceptable neighborhood
                } else
                { // otherwise it's an acceptable region and the closest region yet
                result = p;
                bestDist = p.geometry.distance(region.geometry);
                }
            }

        return result;
        }



    /**
     * Determines whether the Person's current location is acceptable. If not, attempts
     * to move the Person to a better location.
     */
    @Override
    public void step(SimState state)
        {

        if (!acceptable(region))
            { // the current location is unacceptable

//            System.out.println("unacceptable!");

            // try to find and move to a better location
            Polygon potentialNew = bestMove(((PolySchelling) state).polys);

            if (potentialNew != null) // a better location was found
                {
                updateLocation(potentialNew);
                } else // no better location was found. Stay in place.
                {
//                System.out.println("...but immobile");
                }
            }

        }

    }
