(*

We consider the movement of cells towards an area of ligand activity (chemotaxis) and the cells’ 
reactions to different ligands.  

For simplicity, we will consider only horizontal movement on an integer grid in the first two 
parts of this exercise. That is, the locations of the cells can only take integer values and 
the movement will only change the x-part of the cell location. 

The module Cells defines the object Cell and functions that can be applied to it in order to 
define the behaviour of the model.  
 
What you should observe are cells (circles) moving towards two different kinds of ligands, 
called Ras and Notch (indicated as areas of different colour). The cells start in areas 
with no ligand present and move towards the closest ligand or in a random direction if 
they are in the middle between two ligand areas. Once the cells have reached the area of 
ligand activity, they react to the specific ligand. Ras causes the cells to stop their 
movement and grow and Notch results in the cells to stop moving. 

*)
/// Defines the properties of cells and functions describing their behaviour
module Cells = 
    
    /// Creates a random number generator
    let randomGenerator = System.Random()
    
    /// Represents a point (x,y) in the model with coordinates on an integer grid
    type Point = {x:int;y:int}
   
    /// Build a point with coordinates x and y
    let Point(x,y) = {x=x;y=y}
    
    
    /// Represents ligand activity
    type Ligand = 
        /// Represents the presence of Notch protein
        | Notch 
        /// Represents the presence of Ras protein
        | Ras 
        /// Represents the absence of ligand activity
        | NoLigand
    
    ///Represents the state of a cell that defines how it will behave
    type CellMarker = 
        /// Represents a cell that is able to move
        | Move 
        /// Represents a cell that is able to grow
        | Grow 
        /// Represents a cell that stays in the place that it is in and does not do anything
        | Stopped 
        /// Represents a cell that has died
        | Dead 
        /// Represents a cell that is set to divide
        | Divide
    
    
    /// Defines the states and state modifications of a cell
    type Cell(x,y) =
        // Define the initial states of the cell. The keyword 'mutable' is used to indicate
        // that the state can change over time.
        let mutable location  = Point(x,y)
        let mutable size      = 0.5
        let mutable marker   = Move
        let mutable direction = 0
        
        // Expose the states of the cell to make them accessible from outside of the object
        member this.Location   with get() = location  and set(x) = location <- x
        member this.Marker     with get() = marker    and set(x) = marker <- x 
        member this.Direction  with get() = direction and set(x) = direction <- x
        member this.Size       with get() = size      and set(x) = size <- x
    
        // Modify states of the cell using functions that can be called outside of the object
        member this.Move()      = location <- Point(location.x + direction,location.y)
        member this.Grow(x)     = size <- size + x
       
        member this.RenderData() =         
            let color = 
                match marker with
                | Move -> "blue" 
                | Grow -> "magenta"
                | Dead -> "black"
                | Divide -> "red"
                | _ -> "green"     
            (location.x, location.y, size, color)  
    
    /// Computes the ligand activity in location p.
    let ligand (p:Point) =
        let x = p.x
        if x < 10 then Ras 
        elif x > 20 && x < 30 then Notch 
        elif x > 40 && x < 50 then Ras 
        elif x > 60 && x < 70 then Notch 
        elif x > 80 && x < 90 then Ras 
        else NoLigand

        
            
      
    /// This function is called by the simulation engine. It sets the marker of a cell according
    /// to the ligand activity at its location and in part B depending its neighbour and the ligand 
    /// activity at its next location.
    let setMarker (cells:Cell[]) (cell:Cell) =
        // These three definitions are needed in part B
        let nextX = cell.Location.x + cell.Direction
        let nextLoc = Point(nextX,cell.Location.y)
        let neighbour = Array.filter (fun (c:Cell) -> c.Location = nextLoc) cells 
        if ligand cell.Location = Ras then
            cell.Marker <- Grow
        elif ligand cell.Location = Notch then
            cell.Marker <- Stopped
    
    /// This function is called by the simulation engine. It performs the actions 
    /// described by the markers decided by the 'setMarkers' function in an earlier phase of
    /// the simulation on a cell.
    let react (step:float) (cell:Cell) =
        if cell.Marker = Move then
            cell.Move()
        elif cell.Marker = Grow  then
                cell.Grow(step)
    
    
    /// This function is called by the simulation engine. It defines the direction of a cell 
    /// according to its location relative to the ligands.
    let cellDirection (cell:Cell) =
        let x = cell.Location.x
        let y = cell.Location.y
        let right = Point(x+5,y)
        let left = Point(x-5,y)
        if ligand cell.Location = NoLigand && ligand right = NoLigand && ligand left = NoLigand then
        // If the cell is located with equal distance to the ligand on the left and the ligand on the right, the direction is chosen at random.
            let random = randomGenerator.Next(0,3)
            cell.Direction <- random - 1
        else if cell.Direction = 0 then
        // Else if the cell's direction hasn't been set yet, it will move towards the closest ligand.
            if ligand right = Notch || ligand right = Ras then
                cell.Direction <- 1
            else if ligand left = Notch || ligand left = Ras then
                cell.Direction <- -1
    
    /// This function is called by the simulation engine. It lets a cell divide if the location
    /// to its left or its right is free, it places the new daughter cell in the free space
    /// and it resets the division timer of a cell that has just divided. If the neighbouring 
    /// locations aren't free, the function does nothing so that the cell can try to divide 
    /// again at the next simulation step. The function returns the new cell.
    let cellDivision (cells:Cell[]) (cell:Cell) = [| |]
    
    /// This function is called by the simulation engine. It lets a cell die if would
    /// move from an area without ligand to an area with Ras activity, but the location 
    /// is occupied.
    let cellDeath (cell:Cell) = () 

// Now create an HTML5 Canvas element to visualize the scene in the HTML graphics pane
let width, height = 1000, 1000
TryFSharp.Canvas.Show()
TryFSharp.Canvas.RunJavaScript (sprintf @"
(function (self) {
    var canv = document.getElementById('canvas1');
    if (canv == undefined) {
        canv = document.createElement('canvas');
        if (canv.getContext) {
            canv.id = 'canvas1';
            canv.width = %A;
            canv.height = %A;
            document.body.appendChild(canv);
        } else {
            alert('Your browser does not seem to support HTML5 Canvas.');
        };
    };
    var ctx = canv.getContext('2d');
    ctx.scale(2.0,2.0); 
           
    self.render_wall = function() {
            var start,stop,range,color;
            ctx.clearRect(0,0,%A,%A); 
            start=0; stop=10; range=stop-start; color='pink';
            ctx.fillStyle = color; ctx.fillRect(start,0,range,50);
            start=20; stop=30; range=stop-start; color='brown';
            ctx.fillStyle = color; ctx.fillRect(start,0,range,50);
            start=40; stop=50; range=stop-start; color='pink';
            ctx.fillStyle = color; ctx.fillRect(start,0,range,50);
            start=60; stop=70; range=stop-start; color='brown';
            ctx.fillStyle = color; ctx.fillRect(start,0,range,50);
            start=80; stop=90; range=stop-start; color='pink';  
            ctx.fillStyle = color; ctx.fillRect(start,0,range,50);
            ctx.fill();             
        };
        
    self.render_cell = function(pos_x,pos_y,size,color) {
        ctx.beginPath();
        ctx.arc(pos_x,pos_y,size,0,2*Math.PI);
        ctx.fillStyle=color;
        ctx.fill();
        ctx.strokeStyle=color;
        ctx.stroke(); 
    };
    return self;
}(window.CellSim = window.CellSim || {}));
" width height width height)

// Export JS to F#
let r_cell = TryFSharp.Canvas.JavaScriptFunction("CellSim.render_cell")
let r_wall = TryFSharp.Canvas.JavaScriptFunction("CellSim.render_wall")


// Simulation step function. 
let timeDelta = 0.5

let step (cells : Cells.Cell[]) =     
    for cell in cells do
        Cells.cellDirection cell
    for cell in cells do
        Cells.setMarker cells cell
    for cell in cells do
        Cells.react timeDelta cell
    cells         
        
let step_and_draw cells =          
        // Run a simulation step.
        let cells' = step cells
        // Render just the wall.
        r_wall.Invoke() |> ignore
        // Render each cell on the wall.
        Array.iter
            (fun (c:Cells.Cell) -> 
                    let (x,y,size,clr) = c.RenderData()                
                    r_cell.Invoke(x,y,size,clr) |> ignore)
            cells' 
        cells' 

let rec run_steps num_of_steps step state =
    if num_of_steps = 0 then state
    else
        let state' = step state
        run_steps (num_of_steps - 1) step state'

// Initial simulation state, 
let mk_initial_cells _ = 
    let start_cells (x:int) = [| for y in 0 .. 9 -> Cells.Cell(x,2+4*y) |]
    [| yield! start_cells 15
       yield! start_cells 35
       yield! start_cells 55
       yield! start_cells 75 |]
       

/// Run the simulation 
// Make the cells 
let c0 = mk_initial_cells ()
// Render the activity wall. 
r_wall.Invoke() |> ignore
// Run 7 steps         
run_steps 7 step_and_draw c0