/* File: AntApplet.java
 * Author: Christine Meyer
 * Date  : March 4, 2003
 * Class : CS240
*/

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.event.*;
import java.applet.Applet;
import java.util.*;

class TMCell {
    private Color cellColor = Color.black;

    TMCell() {
    }

    TMCell(Color c) {
        cellColor = c;
    }

    public Color getColor() {
        return cellColor;
    }

    public void setColor(Color c) {
        cellColor = c;
    }

}

class TMCellIndex  {
    private int x;
    private int y;

    public TMCellIndex(int x1,int y1) {
        x1 = x;
        y1 = y;
    }
}

class TMBuffer {
    private int size = 1;
    private TMCell[][] buffer;
    private int rows = 600;
    private int cols = 400;
    private BufferedImage offImage = null;
    private Graphics2D offGraphics = null;
    private boolean redraw = false;

    public TMBuffer() {
        // default to 600 horizontal x 400 vertical
        buffer = new TMCell[600][400];
        for (int i=0; i<600; i++) {
            for (int j=0; j<400; j++) {
                buffer[i][j] = new TMCell();
            }
        }
    }

    public TMBuffer(int row, int col) {
        rows = row;
        cols = col;
        buffer = new TMCell[row][col];
        for (int i=0;i<row;i++) {
            for (int j=0;j<col;j++) {
                buffer[i][j] = new TMCell();
            }
        }
    }

    public Color getColor(int row, int col) {
        return buffer[row][col].getColor();
    }

    public void setColor(int row, int col, Color c) {
        if ((row < 600) && (col < 400)) {
            buffer[row][col].setColor(c);
            if ( ((row*size) < 600) && ((col*size) < 400) ) {
                if (offGraphics != null) {
                    offGraphics.setColor(c);
                    offGraphics.fillRect(row*size,col*size,size,size);
                }
            }
        }
    }

    public void sketchAll(Graphics g) {
        if (offImage == null) {
          offImage = new BufferedImage(600,400, BufferedImage.TYPE_INT_RGB);
          offGraphics = offImage.createGraphics();
          redraw = true;
        }
        if (redraw) {
          redraw = false;
          offGraphics.setColor(Color.black);
          offGraphics.fillRect(0,0,600,400);
          int trows = rows/size;
          int tcols = cols/size;
          for (int i=0; i<trows;i++) {
            for (int j=0; j<tcols; j++) {
               if (buffer[i][j].getColor() != Color.black) {
                 offGraphics.setColor(buffer[i][j].getColor());
                 offGraphics.fillRect(i*size,j*size,size,size);
               }
            }
          }
        }
        g.drawImage(offImage, 0, 0, null);
    }

    public void clearBuffer() {
        for (int i=0; i<rows;i++) {
            for (int j=0; j<cols; j++) {
                buffer[i][j].setColor(Color.black);
            }
        }
        redraw = true;
    }

    public void setSize(int sz) {
        size = sz;
        redraw = true;
    }

    public int getSize() {
        return size;
    }
}


class TMStateInfo {
    int dir; // left = 0, forward = 1, right = 2, backward = 3
    int count;
    TMStateInfo(int d, int c) {
        dir = d;
        count=c;
    }
    int getDir() { return dir;}
    int getCount() { return count;}
}

class TMRule {
    int states = 0;
    int maxStates;
    TMStateInfo[] stateInfo;

    public TMRule() {
        maxStates = 16;
        stateInfo = new TMStateInfo[maxStates];
    }

    public TMRule(int ms) {
        maxStates = ms;
        stateInfo = new TMStateInfo[maxStates];
    }

    public void addState(TMStateInfo ti) {
        if (states < maxStates) {
            stateInfo[states] = ti;
            states++;
        }
    }

    // allow state rule to be changed
    public void setState(TMStateInfo ti, int index) {
        if (states > index) {
            stateInfo[index] = ti;
        }
    }

    public TMStateInfo getStateInfo(int i) {
        if (i < states) {
            return stateInfo[i];
        } else {
            return stateInfo[0];
        }
    }

    public int getState(Color c) {
        if (c == Color.black) {
            return (0);
        }
        if (c == Color.white) {
            return (1);
        }
        if (c == Color.green) {
            return (2);
        }
        if (c == Color.cyan) {
            return (3);
        }
        return(0);
    }

    public int getNextState(int s) {
        int i = s + 1;
        if (i >= states) {
            i=0;
        }
        return i;
    }

    public Color getColor(int s) {
        if (s == 0) {
            return (Color.black);
        }
        if (s == 1) {
            return (Color.white);
        }
        if (s == 2) {
            return (Color.green);
        }
        if (s == 3) {
            return (Color.cyan);
        }
        return(Color.black);
    }

    public int getNumStates() {
        return states;
    }

}

// left = 0, forward = 1, right = 2, backward = 3
class TMRule_2State extends TMRule {
    public TMRule_2State() {
        TMStateInfo s0 = new TMStateInfo(2,1);
        addState(s0);
        TMStateInfo s1 = new TMStateInfo(0,1);
        addState(s1);
    }
}

class TMRule_3State extends TMRule {
    public TMRule_3State() {
        TMStateInfo s0 = new TMStateInfo(2,1);
        addState(s0);
        TMStateInfo s1 = new TMStateInfo(1,1);
        addState(s1);
        TMStateInfo s2 = new TMStateInfo(0,1);
        addState(s2);
    }
}

class TMRule_4State extends TMRule {
    public TMRule_4State() {
        TMStateInfo s0 = new TMStateInfo(2,1);
        addState(s0);
        TMStateInfo s1 = new TMStateInfo(0,1);
        addState(s1);
        TMStateInfo s2 = new TMStateInfo(0,1);
        addState(s2);
        TMStateInfo s3 = new TMStateInfo(2,1);
        addState(s3);
    }
}

public class AntApplet extends JApplet {

    private DisplayPanel dp;
    private ControlPanel cp;
    private TMBuffer tmBuffer;
    private Ant[] ants;
    private int selectedAnt = 0;
    private int delay = 10;
    private int population = 1;
    private boolean running = false;
    private int scrollIndex=0;
    private int ruleIndex=0;
    private boolean allAnts = true;

    class Ant extends Thread {
        int x = 0;
        int y = 0;
        int dir = 0; // up=0, forward=1, down=2, backward=3
        TMRule rule;

        public Ant() {
            x = 600/(tmBuffer.getSize()*2);
            y = 400/(tmBuffer.getSize()*2);
            dir = 0;
            rule = new TMRule_2State();
        }

        public Ant(TMRule r) {
            x = 600/(tmBuffer.getSize()*2);
            y = 400/(tmBuffer.getSize()*2);
            dir = 0;
            rule = r;
        }

        public Ant(TMRule r, int xc, int yc, int d) {
            rule = r;
            x = xc;
            y = yc;
            dir = d;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }

        public void setRule(TMRule r) {
            rule = r;
        }

        public void mutate() {
            int numStates = rule.getNumStates();
            // randomly decide which state to change
            int crule = (int) (Math.random() * numStates);
            if (crule >= numStates) crule = numStates - 1;
            TMStateInfo si = rule.getStateInfo(crule);
            int cdir = (int) (Math.random() * 4);
            if (cdir > 3) cdir = 3;
            if (cdir == si.getDir()) cdir = ((cdir + 1) % 4);
            si = new TMStateInfo(cdir,1);
            rule.setState(si,crule);
        }

        public void rand() {
            int numStates = rule.getNumStates();
            for (int i=0;i<numStates;i++) {
                int cdir = (int) (Math.random() * 4);
                if (cdir > 3) cdir = 3;
                TMStateInfo si = new TMStateInfo(cdir,1);
                rule.setState(si, i);
            }
        }

        public void run() {
            try {
                for (;;) {
                    int ymax = 400/tmBuffer.getSize();
                    int xmax = 600/tmBuffer.getSize();
                    if (scrollIndex == 1) { // wrap
                        if (y < 0)  y = ymax;
                        if (y > ymax) y = 0;
                        if (x < 0) x = xmax;
                        if (x > xmax) x = 0;

                    }
                    if (scrollIndex == 2) { // bounce
                        if ((y > ymax) || (y < 0) ||
                           (x > xmax) || (x < 0)) {
                           dir = (dir + 2) % 4;
                           if (y > ymax) y = ymax;
                           if (y < 0) y = 0;
                           if (x > xmax) x = xmax;
                           if (x < 0) x = 0;
                        }
                    }
                    if ((y < 0) || (y > ymax) || (x < 0) || (x > xmax)) {
                        Thread.sleep(1000);
                    } else if (running == false) {
                        Thread.sleep(1000);
                    } else {
                        Color c = tmBuffer.getColor(x,y);
                        int state = rule.getState(c);
                        TMStateInfo ti = rule.getStateInfo(state);
                        if (running == true) {
                          tmBuffer.setColor(x,y,rule.getColor(rule.getNextState(state)));
                        }
                        if (ti.getDir() == 0) {
                            // going to left
                            dir--;
                            if (dir < 0) dir = 3;
                        } else if (ti.getDir() == 2) {
                            // going to right
                            dir++;
                            if (dir > 3) dir = 0;
                        } else if (ti.getDir() == 3) {
                            // reverse direction
                            dir = dir + 2;
                            if (dir > 3) dir = dir - 4;
                        }
                        if (dir == 0) y--;
                        if (dir == 1) x++;
                        if (dir == 2) y++;
                        if (dir == 3) x--;
                        repaint();
                        Thread.sleep(delay);
                    }
                }
            } catch (InterruptedException e) {
                return;
            }
        }
    }


    class DisplayPanel extends JPanel implements MouseListener {
        private Graphics gc;
        private boolean firstTime = true;

        public DisplayPanel() {
            addMouseListener(this);
        }

        public void setFirst() {
            firstTime = true;
        }

        public void paintComponent(Graphics g) {
            // super.paintComponent(g);
            tmBuffer.sketchAll(g);
            if (allAnts == false) {
                if (ants[selectedAnt] != null) {
                    g.setColor(Color.red);
                    int x = ants[selectedAnt].getX();
                    int y = ants[selectedAnt].getY();
                    int sz = tmBuffer.getSize();
                    g.fillRect(x*sz-(sz/2),y*sz-(sz/2),sz*2,sz*2);
                }
            }
        }

        public Dimension getPreferredSize() {
            return new Dimension(600,400);
        }

        public void mouseClicked(MouseEvent me) {
        }

        public void mousePressed(MouseEvent me) {
            int mx = me.getX()/tmBuffer.getSize();
            int my = me.getY()/tmBuffer.getSize();
            int index = 0;
            int sum = 10000;
            int tsum = 0;
            int ax;
            int ay;

            for (int i=0; i<20; i++) {
                if (ants[i] != null) {
                    tsum = Math.abs(ants[i].getX() - mx) +
                           Math.abs(ants[i].getY() - my);
                    if (tsum < sum) {
                        sum = tsum;
                        index = i;
                    }
                }
            }
            selectedAnt = index;
        }

        public void mouseReleased(MouseEvent me) {
        }

        public void mouseEntered(MouseEvent me) {
        }

        public void mouseExited(MouseEvent me) {
        }
 }


    class ControlPanel extends JPanel implements ActionListener, ChangeListener {
        private JButton     startButton;
        private JButton     stopButton;
        private JButton     clearButton;
        private JButton     focusButton;
        private JButton     mutateButton;
        private JButton     randomizeButton;
        private JButton     setRuleButton;
        private JLabel      delayLabel;
        private JSlider     delaySlider;
        private JLabel      cellSizeLabel;
        private JSlider     cellSizeSlider;
        private JLabel      populationLabel;
        private JSlider     populationSlider;
        private JComboBox   scrollComboBox;
        private JComboBox   ruleComboBox;

        class AntSlider extends JSlider {
            AntSlider(int i, int j, int k) {
                super(i,j,k);
            }
            public Dimension getPreferredSize() {
                return new Dimension(160,20);
            }
        }

        class SmallButton extends JButton {
            SmallButton(String s) {
                super(s);
            }
            public Dimension getPreferredSize() {
                return new Dimension(64,25);
            }
        }

        class LargeButton extends JButton {
            LargeButton(String s) {
                super(s);
            }
            public Dimension getPreferredSize() {
                return new Dimension(90,25);
            }
        }

        public ControlPanel() {
            JLabel label;
            GridBagLayout gridbag = new GridBagLayout();
            setLayout(gridbag);
            GridBagConstraints constraints = new GridBagConstraints();

            // create and add startButton
            startButton = new SmallButton("Start");
            startButton.addActionListener(this);
            constraints.gridx=1;
            constraints.gridy=1;
            constraints.gridwidth=1;
            constraints.gridheight=1;
            gridbag.setConstraints(startButton,constraints);
            add(startButton);

            // create and add stop Button
            stopButton = new SmallButton("Stop");
            stopButton.addActionListener(this);
            constraints.gridx=1;
            constraints.gridy=2;
            constraints.gridwidth=1;
            constraints.gridheight=1;
            gridbag.setConstraints(stopButton,constraints);
            add(stopButton);

            // create and add clear Button
            clearButton = new SmallButton("Clear");
            clearButton.addActionListener(this);
            constraints.gridx=1;
            constraints.gridy=3;
            constraints.gridwidth=1;
            constraints.gridheight=1;
            gridbag.setConstraints(clearButton,constraints);
            add(clearButton);

            // create and add All/Focus Button
            focusButton = new LargeButton("All");
            focusButton.addActionListener(this);
            constraints.gridx=0;
            constraints.gridy=0;
            constraints.gridwidth=1;
            constraints.gridheight=1;
            gridbag.setConstraints(focusButton,constraints);
            add(focusButton);

            // create and add mutate Button
            mutateButton = new LargeButton("Mutate");
            mutateButton.addActionListener(this);
            constraints.gridx=0;
            constraints.gridy=1;
            constraints.gridwidth=1;
            constraints.gridheight=1;
            gridbag.setConstraints(mutateButton,constraints);
            add(mutateButton);

            // create and add randomize Button
            randomizeButton = new LargeButton("Random");
            randomizeButton.addActionListener(this);
            constraints.gridx=0;
            constraints.gridy=2;
            constraints.gridwidth=1;
            constraints.gridheight=1;
            gridbag.setConstraints(randomizeButton,constraints);
            add(randomizeButton);

            // create and add randomize Button
            setRuleButton = new LargeButton("Set Rule");
            setRuleButton.addActionListener(this);
            constraints.gridx=0;
            constraints.gridy=3;
            constraints.gridwidth=1;
            constraints.gridheight=1;
            gridbag.setConstraints(setRuleButton,constraints);
            add(setRuleButton);

            label = new JLabel("     ");
            constraints.gridx=0;
            constraints.gridy=4;
            constraints.gridwidth=1;
            constraints.gridheight=1;
            gridbag.setConstraints(label,constraints);
            add(label);

            // create label and slider for Delay
            delayLabel = new JLabel("Delay: 10");
            constraints.gridx=0;
            constraints.gridy=5;
            constraints.gridwidth=2;
            constraints.gridheight=1;
            gridbag.setConstraints(delayLabel,constraints);
            add(delayLabel);
            delaySlider = new AntSlider(1,50,10);
            delaySlider.addChangeListener(this);
            constraints.gridx=0;
            constraints.gridy=6;
            constraints.gridwidth=2;
            constraints.gridheight=1;
            gridbag.setConstraints(delaySlider,constraints);
            add(delaySlider);

            // create label and slider for Cell Size
            cellSizeLabel = new JLabel("Cell Size: 5");
            constraints.gridx=0;
            constraints.gridy=7;
            constraints.gridwidth=2;
            constraints.gridheight=1;
            gridbag.setConstraints(cellSizeLabel,constraints);
            add(cellSizeLabel);
            cellSizeSlider = new AntSlider(1,10,5);
            cellSizeSlider.addChangeListener(this);
            constraints.gridx=0;
            constraints.gridy=8;
            constraints.gridwidth=2;
            constraints.gridheight=1;
            gridbag.setConstraints(cellSizeSlider,constraints);
            add(cellSizeSlider);

            // create label and slider for Population
            populationLabel = new JLabel("Population: 1");
            constraints.gridx=0;
            constraints.gridy=9;
            constraints.gridwidth=2;
            constraints.gridheight=1;
            gridbag.setConstraints(populationLabel,constraints);
            add(populationLabel);
            populationSlider = new AntSlider(1,20,1);
            populationSlider.addChangeListener(this);
            constraints.gridx=0;
            constraints.gridy=10;
            constraints.gridwidth=2;
            constraints.gridheight=1;
            gridbag.setConstraints(populationSlider,constraints);
            add(populationSlider);

            label = new JLabel("     ");
            constraints.gridx=0;
            constraints.gridy=11;
            constraints.gridwidth=1;
            constraints.gridheight=1;
            gridbag.setConstraints(label,constraints);
            add(label);


            // create label and combo box for scroll
            JPanel p1 = new JPanel();
            label = new JLabel("Scroll:");
            p1.add(label);
            String[] items = {"Die", "Wrap", "Bounce"};
            scrollComboBox = new JComboBox(items);
            scrollComboBox.addActionListener(this);
            p1.add(scrollComboBox);
            constraints.gridx=0;
            constraints.gridy=12;
            constraints.gridwidth=2;
            constraints.gridheight=1;
            gridbag.setConstraints(p1,constraints);
            add(p1);

            label = new JLabel("     ");
            constraints.gridx=0;
            constraints.gridy=13;
            constraints.gridwidth=1;
            constraints.gridheight=1;
            gridbag.setConstraints(label,constraints);
            add(label);

            // create label and combo box for rule
            JPanel p2 = new JPanel();
            label = new JLabel("Rule:");
            p2.add(label);
            String[] items2 = {"1 State-2 Color", "1 State-3 Color", "1 State-4 Color"};
            ruleComboBox = new JComboBox(items2);
            ruleComboBox.addActionListener(this);
            p2.add(ruleComboBox);
            constraints.gridx=0;
            constraints.gridy=14;
            constraints.gridwidth=2;
            constraints.gridheight=1;
            gridbag.setConstraints(p2,constraints);
            add(p2);
        }

        public void paintComponent(Graphics g) {
            super.paintComponent(g);
        }

        public void actionPerformed(ActionEvent evt) {
            Object source = evt.getSource();

            if (source == clearButton) {
               // clear the display
               running = false;
               for (int i=0;i<20;i++) {
                 if (ants[i] != null) {
                    try {
                      ants[i].interrupt();
                    } catch (Exception e) {}
                    ants[i] = null;
                 }
               }
               tmBuffer.clearBuffer();
               dp.repaint();
            }

            if ((source == startButton) || (source == clearButton)) {
                // start the simulation
              int target = ruleComboBox.getSelectedIndex();
              int xpos;
              int ypos;
              for (int i=0;i<population;i++) {
                if (population == 1) {
                   xpos = 600/(tmBuffer.getSize()*2);
                   ypos = 400/(tmBuffer.getSize()*2);
                } else {
                   xpos = (int) (Math.random() * (560/tmBuffer.getSize())) + 20/tmBuffer.getSize();
                   ypos = (int) (Math.random() * (360/tmBuffer.getSize())) + 20/tmBuffer.getSize();
                }
                int d = (int) (Math.random() * 4);
                if (ants[i] == null) {
                    if (target == 0) {
                        TMRule rule0 = new TMRule_2State();
                        ants[i] = new Ant(rule0, xpos, ypos, d);
                        ants[i].start();
                    } else if (target == 1) {
                        TMRule rule1 = new TMRule_3State();
                        ants[i] = new Ant(rule1, xpos, ypos, d);
                        ants[i].start();
                    } else if (target == 2) {
                        TMRule rule2 = new TMRule_4State();
                        ants[i] = new Ant(rule2, xpos, ypos, d);
                        ants[i].start();
                    }
                }
              }
              running = true;
            }

            if (source == stopButton) {
                running = false;
            }

            if (source == focusButton) {
                if (allAnts) {
                    allAnts = false;
                    focusButton.setText("Focus");
                } else {
                    allAnts = true;
                    focusButton.setText("All");
                }
            }
            if (source == mutateButton) {
                // mutate
                if (allAnts) {
                   for (int i=0;i<population;i++) {
                      if (ants[i] != null) {
                        ants[i].mutate();
                      }
                   }
                } else {
                    ants[selectedAnt].mutate();
                }
            }
            if (source == randomizeButton) {
                // randomize
                if (allAnts) {
                   for (int i=0;i<population;i++) {
                      if (ants[i] != null) {
                        ants[i].rand();
                      }
                   }
                } else {
                    ants[selectedAnt].rand();
                }
            }
            if (source == setRuleButton) {
                int target = ruleComboBox.getSelectedIndex();
                TMRule myRule;
                if (target == 0) {
                    myRule = new TMRule_2State();
                } else if (target == 1) {
                    myRule = new TMRule_3State();
                } else if (target == 2) {
                    myRule = new TMRule_4State();
                } else {
                    myRule = new TMRule_2State();
                }
                if (allAnts) {
                   for (int i=0;i<population;i++) {
                      if (ants[i] != null) {
                        ants[i].setRule(myRule);
                      }
                   }
                } else {
                    ants[selectedAnt].setRule(myRule);
                }
            }

            if (source == scrollComboBox) {
                // scroll ComboBox changed
                int target = scrollComboBox.getSelectedIndex();
                scrollIndex = target;
            }
            if (source == ruleComboBox) {
                // rule ComboBox changed
                int target = ruleComboBox.getSelectedIndex();
                ruleIndex = target;
            }
        }

        public void stateChanged(ChangeEvent event) {

            JSlider source = (JSlider) event.getSource();
            if (source == delaySlider) {
                delay = source.getValue();
                delayLabel.setText("Delay: " + source.getValue());
            }
            if (source == cellSizeSlider) {
                cellSizeLabel.setText("Cell Size: " + source.getValue());
                int sz = source.getValue();
                tmBuffer.setSize(sz);
            }
            if (source == populationSlider) {
                population = source.getValue();
                populationLabel.setText("Population: " + source.getValue());
                if (source.getValueIsAdjusting() == false) {
                   int xpos;
                   int ypos;
                   int target = ruleComboBox.getSelectedIndex();

                   for (int i=0;i<population;i++) {
                      if (ants[i] == null) {
                        if (population == 1) {
                         xpos = 600/(tmBuffer.getSize()*2);
                         ypos = 400/(tmBuffer.getSize()*2);
                        } else {
                         xpos = (int) (Math.random() * (560/tmBuffer.getSize())) + 20/tmBuffer.getSize();
                         ypos = (int) (Math.random() * (360/tmBuffer.getSize())) + 20/tmBuffer.getSize();
                        }
                        int d = (int) (Math.random() * 4);
                        if (target == 0) {
                          TMRule rule0 = new TMRule_2State();
                          ants[i] = new Ant(rule0, xpos, ypos, d);
                          ants[i].start();
                        } else if (target == 1) {
                          TMRule rule1 = new TMRule_3State();
                          ants[i] = new Ant(rule1, xpos, ypos, d);
                          ants[i].start();
                        } else if (target == 2) {
                          TMRule rule2 = new TMRule_4State();
                          ants[i] = new Ant(rule2, xpos, ypos, d);
                          ants[i].start();
                        }
                      }
                   }
                   for (int i=population; i<20; i++) {
                      if (ants[i] != null) {
                        try {
                          ants[i].interrupt();
                        } catch (Exception e) {}
                        ants[i] = null;
                      }
                   }
                }
            }

        }
    }

    public void init() {

        tmBuffer = new TMBuffer();
        tmBuffer.setSize(5);
        ants = new Ant[20];
        dp = new DisplayPanel();
        cp = new ControlPanel();

        getContentPane().add(cp,"West");
        getContentPane().add(dp,"Center");
    }

}


