module critter_control  (
  input clock,
  input reset,

  input enable,
  output val,

  output [8:0]  comp_addr,
  input  [64:0] comp_gene,

  output [9:0]  env_X,
  output [9:0]  env_Y,
  input  [17:0] env_param,

  input  [64:0] gene_in,
  output [64:0] gene_out,
  output child
);
  parameter water =   4'd3, veg    =   4'd7, temp = 4'd11, terrain = 4'd15, 
            alive =  65'd0, spec   =  65'd1, actd = 65'd2,
            pos_x = 65'd11, pos_y  = 65'd20, 
            age   = 65'd30, hunger = 65'd40, 
            gene1 = 65'd50, // camo-eyesight/metabolism
            gene2 = 65'd60, // max age/disease resistance
            cldwn = 65'd64, // cooldown for reproduction

            rst    = 4'd0,  // reset
            surv_1 = 4'd1,  // age
            surv_2 = 4'd2,  // food
            surv_3 = 4'd3,  // disease
            colide = 4'd4,  // collision
            mate_1 = 4'd5,  // crossover gene1
            mate_2 = 4'd6,  // crossover gene2
            pryprd = 4'd7,  // prey/predator
            muta_1 = 4'd8,  // gene1 mutation
            muta_2 = 4'd9,  // gene2 mutation
            move_1 = 4'd10, // x movement
            move_2 = 4'd11, // y movement

            p_xovr = 16'h2000,  // 12.5%
            p_mutH = 16'h0080,  // 3.13%
            p_mutL = 16'h0020;  // 0.78%

  reg [8:0] addr, env_addr_X, env_addr_Y;
  reg valid, offspring;
  reg [64:0] out;

  assign gene_out = out;
  assign val = valid;
  assign child = offspring;
  assign comp_addr = addr;
  assign env_X = env_addr_X;
  assign env_Y = env_addr_Y;

  wire in_alive, in_spec, in_actd, comp_alive, comp_spec, comp_actd;
  wire [8:0] in_x, in_y, comp_x, comp_y;
  wire [9:0] in_age,  in_hunger,  in_gene1,  in_gene2, 
                     comp_age, comp_gene1, comp_gene2;
  wire [3:0] in_cldwn, comp_cldwn;

  assign in_alive = gene_in[alive];
  assign in_spec = gene_in[spec];
  assign in_actd = gene_in[actd];
  assign in_x = gene_in[pos_x:actd+1];
  assign in_y = gene_in[pos_y:pos_x+1];
  assign in_age = gene_in[age:pos_y+1];
  assign in_hunger = gene_in[hunger:age+1];
  assign in_gene1 = gene_in[gene1:hunger+1];
  assign in_gene2 = gene_in[gene2:gene1+1];
  assign in_cldwn = gene_in[cldwn:gene2+1];

  assign comp_alive = comp_gene[alive];
  assign comp_spec = comp_gene[spec];
  assign comp_actd = comp_gene[actd];
  assign comp_x = comp_gene[pos_x:actd+1];
  assign comp_y = comp_gene[pos_y:pos_x+1];
  assign comp_age = comp_gene[age:pos_y+1];
  assign comp_gene1 = comp_gene[gene1:hunger+1];
  assign comp_gene2 = comp_gene[gene2:gene1+1];
  assign comp_cldwn = comp_gene[cldwn:gene2+1];

  reg [15:0] threshold[1:0];
  wire rng_out[1:0];
  wire rng_half;
  RNG_decision rng_module1 (
    .clk(clock),
    .reset(reset),
    .init(32'h2af45f44),
    .threshold(threshold[0]),
    .out(rng_out[0])
  );

  RNG_decision rng_module2 (
    .clk(clock),
    .reset(reset),
    .init(32'h2af45f44),
    .threshold(threshold[1]),
    .out(rng_out[1])
  );

  RNG_decision rng_half_module (
    .clk(clock),
    .reset(reset),
    .init(32'h2af45f44),
    .threshold(16'h8000),
    .out(rng_half)
  );

  wire signed [4:0] dst = (in_spec == comp_spec) ? 5'd2 : 5'd10;
  wire in_range;
  InRange range_module (
    .x_1(in_x),
    .x_2(comp_x),
    .y_1(in_y),
    .y_2(comp_y),
    .range(dst),
    .yn(in_range)
  );

  reg [3:0] state;
  always @ (negedge clock) begin
    if (reset) begin
      state <= rst;
      valid <= 1'b0;
    end
    else if (enable) begin
      if (state == rst) begin
        valid <= 1'b0;
        // if alive, continue
        if (in_alive) begin
          addr <= 9'd0;
          env_addr_X <= in_x;
          env_addr_Y <= in_y;
          out <= gene_in;
          out[actd] <= 1'b0;
          out[cldwn:gene2+1] <= (in_cldwn == 0) ? 4'd0 : in_cldwn - 4'd1;
          offspring <= 1'b0;
          state <= surv_1;
        end  
        // if dead, ignore
        else begin
          valid <= 1'b1;
          out <= gene_in;
        end
      end

      // age check
      else if (state == surv_1) begin
        out[age:pos_y+1] <= in_age + 10'd1;
        if (out[age:pos_y+1] > in_gene2) begin
          out[alive] <= 1'b0;
          valid <= 1'b1;
          state <= rst;
        end
        else begin
          state <= surv_2;
        end
      end

      // food check
      else if (state == surv_2) begin
        // predator starved to death
        if (in_spec == 1 && in_hunger < 
              ({7'd0, gene_in[hunger+1], gene_in[hunger+3], 
              gene_in[hunger+5]} + 1)) begin
          out[alive] <= 1'b0;
          valid <= 1'b1;
          state <= rst;
        end 
        // prey starved to death
        else if (in_spec == 0 && in_hunger < 
              ({6'd0, gene_in[hunger+1], gene_in[hunger+3],
              gene_in[hunger+5], gene_in[hunger+7]} + 1)) begin
          out[alive] <= 1'b0;
          valid <= 1'b1;
          state <= rst;
        end
        else begin
          state <= surv_3;
          if (in_spec == 1) begin
            out[hunger:age+1] <= in_hunger - 10'd1
                - {7'd0, gene_in[hunger+1], gene_in[hunger+3],
                          gene_in[hunger+5]};
          end
          else begin
            // rng for food spawn
            threshold[0] <= {2'd0, env_param[water:0], 4'd0} 
                          + {2'd0, env_param[veg:water+1], 4'd0} 
                          + {4'd0, env_param[temp:veg+1], 2'd0} 
                          - {4'd0, env_param[terrain:temp+1], 2'd0};
            if (rng_out[0])
              out[hunger:age+1] <= in_hunger
                  + 10'h020 - {6'd0, gene_in[hunger+1], gene_in[hunger+3],
                                      gene_in[hunger+5], gene_in[hunger+7]};
            else
              out[hunger:age+1] <= in_hunger - 10'd1
                  - {6'd0, gene_in[hunger+1], gene_in[hunger+3],
                            gene_in[hunger+5], gene_in[hunger+7]};
          end
        end
      end

      // disease check
      else if (state == surv_3) begin
        // rng for disease
        threshold[0] <= {6'd0, env_param[water:0]} 
                      + {6'd0, env_param[veg:water+1]} 
                      + {6'd0, env_param[temp:veg+1]} 
                      + {4'd0, in_gene2, 2'd0};
        // death by sickness
        if (rng_out[0]) begin
          out[alive] <= 1'b0;
          valid <= 1'b1;
          state <= rst;
        end
        else begin
          state <= colide;
        end
      end

      // collision check
      else if (state == colide) begin
        if (comp_alive && !comp_actd && in_range) begin
          if (in_spec == comp_spec)
            state <= mate_1;
          else
            state <= pryprd;
        end 
        else begin
          if (addr == 9'd10)
            state <= muta_1;
          else
            addr <= addr + 9'd1;
        end
      end

      // mating check
      else if (state == mate_1) begin
        if (in_actd || in_cldwn != 0 || comp_cldwn != 0 ||
            in_age < (in_gene2>>3) || comp_age < (comp_gene2>>3)) begin
          if (addr == 9'd10)
            state <= muta_1;
          else begin
            addr <= addr + 9'd1;
            state <= colide;
          end
        end
        else begin
          if(rng_half)
            out[gene1:hunger+1] <= comp_gene1;

          state <= mate_2;
        end
      end
      else if (state == mate_2) begin
        if(rng_half)
          out[gene2:gene1+1] <= comp_gene2;

        out[actd]  <= 1'b1;
        out[cldwn:gene2+1] <= 4'd15;
        offspring <= 1'b1;
        state <= muta_1;
      end

      // prey-predator check
      else if (state == pryprd) begin
        // comp is predator and current is prey
        if (in_spec == 0 && in_gene1 < comp_gene1) begin
          out[alive] <= 1'b0;
          valid <= 1'b1;
          state <= rst;
        end
        // current is predator and comp is prey
        else if (in_spec == 1 && in_gene1 > comp_gene1) begin
          out[hunger:age+1] <= in_hunger + 10'd040;
          state <= muta_1;
        end
        else begin
          if (addr == 9'd10)
            state <= muta_1;
          else begin
            addr <= addr + 9'd1;
            state <= colide;
          end
        end
      end

      // mutation
      else if (state == muta_1) begin
        threshold[0] <= p_mutL;
        threshold[1] <= p_mutH;
        if (rng_half) begin
          if (rng_out[0] && rng_out[1])
            out[gene1:hunger+1] <= out[gene1:hunger+1] + 10'd3;    
          else if (rng_out[1])
            out[gene1:hunger+1] <= out[gene1:hunger+1] + 10'd2;
          else if (rng_out[0])
            out[gene1:hunger+1] <= out[gene1:hunger+1] + 10'd1;
        end
        else begin
          if (rng_out[0] && rng_out[1])
            out[gene1:hunger+1] <= out[gene1:hunger+1] - 10'd3;    
          else if (rng_out[1])
            out[gene1:hunger+1] <= out[gene1:hunger+1] - 10'd2;
          else if (rng_out[0])
            out[gene1:hunger+1] <= out[gene1:hunger+1] - 10'd1;
        end
        state <= muta_2;
      end
      else if (state == muta_2) begin
        if (rng_half) begin
          if (rng_out[0] && rng_out[1])
            out[gene2:gene1+1] <= out[gene2:gene1+1] + 10'd3;  
          else if (rng_out[1])
            out[gene2:gene1+1] <= out[gene2:gene1+1] + 10'd2;
          else if (rng_out[0])
            out[gene2:gene1+1] <= out[gene2:gene1+1] + 10'd1;
        end
        else begin
          if (rng_out[0] && rng_out[1])
            out[gene2:gene1+1] <= out[gene2:gene1+1] - 10'd3;    
          else if (rng_out[1])
            out[gene2:gene1+1] <= out[gene2:gene1+1] - 10'd2;  
          else if (rng_out[0])
            out[gene2:gene1+1] <= out[gene2:gene1+1] - 10'd1;  
        end
        state <= move_1;
      end

      // movement
      else if (state == move_1) begin
        threshold[0] <= 16'h4000 + {1'b0,in_hunger,5'b0};
        if (rng_out[0]) begin
          if (rng_half)
            out[pos_x:actd+1] <= in_x + 9'd8 - {6'd0, env_param[terrain-1:temp+1]};
          else
            out[pos_x:actd+1] <= in_x - 9'd8 + {6'd0, env_param[terrain-1:temp+1]};
        end
        state <= move_2;
      end
      else if (state == move_2) begin
        if (rng_out[0]) begin
          if (rng_half) 
            out[pos_y:pos_x+1] <= in_y + 9'd8 - {6'd0, env_param[terrain-1:temp+1]};
          else
            out[pos_y:pos_x+1] <= in_y - 9'd8 + {6'd0, env_param[terrain-1:temp+1]};
        end
        valid <= 1'b1;
        state <= rst;
      end
    end
  end
endmodule

module RNG_decision (
  input clk,
  input reset,
  input [31:0] init,
  input [15:0] threshold,
  output out
);
  reg [31:0] gen_rand;  // current random number
  //make decision based on comparison to threshold
  assign out = (gen_rand[31:16] <= threshold) ? 1'b1 : 1'b0;

  //generate the random number
  always @ (posedge clk) begin
    if (reset)
      gen_rand <= init;
    else
      gen_rand <= {gen_rand[30:0], gen_rand[27] ^ gen_rand[31]};
  end
endmodule

module InRange (
  input [8:0] x_1,
  input [8:0] x_2,
  input [8:0] y_1,
  input [8:0] y_2,
  input signed [4:0] range,
  output yn
);
  reg result;
  assign yn = result;
  wire signed [8:0] x = x_1 - x_2;
  wire signed [8:0] y = y_1 - y_2;
  always @ (*) begin
    if ((x<range && x>-range) && (y<range && y>-range))
      result <= 1'b1;
    else
      result <= 1'b0;
  end
endmodule