/**
 * Simulation of the disposition of space in a gallery exhibition.
 */
package org.onetwothree.grid;

import org.onetwothree.util.Wait;

import java.util.Arrays;
import java.util.Collections;
import java.util.Vector;

import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PImage;

/**
 * @author brad borevitz
 */
public class Disposition extends PApplet implements Constants {

    /**
     * Set serialVersionUID.
     */
    private static final long serialVersionUID = 1L;

    /**
     * Main method
     * 
     * @param args
     */
    public static void main(String args[]) {
        PApplet.main(new String[] {
                "org.onetwothree.grid.Disposition" });
    }

    PFont sansSerif;

    Game game;

    int state = INTRO;

    Vector highScores = new Vector();

    int scoreRows, scoreCols;

    int scoreThumbW, scoreThumbH;

    int margin;

    /**
     * Setup.
     */
    public void setup() {
        size(656, 456);
        frameRate(15);
        noCursor();
        colorMode(HSB, 100);

        sansSerif = createFont("sansserif", 32, true);
        textFont(sansSerif, 12);

        scoreCols = 4;
        scoreRows = 3;
        margin = height / 20;

        scoreThumbW = (width - 2 * margin) / scoreCols;
        scoreThumbH = (height - 3 * margin) / scoreRows;

        game = new Game();
        background(100);
    }

    /**
     * Draw called in a loop.
     */
    public void draw() {
        switch (state) {
        case INTRO:
            background(100);
            fill(0);
            textSize(20);
            textAlign(CENTER);
            text("Disposition of the Space ...", width / 2, 2 * margin);

            textSize(12);
            textAlign(LEFT);
            text(
                    "The game for dividing up the gallery. The more satisfied"
                            + " all the participants are, and the more positively they"
                            + " feel about each other, the higher"
                            + " the score. Initialy all assignments are random. Players"
                            + " can offer to trade what they have for what they want;"
                            + " but that doesn't mean anyone will agree. "
                            + "\n\nKey to Participant Characteristics:\n\n"
                            + "P :: Proximity is valued.\n"
                            + "C :: Contiguity is valued.\n"
                            + "L :: Location is valued.\n"
                            + "A :: Affinity is valued.\n"
                            + "R :: Requests are selective.\n"
                            + "O :: Offers are selective."
                    // + "S :: Sattisfaction is valued"
                    , width / 4, 4 * margin, width / 2, height - 5 * margin);
            state = INTRO_PAUSE;
            break;
        case INTRO_PAUSE:
            Wait.aSec(pause);
            state = PLAY;
            break;
        case PLAY:
            background(100);
            game.turn();
            if (game.isOver()) {
                state = OVER;
            }
            break;
        case OVER:
            PImage saved = game.save();
            topScore(new HighScore(saved, game.data.score));
            fill(0);
            textSize(20);
            textAlign(CENTER);
            text("Game Over", game.space.x + game.space.width / 2, game.space.y
                    + game.space.height / 2);
            textAlign(LEFT);
            state = OVER_PAUSE;
            break;
        case OVER_PAUSE:
            Wait.aSec(pause);
            state = HIGH;
            break;
        case HIGH:
            background(100);
            fill(0);
            textSize(20);
            textAlign(CENTER);
            text("High Scores", width / 2, margin + textAscent());
            textSize(12);
            for (int i = 0; i < scoreRows; i++) {
                for (int j = 0; j < scoreCols; j++) {
                    if (i * scoreCols + j < highScores.size()) {
                        HighScore score = (HighScore) highScores.get(i
                                * scoreCols + j);
                        image(score.img, margin + j * scoreThumbW, 2 * margin
                                + i * scoreThumbH, scoreThumbW, scoreThumbH);
                        text(score.score, margin + j * scoreThumbW
                                + scoreThumbW / 2, 2 * margin + i * scoreThumbH
                                + scoreThumbH / 2);
                    }
                }
            }
            textAlign(LEFT);
            state = HIGH_PAUSE;
            game = new Game();
            break;
        case HIGH_PAUSE:
            Wait.aSec(pause);
            state = INTRO;
            break;
        default:
            break;
        }
    }

    /**
     * if mouse is pressed start new game.
     */
    public void mousePressed() {
        game = new Game();
        state = OVER;
    }

    /**
     * Class models the instantiation of the simulation: the game.
     */
    class Game {
        Data data;

        Participant[] participants;

        int numParticipants;

        int minFractionsPer;

        int[] rankProxFactor;

        int[] rankContFactor;

        int[] rankScoreFactor;

        int[] affinityFactor;

        int[] reqFactor;

        int[] offFactor;

        int[] satisFactor;

        int turn;

        int diagonal; // for proximity

        boolean theSame; // if participants have the same characteristics

        int turns;

        int turnLimit;

        Space space;

        /**
         * Constructor for Game
         */
        public Game() {

            numParticipants = (int) (1 + random(1, maxParticipants));
            minFractionsPer = (int) (1 + random(1, 12));
            theSame = (random(1) > 0.5) ? true : false;
            turnLimit = (int) (numParticipants * random(10, maxRounds));

            turns = 0;
            turn = 0;

            // layout stuff
            // TODO bug - when ratio is close to 1, too far to left
            float ratio = random(1, maxRatio) / random(1, maxRatio);
            float dataWidth = margin * 7f;
            float spaceWidth = (width - dataWidth) - margin;
            float spaceHeight = height - 2 * margin;
            int layoutWidth, layoutHeight, layoutX, layoutY;

            if (spaceWidth / spaceHeight <= ratio) {
                // width is determinate
                layoutWidth = (int) spaceWidth;
                layoutHeight = (int) ((1f / ratio) * layoutWidth);
                layoutX = margin;
                layoutY = (int) ((height - layoutHeight) / 2f);
            } else {
                // height is determinate
                layoutHeight = (int) spaceHeight;
                layoutWidth = (int) (ratio * layoutHeight);
                layoutX = (int) ((spaceWidth - layoutWidth) / 2f);
                layoutY = margin;
            }

            space = new Space(layoutX, layoutY, layoutWidth, layoutHeight);
            space.layout();
            space.score();
            space.populate();
            space.distribute();
            for (int i = 0; i < numParticipants; i++) {
                participants[i].rank();
            }
            data = new Data((int) (spaceWidth + margin), 0, (int) dataWidth,
                    height);
        }

        /**
         * Method to populate all character arrays at random
         */
        public void characterize() {
            characterize(0);
            for (int i = 1; i < numParticipants; i++) {
                rankProxFactor[i] = rankProxFactor[0];
                rankContFactor[i] = rankContFactor[0];
                rankScoreFactor[i] = rankScoreFactor[0];
                affinityFactor[i] = affinityFactor[0];
                reqFactor[i] = reqFactor[0];
                offFactor[i] = offFactor[0];
                satisFactor[i] = satisFactor[0];
            }
        }

        /**
         * Method to populate a single character array at random
         * 
         * @param self
         */
        public void characterize(int self) {
            int p = (int) random(101);
            int c = (int) random(101);
            int v = (int) random(101);
            float total = p + c + v;
            rankProxFactor[self] = (int) (100 * p / total);
            rankContFactor[self] = (int) (100 * c / total);
            rankScoreFactor[self] = (int) (100 * v / total);
            affinityFactor[self] = (int) (100 * random(1));
            reqFactor[self] = (int) (100 * random(1));
            offFactor[self] = (int) (100 * random(1));
            satisFactor[self] = (int) (100 * random(1));
        }

        /**
         * Method to determine if game is over.
         * 
         * @return
         */
        private boolean isOver() {
            if (turns > turnLimit) {
                return true;
            } else {
                return false;
            }
        }

        private PImage save() {
            PImage screen = new PImage(width, height);
            loadPixels();
            screen.pixels = (int[]) pixels.clone();
            PImage saved = new PImage(space.width, space.height);
            saved.copy(screen, space.x, space.y, space.width, space.height + 5,
                    0, 0, space.width, space.height);
            return saved;
        }

        /**
         * Method that lets one particpant take their turn; checks for end of
         * game and calls endGame if necessary. Is called in loop by draw();
         */
        public void turn() {
            boolean trade = participants[turn].trade();
            data.update(turn, trade);

            background(100);
            space.draw();
            data.draw();
            turns++;
            turn++;
            if (turn >= numParticipants) {
                data.evaluate();
                turn = 0;
            }
        }

        /**
         * class to calculate and display meta data on process
         */
        public final class Data {

            int x, y, width, height;

            float colx, colw;

            int[] participantAvgPossRank;

            int[] participantAvgFracRank;

            int[] participantAvgSatisfaction;

            int[] participantAvgAffin;

            double meanAvgPossRank, startMeanAvgPossRank, minMeanAvgPossRank,
                    maxMeanAvgPossRank;

            double stdAvgPossRank;

            double meanAvgFracRank, startMeanAvgFracRank, minMeanAvgFracRank,
                    maxMeanAvgFracRank;

            double stdAvgFracRank;

            double meanAvgSatisfaction, startMeanAvgSatisfaction,
                    minMeanAvgSatisfaction, maxMeanAvgSatisfaction;

            double stdAvgSatisfaction;

            double meanAvgAffin, startMeanAvgAffin, minMeanAvgAffin,
                    maxMeanAvgAffin;

            double stdAvgAffin;

            int traded, trades;

            double tradeRate, minTradeRate, maxTradeRate;

            int score;

            /**
             * Constructor for Data Class.
             * 
             * @param x
             * @param y
             * @param width
             * @param height
             */
            Data(int x, int y, int width, int height) {
                // layout
                this.x = x;
                this.y = y;
                this.width = width;
                this.height = height;
                colw = 5f * margin; // measures in data width -2
                colx = x + (width - colw) / 2f;

                participantAvgPossRank = new int[participants.length];
                participantAvgFracRank = new int[participants.length];
                participantAvgSatisfaction = new int[participants.length];
                participantAvgAffin = new int[participants.length];
                traded = 0;
                trades = 0;
                tradeRate = 0.5;

                for (int i = 0; i < participants.length; i++) {
                    update(i);
                }

                minMeanAvgAffin = Double.MAX_VALUE;
                minMeanAvgSatisfaction = Double.MAX_VALUE;
                minTradeRate = Double.MAX_VALUE;
                maxMeanAvgAffin = Double.MIN_VALUE;
                maxMeanAvgSatisfaction = Double.MIN_VALUE;
                maxTradeRate = Double.MIN_VALUE;

                evaluate();
                startMeanAvgPossRank = meanAvgPossRank;
                startMeanAvgFracRank = meanAvgFracRank;
                startMeanAvgAffin = meanAvgAffin;
                startMeanAvgSatisfaction = meanAvgSatisfaction;

            }

            /**
             * Method for drawing a grid of all the affinity relations; rows
             * show the affinity towards each participant of the particpant
             * whose color is shown on the left.
             * 
             * @param x
             * @param y
             * @param height
             * @param width
             */
            public void affinityWidget(int x, int y, int width, int height) {
                float pxWidth = (float) width / (numParticipants + 1);
                float pxHeight = (float) height / (numParticipants + 1);

                noStroke();
                for (int i = 0; i < numParticipants + 1; i++) {
                    for (int j = 0; j < numParticipants + 1; j++) {
                        // draw pixel
                        if (!(i == j)) {
                            if (i == 0) {
                                fill(participants[j - 1].color);
                            } else if (j == 0) {
                                fill(participants[i - 1].color);
                            } else {
                                fill(gradient(
                                        participants[i - 1].affinities[j - 1] + 50,
                                        gradient));
                            }
                            rect(x + j * pxWidth, y + i * pxHeight, pxWidth,
                                    pxHeight);
                        }
                    }
                }
                // draw frame
                stroke(0);
                noFill();
                rect(x, y, width, height);
            }

            /**
             * Method for drawing a bar indicator for value and std deviation
             * 
             * @param x
             * @param y
             * @param height
             * @param width
             * @param value
             * @param stdDev
             * @param min
             * @param max
             */
            public void barWidget(int x, int y, int width, int height,
                    float value, float minValue, float maxValue, float stdDev,
                    float min, float max) {
                float range = max - min;
                float stdDevWidth = width * ((stdDev) / range);
                float valuePos = (width * ((value - min) / range));
                float minValuePos = (width * ((minValue - min) / range));
                if (minValuePos <= 0) {
                    minValuePos = 1;
                }
                float maxValuePos = (width * ((maxValue - min) / range));
                if (maxValuePos >= width) {
                    maxValuePos = width - 1;
                }
                // background (red/black)
                noStroke();
                float inc = (float) width / 8;
                for (int i = 0; i < 8; i++) {
                    fill(gradient(i * 12.5f, gradient));
                    rect(x + i * inc, y, inc, height);
                }
                // white mask stripe
                fill(0, 0, 100, 50);
                rect(x, y + height / 3f, width, height / 3f);

                strokeWeight(1);
                // frame
                noFill();
                stroke(0);
                rect(x, y, width, height);
                // stdDev
                if (stdDev != 0) {
                    fill(50);
                    noStroke();
                    rect(x + valuePos - (stdDevWidth / 2), y, stdDevWidth,
                            height - 1);
                }
                // minValue
                stroke(0);
                line(x + minValuePos, y, x + minValuePos, y + height - 2);
                // maxValue
                stroke(0);
                line(x + maxValuePos, y, x + maxValuePos, y + height - 2);
                // value
                stroke(100);
                line(x + valuePos - 1, y, x + valuePos - 1, y + height - 2);
            }

            /**
             * Method to draw the characteristics of all the participants
             * 
             * @param x
             * @param y
             * @param width
             * @param height
             */
            public void characteristics(int x, int y, int width, int height) {
                Object[] dataColumns = { rankProxFactor, rankContFactor,
                        rankScoreFactor, affinityFactor, reqFactor, offFactor
                // ,satisFactor
                };
                char[] dataKey = { 'P', 'C', 'L', 'A', 'R', 'O'
                // ,'S'
                };

                float pxWidth = (float) width / (dataKey.length + 1);

                // draw key columns
                fill(0);
                float size = textAscent();
                if (size > pxWidth) {
                    size = pxWidth;
                }
                textSize(size);
                int xoffset = (int) ((pxWidth - textWidth(dataKey[0])) / 2f);
                for (int i = 1; i < dataKey.length + 1; i++) {
                    text(dataKey[i - 1], x + xoffset + i * pxWidth, y
                            + textAscent());
                }

                float pxHeight = (height - size) / (numParticipants);

                noStroke();
                // draw key row
                for (int i = 0; i < numParticipants; i++) {
                    fill(participants[i].color);
                    rect(x, y + i * pxHeight + size, pxWidth, pxHeight);
                }

                // draw data rows
                for (int i = 0; i < numParticipants; i++) {
                    for (int j = 1; j < dataColumns.length + 1; j++) {
                        int[] data = (int[]) dataColumns[j - 1];
                        fill(gradient(data[i], gradient));
                        rect(x + j * pxWidth, y + i * pxHeight + size, pxWidth,
                                pxHeight);
                    }
                }
                // draw frame
                stroke(0);
                noFill();
                rect(x, y, width, height);
            }

            /**
             * draws the data in the applet
             * 
             */
            public void draw() {

                // bg
                noStroke();
                // stroke(0);
                fill(100);
                rect(x, y, width, height);

                // score
                fill(0);
                textSize(0.45f * margin);
                text("Game Score: " + score, colx, 1.5f * margin
                        - textDescent());

                // Mean Affinity Towards Others
                text("Group Affinity", colx, 2.5f * margin - textDescent());
                barWidget((int) colx, (int) (2.5f * margin), (int) colw,
                        (margin / 2), (float) meanAvgAffin,
                        (float) minMeanAvgAffin, (float) maxMeanAvgAffin,
                        (float) stdAvgAffin, -50f, 50f);

                // Trade rate
                fill(0);
                text("Group Agreeableness", colx, 4f * margin - textDescent());
                barWidget((int) colx, (int) (4f * margin), (int) colw,
                        (margin / 2), (float) tradeRate, (float) minTradeRate,
                        (float) maxTradeRate, 0f, 0f, 1f);

                // group Satisfaction
                fill(0);
                text("Group Satisfaction", colx, 5.5f * margin - textDescent());
                barWidget((int) colx, (int) (5.5f * margin), (int) colw,
                        (margin / 2), (float) meanAvgSatisfaction,
                        (float) minMeanAvgSatisfaction,
                        (float) maxMeanAvgSatisfaction,
                        (float) stdAvgSatisfaction, 0f, 100f);

                // satisfaction
                fill(0);
                text("Participant Satisfaction", colx, 7f * margin
                        - textDescent());
                satisfactionWidget((int) colx, (int) (7f * margin), (int) colw,
                        (margin));

                // Affinity Chart
                fill(0);
                text("Participant Affinities", colx, 9f * margin
                        - textDescent());
                affinityWidget((int) colx, (int) (9f * margin), (int) colw,
                        (int) colw);

                // characteristics
                fill(0);
                text("Participant Characteristics", colx, 15f * margin
                        - textDescent());
                characteristics((int) colx, (int) (15f * margin), (int) colw,
                        (int) (4f * margin));
            }

            /**
             * Method evaluates all the statistical data
             */
            public void evaluate() {
                // poss rank
                meanAvgPossRank = mean(participantAvgPossRank,
                        participantAvgPossRank.length);
                stdAvgPossRank = standardDeviation(participantAvgPossRank,
                        participantAvgPossRank.length, meanAvgPossRank);
                // affin
                meanAvgAffin = mean(participantAvgAffin,
                        participantAvgAffin.length);
                if (meanAvgAffin < minMeanAvgAffin) {
                    minMeanAvgAffin = meanAvgAffin;
                }
                if (meanAvgAffin > maxMeanAvgAffin) {
                    maxMeanAvgAffin = meanAvgAffin;
                }
                stdAvgAffin = standardDeviation(participantAvgAffin,
                        participantAvgAffin.length, meanAvgAffin);
                // frac rank
                meanAvgFracRank = mean(participantAvgFracRank,
                        participantAvgFracRank.length);
                stdAvgFracRank = standardDeviation(participantAvgFracRank,
                        participantAvgFracRank.length, meanAvgFracRank);
                // satisfaction
                meanAvgSatisfaction = mean(participantAvgSatisfaction,
                        participantAvgSatisfaction.length);
                if (meanAvgSatisfaction < minMeanAvgSatisfaction) {
                    minMeanAvgSatisfaction = meanAvgSatisfaction;
                }
                if (meanAvgSatisfaction > maxMeanAvgSatisfaction) {
                    maxMeanAvgSatisfaction = meanAvgSatisfaction;
                }
                stdAvgSatisfaction = standardDeviation(
                        participantAvgSatisfaction,
                        participantAvgSatisfaction.length, meanAvgSatisfaction);
                // trade rate
                if (trades != 0) {
                    tradeRate = (double) traded / trades;
                }
                if (tradeRate < minTradeRate) {
                    minTradeRate = tradeRate;
                }
                if (tradeRate > maxTradeRate) {
                    maxTradeRate = tradeRate;
                }
                // score
                float affinitySD = (float) ((stdAvgAffin == 0) ? Float.MIN_VALUE
                        : stdAvgAffin);
                float satisfactionSD = (float) ((stdAvgSatisfaction == 0) ? Float.MIN_VALUE
                        : stdAvgSatisfaction);
                score = (int) (10 * ((meanAvgAffin / affinitySD) + (meanAvgSatisfaction / satisfactionSD)));
            }

            /**
             * method returns color in a specified gradient pallet according to
             * the value specified from 0 to 100.
             * 
             * @param value
             * @param type
             * @return int
             */
            public int gradient(float value, int type) {
                int grad = 0;
                switch (type) {
                case GREY:
                    grad = color(100f - value);
                    break;
                case GREY2:
                    grad = color(value);
                    break;
                case RED_BLACK:
                    grad = color(0f, 100f, 100f - value);
                    break;
                case RED_BLACK2:
                    grad = color(0f, 50 + abs(50 - (value)), 100f - value);
                    break;
                case RED_GREEN:
                    grad = color(value * 0.4f, participantSaturation,
                            participantBrightness);
                    break;
                default:
                    break;
                }
                return grad;
            }

            public void message(String msg, int x, int y, int width,
                    float height) {
                fill(0);
                float margin = width / 20;
                text(msg, x + margin, y + margin, width - (2 * margin), height
                        - (2 * margin));

                // draw frame
                stroke(0);
                noFill();
                rect(x, y, width, height);
            }

            /**
             * Prints data on the console
             * 
             */
            public void print() {
                println("::Satisfaction- AVG: " + (float) meanAvgPossRank
                        + " (STDV: " + (float) stdAvgPossRank + ") ");

                println("::Affinity- AVG: " + (float) meanAvgAffin + " (STDV: "
                        + (float) stdAvgAffin + ") ");
            }

            /**
             * Method for drawing a chart of all the players levels of
             * satisfaction.
             * 
             * @param x
             * @param y
             * @param height
             * @param width
             */
            public void satisfactionWidget(int x, int y, int width, int height) {
                float pxWidth = (float) width / (numParticipants);

                // sort satisfaction
                Satisfaction[] s = new Satisfaction[numParticipants];
                for (int i = 0; i < numParticipants; i++) {
                    s[i] = new Satisfaction(i, participantAvgSatisfaction[i]);
                }
                Arrays.sort(s);
                
                noStroke();
                for (int i = 0; i < numParticipants; i++) { // column
                    for (int j = 0; j < 2; j++) { // row
                        // draw pixel
                        if (j == 0) {
                            fill(participants[s[i].participant].color);
                        } else {
                            fill(gradient(s[i].level,
                                    gradient));
                        }
                        rect(x + i * pxWidth, y + j * height / 2, pxWidth,
                                height / 2);
                    }
                }
                // draw frame
                stroke(0);
                noFill();
                rect(x, y, width, height);
            }

            /**
             * Method updates the participant's data
             * 
             * @param participant
             *            index of participant in participants array
             */
            public void update(int participant) {

                participantAvgPossRank[participant] = (int) mean(
                        participants[participant].possessRanks,
                        participants[participant].numPossessions);
                participantAvgAffin[participant] = (int) mean(removeSelf(
                        participants[participant].affinities, turn),
                        participants.length - 1);
                int[] tmp = removeMinusOnes(participants[participant].ranks);
                participantAvgFracRank[participant] = (int) mean(tmp,
                        tmp.length);
                participantAvgSatisfaction[participant] = (100 + (participantAvgPossRank[participant] - participantAvgFracRank[participant])) / 2;
            }

            /**
             * Method updates the participant's data; this version tracks the
             * trades too
             * 
             * @param participant
             * @param trade
             */
            public void update(int participant, boolean trade) {
                trades++;
                if (trade) {
                    traded++;
                }
                update(participant);
            }
        }

        /**
         * Class for sorted satisfaction
         */
        class Satisfaction implements Comparable {
            int participant;

            int level;

            Satisfaction(int participant, int level) {
                this.participant = participant;
                this.level = level;
            }

            /**
             * comparator
             */
            public int compareTo(Object o) {
                Satisfaction s = (Satisfaction) o;
                return level - s.level;
            }
        }

        /**
         * Class models the participants in the exhibition
         */
        class Participant {

            int self; // index of self in participants array

            int color;

            int[] possessions; // index in fractions of possession

            int numPossessions;

            int[] affinities;

            int[] ranks;

            int[] possessRanks;

            /**
             * Constructor for participant
             * 
             * @param self
             * @param color
             */
            Participant(int self, int color) {
                this.self = self;
                numPossessions = 0;
                this.color = color;
                possessions = new int[minFractionsPer + 5];
                Arrays.fill(possessions, -1);
                possessRanks = new int[minFractionsPer + 5];
                affinities = new int[numParticipants];
                Arrays.fill(affinities, 0);
                ranks = new int[space.xGrid * space.yGrid];
                Arrays.fill(ranks, 0);
            }

            /**
             * Method for converting
             */
            public float factorToAffin(int factor) {
                return factor / 100f;
            }

            /**
             * Method for converting offerFactor to a low number
             */
            public int factorToOffer(int factor) {
                return (int) (1 + (offMax * factor / 100f));
            }

            /**
             * Method for converting
             */
            public int factorToRequest(int factor) {
                return (int) (1 + (reqMax * factor / 100f));
            }

            /**
             * Method for converting
             */
            public float factorToSatis(int factor) {
                return factor / 400f;
            }

            /**
             * Method for finding a fraction possesed by participant
             * 
             * @param frac
             * @return
             */
            public int find(int frac) {
                for (int i = 0; i < possessions.length; i++) {
                    if (possessions[i] == frac) {
                        return i;
                    }
                }
                return -1;
            }

            /**
             * Given an integer array (index of possesRanks) return the index of
             * the max value don't return door!
             * 
             * @param array
             * @param outOf
             * @return
             */
            public int getIndexOfHighestValue(int[] array, int outOf) {
                Pair[] parallel = new Pair[array.length];
                for (int i = 0; i < array.length; i++) {
                    parallel[i] = new Pair(i, array[i]);
                }
                Arrays.sort(parallel);
                // expand choices if value of next item is the same
                while (parallel[(array.length - 1) - (outOf - 1)].value == parallel[(array.length - 1)
                        - outOf].value) {
                    outOf++;
                }
                return parallel[(array.length - 1) - (int) random(outOf)].index;
            }

            /**
             * Given an integer array (index of ranks) return the index of the
             * min value
             * 
             * @param array
             * @param length
             *            number of meaningful entries in array
             * @param outOf
             * @return
             */
            public int getIndexOfLowestValue(int[] array, int length, int outOf) {
                if (length == 1) {
                    return 0;
                }
                Pair[] parallel = new Pair[length];
                for (int i = 0; i < length; i++) {
                    parallel[i] = new Pair(i, array[i]);
                }
                Arrays.sort(parallel);
                // expand choices if value of next item is the same
                if (outOf > length) {
                    outOf = length;
                }
                while ((outOf < length)
                        && (parallel[outOf - 1].value == parallel[outOf].value)) {
                    outOf++;
                }
                return parallel[(int) random(outOf)].index;
            }

            /**
             * Method for ranking properties. based on score, proximity, and
             * affinity w/ owner.
             * 
             */
            public void rank() {
                // rank fractions owned by others; mark owned by self with -1
                for (int i = 0; i < ranks.length; i++) {
                    if ((i == space.door) || (space.fractions[i].owner == self)) {
                        ranks[i] = -1;
                    } else {
                        // rank is score
                        ranks[i] = rankScoreFactor[self]
                                * space.fractions[i].score;
                        // plus proximity to owned fractions
                        ranks[i] += rankProxFactor[self] * selfProximity(i);

                        // plus continuity
                        ranks[i] += rankContFactor[self]
                                * space.contiguity(i, self);
                        ranks[i] = ranks[i] / 100;
                    }
                }
                // rank fractions owned by self
                for (int i = 0; i < numPossessions; i++) {
                    // rank is score
                    possessRanks[i] = rankScoreFactor[self]
                            * space.fractions[possessions[i]].score;
                    // plus proximity
                    possessRanks[i] += rankProxFactor[self] * selfProximity(i);
                    // plus continuity
                    possessRanks[i] += rankProxFactor[self]
                            * space.contiguity(possessions[i], self);
                    possessRanks[i] = possessRanks[i] / 100;
                    // println(selfProximity(i) + ":" +
                    // space.fractions[possessions[i]].score + ":"+
                    // possessRanks[i]);
                }
            }

            /**
             * Method for recieving and disposing of a trade request
             * 
             * @param other
             * @param requestFracIndex
             * @param offerFracIndex
             * @return
             */
            public boolean requestTrade(int other, int requestFracIndex,
                    int offerFracIndex) {
                int idx = find(requestFracIndex);
                if (idx < 0) {
                    println("Error: failed to find requested item!");
                    return false;
                } else if (random(101) < (affinities[other] + 50)
                        + (ranks[offerFracIndex] - possessRanks[idx])
                        - (factorToSatis(satisFactor[self]) * data.participantAvgSatisfaction[self])) {

                    // accept trade:change affinity of other in relation to
                    // difference of value
                    affinities[other] += (int) (factorToAffin(affinityFactor[self]) * (ranks[offerFracIndex] - possessRanks[idx]));
                    if (affinities[other] > 50) {
                        affinities[other] = 50;
                    }
                    if (affinities[other] < -50) {
                        affinities[other] = -50;
                    }

                    // take possesion of offer
                    possessions[idx] = offerFracIndex;

                    // mark new owner of request in fractions
                    space.fractions[requestFracIndex].owner = other;

                    // update rankings
                    rank();

                    return true;
                } else {
                    // reject trade: change affinity of other as
                    // consolation/debt
                    affinities[other] -= (int) (factorToAffin(affinityFactor[self]) * (ranks[offerFracIndex] - possessRanks[idx]));
                    if (affinities[other] > 50) {
                        affinities[other] = 50;
                    }
                    if (affinities[other] < -50) {
                        affinities[other] = -50;
                    }
                    return false;
                }
            }

            /**
             * function to return avg proximity of fraction to all of self's
             * possessed fractions
             * 
             * @param fracIndex
             * @return
             */
            public int selfProximity(int fracIndex) {
                int sumProximity = 0;
                for (int j = 0; j < numPossessions; j++) {
                    sumProximity += space.proximity(possessions[j], fracIndex);
                }
                return sumProximity / numPossessions;
            }

            /**
             * Method for trading properties; returns true if succeessful
             * 
             * @return
             */
            public boolean trade() {

                int offerPossIndex, offerFracIndex, requestFracIndex;
                offerPossIndex = getIndexOfLowestValue(possessRanks,
                        numPossessions, factorToOffer(offFactor[self]));
                offerFracIndex = possessions[offerPossIndex];
                requestFracIndex = getIndexOfHighestValue(ranks,
                        factorToRequest(reqFactor[self]));
                int other = space.fractions[requestFracIndex].owner;
                boolean accepted = participants[other].requestTrade(self,
                        requestFracIndex, offerFracIndex);

                if (accepted) {
                    // trade accepted:change affinity of other in relation to
                    // difference of value
                    affinities[other] += (int) (factorToAffin(affinityFactor[self]) * (ranks[requestFracIndex] - possessRanks[offerPossIndex]));
                    if (affinities[other] > 50) {
                        affinities[other] = 50;
                    }
                    if (affinities[other] < -50) {
                        affinities[other] = -50;
                    }

                    // take possesion of request
                    possessions[offerPossIndex] = requestFracIndex;

                    // mark new owner of offer in fractions
                    space.fractions[offerFracIndex].owner = other;

                    // update rankings
                    rank();
                } else {
                    // trade rejected: change affinity of other
                    affinities[other] -= (int) (factorToAffin(affinityFactor[self]) * (ranks[requestFracIndex] - possessRanks[offerPossIndex]));
                    if (affinities[other] > 50) {
                        affinities[other] = 50;
                    }
                    if (affinities[other] < -50) {
                        affinities[other] = -50;
                    }
                }
                return accepted;
            }
        }

        /**
         * Class models the whole space.
         */
        class Space {
            int x, y, width, height; // dimensions in px

            int xGrid, yGrid;

            int door;

            Fraction[] fractions;

            /**
             * Constructor for Space
             * 
             * @param x
             * @param y
             * @param width
             * @param height
             */
            public Space(int x, int y, int width, int height) {
                this.x = x;
                this.y = y;
                this.width = width;
                this.height = height;
            }

            /**
             * Method for getting contiguity score of fraction of a given
             * participant
             * 
             * @param fraction
             * @param participant
             * @return
             */
            public int contiguity(int fraction, int participant) {
                int contiguity = 0;
                for (int i = -1; i <= 1; i++) {
                    for (int j = -1; j <= 1; j++) {
                        int neighbor = fraction + i + j * yGrid;
                        if ((neighbor >= 0) && (neighbor < fractions.length)) {
                            if (fractions[neighbor].owner == participant) {
                                if (((i == j) || (i == -j)) && (i != 0)) {
                                    contiguity += 10;
                                } else {
                                    contiguity += 15;
                                }
                            }
                        }
                    }
                }
                return contiguity;
            }

            /**
             * Method to distribute fractions of the space to participants.
             */
            public void distribute() {
                Vector unowned = new Vector();
                // get all possible fractions
                for (int i = 0; i < xGrid * yGrid; i++) {
                    if (!fractions[i].has(A_DOOR)) {
                        unowned.add(new Integer(i));
                    }
                }
                // pass them out
                while (unowned.size() > 0) {
                    for (int i = 0; i < participants.length; i++) {
                        if (unowned.size() > 0) {
                            int idx = (int) random(unowned.size());
                            int property = ((Integer) (unowned.get(idx)))
                                    .intValue();
                            unowned.remove(idx);
                            participants[i].possessions[participants[i].numPossessions] = property;
                            participants[i].numPossessions++;
                            fractions[property].owner = i;
                        } else {
                            break;
                        }
                    }
                }
                fractions[door].owner = -1;
            }

            /**
             * method to draw the entire array of fractions
             */
            public void draw() {
                for (int i = 0; i < fractions.length; i++) {
                    fractions[i].draw();
                }
            }

            /**
             * Method to layout space and implement fraction walls and enterence
             * and create fractions. Space should be divided into roughly square
             * pieces with enough pieces for all participants to have at least
             * the minimum number. (The enterence fraction is not distributed.)
             * 
             * Later version may have random shapes of walls; this version will
             * simply line the space and pick the enterence at random.
             */
            public void layout() {
                Vector edges = new Vector();

                float FracLength = sqrt((float) (width * height)
                        / (numParticipants * minFractionsPer + 1));
                xGrid = ceil(width / FracLength);
                yGrid = ceil(height / FracLength);
                float xLength = (float) width / xGrid;
                float yLength = (float) height / yGrid;
                diagonal = (int) sqrt((xGrid * xGrid) + (yGrid * yGrid));

                fractions = new Fraction[xGrid * yGrid];

                for (int ix = 0; ix < xGrid; ix++) {
                    for (int iy = 0; iy < yGrid; iy++) {
                        int structure = 0;
                        if (ix == 0) {
                            structure |= WALL_LEFT;
                            structure |= A_WALL;
                        }
                        if (ix == xGrid - 1) {
                            structure |= WALL_RIGHT;
                            structure |= A_WALL;
                        }
                        if (iy == 0) {
                            structure |= WALL_TOP;
                            structure |= A_WALL;
                        }
                        if (iy == yGrid - 1) {
                            structure |= WALL_BOTTOM;
                            structure |= A_WALL;
                        }
                        int fracNum = ix * yGrid + iy;
                        fractions[fracNum] = new Fraction(ix, iy, x
                                + (ix * xLength), y + (iy * yLength), xLength,
                                yLength, structure);
                        if (fractions[fracNum].has(A_WALL)) {
                            edges.add(new Integer(fracNum));
                        }
                    }
                }
                // choose door location on edge
                int loc = ((Integer) edges.get((int) random(edges.size())))
                        .intValue();
                door = loc;
                Vector walls = new Vector();
                for (int i = 0; i < 4; i++) {
                    int wall = (int) pow(2, i);
                    if ((fractions[loc].structures & wall) == wall) {
                        walls.add(new Integer(wall));
                    }
                }
                // choose door position from possible walls
                int pos = ((Integer) walls.get((int) random(walls.size())))
                        .intValue();
                // turn off wall in that position
                fractions[loc].structures &= ~pos;
                // turn on door
                pos = (int) (pow(2, 5 + log2(pos)));
                fractions[loc].structures |= pos;
                fractions[loc].structures |= A_DOOR;
            }

            /**
             * utility to check ownership in array
             */
            public void ownerTest() {
                for (int i = 0; i < fractions.length; i++) {
                    if (i != door) {
                        if (participants[fractions[i].owner].find(i) == -1) {
                            println("- for fraction " + i
                                    + " owner should be: " + fractions[i].owner
                                    + " door: " + door);
                        }
                    }
                }
            }

            /**
             * Method to instantiate participants
             */
            public void populate() {
                participants = new Participant[numParticipants];
                rankProxFactor = new int[numParticipants];
                rankContFactor = new int[numParticipants];
                rankScoreFactor = new int[numParticipants];
                affinityFactor = new int[numParticipants];
                reqFactor = new int[numParticipants];
                offFactor = new int[numParticipants];
                satisFactor = new int[numParticipants];
                for (int i = 0; i < numParticipants; i++) {
                    // pick color avoiding red at hue = 0
                    participants[i] = new Participant(i, color(100 * (i + 1)
                            / (numParticipants + 1), participantSaturation,
                            participantBrightness));
                    if (!theSame) {
                        characterize(i);
                    }
                }
                if (theSame) {
                    characterize();
                }

            }

            /**
             * Method for getting proximity scrore between fractions; proximity
             * given as difference betwee the longest distance (diagonal) and
             * the distance between the two.
             * 
             * @param a
             *            index of fraction
             * @param b
             *            index of fraction
             * 
             * @return proximity
             */
            public int proximity(int a, int b) {
                return (int) (100 * (diagonal - sqrt((pow(space.fractions[a].ix
                        - space.fractions[b].ix, 2) + pow(space.fractions[a].iy
                        - space.fractions[b].iy, 2)))) / diagonal);
            }

            /**
             * utility to ranks for eroneaus -1's
             * 
             * @param part
             */
            public void rankTest(int part) {
                for (int i = 0; i < participants[part].ranks.length; i++) {
                    if (i != door) {
                        if (participants[part].ranks[i] == -1) {
                            if (fractions[i].owner != part) {
                                println("-- for fraction " + i + " in ranks of"
                                        + part + "owner should be: "
                                        + fractions[i].owner
                                        + " ;found in possessions: "
                                        + participants[part].find(i));
                            }
                        }
                    }
                }
            }

            /**
             * Method to calculate the desirability scores of the fractions of
             * the space.
             */
            public void score() {
                // based on distance from door

                int max = Integer.MIN_VALUE;
                int min = Integer.MAX_VALUE;

                for (int i = 0; i < fractions.length; i++) {
                    fractions[i].score = proximity(i, door);

                    // based on presence of walls
                    if (fractions[i].has(A_WALL)) {
                        fractions[i].score += 20;
                    }

                    // based on random factors
                    if (random(100) > 80) {
                        fractions[i].score += 5;
                    }

                    if (fractions[i].score > max) {
                        max = fractions[i].score;
                    }
                    if (fractions[i].score < min) {
                        min = fractions[i].score;
                    }
                }

                for (int i = 0; i < fractions.length; i++) {
                    fractions[i].score = 100 * (fractions[i].score - min)
                            / (max - min);
                }
                fractions[door].score = 0;
            }

            /**
             * Class models the pieces of the space divided by a grid
             */
            class Fraction {

                int structures;

                int owner;

                int score; // desirability

                float x, y, width, height;

                int ix, iy; // coordinates in grid of fractions

                /**
                 * Constructor for Fractions
                 * 
                 * @param ix
                 * @param iy
                 * @param x
                 * @param y
                 * @param width
                 * @param height
                 * @param structures
                 */
                public Fraction(int ix, int iy, float x, float y, float width,
                        float height, int structures) {
                    this.ix = ix;
                    this.iy = iy;
                    this.x = x;
                    this.y = y;
                    this.width = width;
                    this.height = height;
                    this.structures = structures;
                    owner = -1;
                }

                /**
                 * Method for finding if point lies inside of Fraction.
                 * 
                 * @param xx
                 * @param yy
                 * @return
                 */
                public boolean contains(int xx, int yy) {
                    return ((xx >= x) && (yy >= y) && (xx < x + width) && (yy < y
                            + height));
                }

                /**
                 * Method to draw each fraction
                 */
                public void draw() {
                    if (this.has(A_DOOR)) {
                        fill(0, 0, 100);
                    } else {
                        fill(participants[owner].color);
                    }
                    noStroke();
                    rect(x, y, width, height);
                    // draw walls
                    if (this.has(A_WALL)) {
                        stroke(0);
                        strokeCap(SQUARE);
                        int thick = 2;
                        strokeWeight(thick * 2);
                        if (this.has(WALL_LEFT)) {
                            line(x + thick, y, x + thick, y + height);
                        }
                        if (this.has(WALL_TOP)) {
                            line(x, y + thick, x + width, y + thick);
                        }
                        if (this.has(WALL_RIGHT)) {
                            line(x - thick + width, y, x - thick + width, y
                                    + height);
                        }
                        if (this.has(WALL_BOTTOM)) {
                            line(x, y + height - thick, x + width, y + height
                                    - thick);
                        }
                    }
                    // draw door
                    if (this.has(A_DOOR)) {
                        stroke(0);
                        strokeCap(PROJECT);
                        int thick = 2;
                        smooth();
                        float doorX = sqrt(width * width / 2);
                        float doorY = sqrt(height * height / 2);
                        if (this.has(DOOR_LEFT)) {
                            // good
                            line(x + thick, y, x + thick + doorY, y + doorY);
                        }
                        if (this.has(DOOR_TOP)) {
                            line(x, y + thick, x + doorX, y + thick + doorX);
                        }
                        if (this.has(DOOR_RIGHT)) {
                            // good
                            line(x - thick + width, y, x - thick - doorY
                                    + width, y + doorY);
                        }
                        if (this.has(DOOR_BOTTOM)) {
                            // good
                            line(x, y + height - thick, x + doorX, y
                                    + (width - doorX) - thick);
                        }
                        noSmooth();
                    }
                }

                /**
                 * Method to determine if fraction has a particular structural
                 * feature (wall or door).
                 * 
                 * @param structure
                 * @return
                 */
                public boolean has(int structure) {
                    if ((structures & structure) == structure) {
                        return true;
                    } else {
                        return false;
                    }
                }

                /**
                 * Method to determine if fraction has a particular structural
                 * feature (wall or door).
                 */
                public int walls() {
                    int count = 0;
                    if (this.has(A_WALL)) {
                        if (this.has(WALL_LEFT)) {
                            count++;
                        }
                        if (this.has(WALL_TOP)) {
                            count++;
                        }
                        if (this.has(WALL_RIGHT)) {
                            count++;
                        }
                        if (this.has(WALL_BOTTOM)) {
                            count++;
                        }
                    }
                    return count;
                }
            }
        }
    }

    /**
     * Class models the end state of played out games including image and score
     */
    class HighScore implements Comparable {
        int score;

        PImage img;

        /**
         * Constructor for High Score takes picture and calculates score.
         */
        HighScore(PImage img, int score) {
            this.img = new PImage(scoreThumbW, scoreThumbH);
            float scale = min((float) scoreThumbW / img.width,
                    (float) scoreThumbH / img.height);
            scale = scale * 0.9f;
            int imgW = (int) (scale * img.width);
            int imgH = (int) (scale * img.height);
            int imgX = (scoreThumbW - imgW) / 2;
            int imgY = (scoreThumbH - imgH) / 2;
            this.img.copy(img, 0, 0, img.width, img.height, imgX, imgY, imgW,
                    imgH);
            // this.img = img;
            this.score = score;
        }

        public int compareTo(Object o) {
            HighScore hs = (HighScore) o;
            return hs.score - score; // needs to sort descending: so
            // backwards
        }

        /**
         * Define equality of state: shallow equivalency based on score
         */
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof HighScore)) {
                return false;
            }
            HighScore that = (HighScore) o;
            return (this.score == that.score);
        }

    }

    /**
     * class for pairs of index and value (so it can be sorted by valued)
     */
    class Pair implements Comparable {
        int index;

        int value;

        Pair(int index, int value) {
            this.index = index;
            this.value = value;
        }

        public int compareTo(Object obj) {
            Pair pair = (Pair) obj;
            return value - pair.value;
        }
    }

    /**
     * utility function for log base 2
     * 
     * @param x
     * @return
     */
    public int log2(int x) {
        return (int) (Math.log(x) / Math.log(2));
    }

    /**
     * Given an integer array (index of possesRanks) return the index of the max
     * value don't return door!
     * 
     * @param array
     * @param length
     * @return
     */
    public int max(int[] array, int length) {
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < length; i++) {
            if (array[i] > max) {
                max = array[i];
            }
        }
        return max;
    }

    /**
     * Calculate mean of int array
     * 
     * @param values
     * @param length
     * @return
     */
    public double mean(final int[] values, final int length) {
        // calc sum
        double sum = 0.0;
        for (int i = 0; i < length; i++) {
            sum += values[i];
        }
        // return mean
        return sum / length;
    }

    /**
     * Given an integer array (index of ranks) return the index of the min value
     * 
     * @param array
     * @param length
     *            number of meaningful entries in array
     * @return min
     */
    public int min(int[] array, int length) {
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < length; i++) {
            if (array[i] < min) {
                min = array[i];
            }
        }
        return min;
    }

    /**
     * Utility for removing -1's from array
     * 
     * @param array
     * @return
     */
    public int[] removeMinusOnes(int[] array) {
        int[] tmp = new int[array.length];
        int j = 0;
        for (int i = 0; i < array.length; i++) {
            if (array[i] != -1) {
                tmp[j] = array[i];
                j++;
            }
        }
        int[] out = new int[j];
        System.arraycopy(tmp, 0, out, 0, j);
        return out;
    }

    /**
     * utility to remove the self's affinity from affinity array
     * 
     * @param array
     * @param self
     * @return
     */
    public int[] removeSelf(int[] array, int self) {
        int[] out = new int[array.length - 1];
        int j = 0;
        for (int i = 0; i < array.length; i++) {
            if (i != self) {
                out[j] = array[i];
                j++;
            }
        }
        return out;
    }

    /**
     * Calculate standard deviation of int array
     * 
     * @param values
     * @param length
     * @param mean
     * @return
     */
    public double standardDeviation(final int[] values, final int length,
            final double mean) {

        // calc variance
        double var = Double.NaN;

        if (length == 1) {
            var = 0.0;
        } else if (length > 1) {
            double accum = 0.0;
            double accum2 = 0.0;
            for (int i = 0; i < length; i++) {
                accum += Math.pow((values[i] - mean), 2.0);
                accum2 += (values[i] - mean);
            }
            var = (accum - (Math.pow(accum2, 2) / (length))) / length;

        }
        // return standard deviation
        return Math.sqrt(var);
    }

    /**
     * Method to test if game is in top scores.
     */
    public void topScore(HighScore current) {
        if (highScores.size() < maxScores) {
            highScores.add(current);
        } else {
            int index = highScores.size() - 1;
            while ((index >= 0)
                    && (current.score > ((HighScore) highScores.get(index)).score)) {
                index--;
            }
            if (index < highScores.size() - 1) {
                highScores.add(index + 1, current);
            }
            if (highScores.size() > maxScores) {
                highScores.remove(highScores.size() - 1);
            }
        }
        Collections.sort(highScores);
    }

}

