pub trait Drawable {
    fn draw(&self);
}
use macroquad::prelude::*;
use std::collections::HashMap;

use crate::display::Drawable;
use crate::inputs::Clickable;
use crate::maqs::*;

use crate::tiles::*;

//Maquinita Floor (the map)
// len + wid are length and width
// states is an array of what is in each area
#[derive(Debug, Clone)]
pub struct MaqFloor {
    pub len: usize,
    pub wid: usize,
    pub states: Box<[usize]>,
    pub maqs: HashMap<(usize, usize), Maq>,
}

impl Drawable for MaqFloor {
    fn draw(&self){
        //loop variables
        let tile_wid = (screen_width()*0.95) / self.wid as f32;
        let tile_len = screen_height() / self.len as f32;
        //loop params
        let mut x = 0;
        let mut y = 0;
        let states = &self.states;
        //iterate over floor
        while x < self.wid {
            while y < self.len {
                //get machine filling [x][y]
                let tile_id = states[(x * self.wid)+y];
                let tile = if tile_id < tile_count() {
                    get_tile(tile_id)
                } else {
                    get_maq_tile(tile_id-tile_count())
                };
                //draw its color at [x][y]
                draw_rectangle(tile_wid*(x as f32), tile_len*(y as f32), tile_wid, tile_len, tile.color);

                y+=1;
            }
            y=0;
            x+=1;
        }
    }
}

impl Clickable for MaqFloor {
    fn click(&mut self, mouse: MouseButton, pos: (f32, f32), dat: isize){
        let (x, y) = (
            pos.0/(screen_width()*0.95)*self.wid as f32,
            pos.1/screen_height()*self.len as f32
            );
        if mouse == MouseButton::Left {
            if dat >= 0{
                self.place(dat as usize, (x as usize,y as usize));
            }
        }
    }
}

impl MaqFloor{
    //Place function
    // id: Tile/Maq ID to place
    // pos: where
    //This function has the functions for placing an object, and additionally,
    //it adds Maqs to the entity list.
    pub fn place(&mut self, id: usize, pos: (usize, usize)){
        let orig = self.states[pos.0*self.wid+pos.1];
        if orig != id{
            self.states[pos.0*self.wid+pos.1] = id;
            if id >= tile_count()+10 {
                return
            } else if id as usize >= tile_count() {
                self.maqs.insert(pos, Maq{
                    counter: 0,
                    enact: match id as usize-tile_count() {
                        0|1 => 15,
                        2|3|4|5 => 10,
                        6|7|8|9 => 10,
                        _ => 30,
                    },
                    id: id,
                });
            }
        }
    }

    //Shift function
    // pos_in: starting position
    // right: true go right, false go down
    // dist: distance to go. negatives for backwards
    pub fn shift(&mut self, pos_in: (usize, usize), right: bool, dist: i16) -> bool {
        let mut i = 0;
        let mut pos_out = pos_in.clone();
        while i != dist {
            let refer = if right {&mut pos_out.0} else {&mut pos_out.1};
            *refer = if dist > 0 {*refer+1 as usize} else {
                if *refer<=0 {
                    return false
                } else {
                    *refer-1 as usize
                }
            };
            if right{
                if *refer >= self.wid {
                    return false
                }
            } else {
                if *refer >= self.len {
                    return false
                }
            }
            i += if dist > 0 {1} else {-1};
            let temp = self.states[pos_out.0*self.wid+pos_out.1];
            let this = self.states[pos_in.0*self.wid+pos_in.1];
            let tile = if temp<tile_count() {get_tile(temp)} else {get_maq_tile(temp-tile_count())};
            if !tile.passable || temp==this {
                return false
            } else {
                if tile.movable && temp!=0 {
                    self.shift(pos_out,right,dist);
                }
                self.swap(pos_in,pos_out);
            }
        }
        return true;
    }

    //Swap function
    // Swaps the tiles at two positions, swapping their Maqs as well.
    pub fn swap(&mut self, pos_1: (usize, usize), pos_2: (usize, usize)) {
        if pos_1.0>=self.wid||pos_2.0>=self.wid||pos_1.1>=self.len||pos_2.1>=self.len {
            return
        }
        let temp1 = self.states[pos_1.0*self.wid+pos_1.1];
        let temp2 = self.states[pos_2.0*self.wid+pos_2.1];

        if !(get_tile(temp1).movable&&get_tile(temp1).movable) {
            return
        }

        self.states[pos_1.0*self.wid+pos_1.1] = temp2;
        self.states[pos_2.0*self.wid+pos_2.1] = temp1;

        let maq_temp1 = self.maqs.get(&pos_1).cloned();
        let maq_temp2 = self.maqs.get(&pos_2).cloned();

        match maq_temp1 {
            Some(maq1) => {
                self.maqs.insert(pos_2,maq1);
            },
            None => {
                self.maqs.remove(&pos_2);
            }
        }
        match maq_temp2 {
            Some(maq2) => {
                self.maqs.insert(pos_1,maq2);
            },
            None => {
                self.maqs.remove(&pos_1);
            }
        }
    }
}

pub fn init_floor() -> MaqFloor {
    //create the floor size
    const L: usize = 40;
    const W: usize = 40;

    //create state array
    let states = [0;L*W];

    //create floor, encasing the array in a Box
    let floor = MaqFloor {
        len: L,
        wid: W,
        states: Box::new(states),
        maqs: HashMap::new(),
    };

    //return the floor
    floor
}
use macroquad::prelude::*;

use crate::floor::MaqFloor;
use crate::toolbar::Toolbar;

//Defines something clickable
pub trait Clickable {
    //Function to run when clicking something.
    // mouse: MouseButton enum
    // pos: Tuple with the x and y
    // dat: any data that needs to get passed to the clicked entity
    fn click(&mut self, mouse: MouseButton, pos: (f32, f32), dat: isize);
}

//Main parse loop, another loop will run if a non-main menu is active
pub fn parse_main(toolbar: &mut Toolbar, floor: &mut MaqFloor) {
    //Left click block
    let temp = if !is_key_down(KeyCode::LeftShift)
        {is_mouse_button_pressed(MouseButton::Left)}
        else {is_mouse_button_down(MouseButton::Left)};
    if temp {
       let (x, y) = mouse_position();
       //If x is to the right of the toolbar
       if x > (screen_width()*0.95) {
           toolbar.click(MouseButton::Left, (x, y), 0);
       } else {
           floor.click(MouseButton::Left, (x, y), toolbar.selected);
       }
    }
}
use macroquad::prelude::*;

pub mod floor;
pub mod toolbar;
pub mod display;
pub mod tiles;
pub mod inputs;
pub mod pheno;
pub mod maqs;

use crate::display::Drawable;

#[macroquad::main("Maquinitas")]
async fn main() {

    //game vars
    let mut game_floor = floor::init_floor();
    let mut toolbar = toolbar::init_toolbar();

    //MAIN GAME LOOP
    loop {
        //clear screen to red
        clear_background(GRAY);

        //draw things
        game_floor.draw();
        toolbar.draw();

        //query inputs
        inputs::parse_main(&mut toolbar, &mut game_floor);

        //query phenomena
        pheno::advance(&mut game_floor);
        pheno::pass_time(&mut game_floor);

        //wait for the next frame to arrive before looping again
        next_frame().await;
    }
}
use macroquad::prelude::*;
use crate::floor::MaqFloor;
use crate::tiles::*;

pub trait Worker {
    fn work(&mut self, floor: &mut MaqFloor, pos:(usize, usize));
}

#[derive(Debug,Clone,Copy)]
pub struct Maq {
    pub counter: u8,
    pub enact: u8,
    pub id: usize,
}
pub struct Mover {
    pub maq: Maq,
    pub right: bool,
}
pub struct Walker {
    pub maq: Maq,
    pub dir: u8,
}
pub struct Rotator {
    pub maq: Maq,
    pub dir: u8,
}

impl Worker for Mover {
    fn work(&mut self, floor: &mut MaqFloor, pos: (usize, usize)) {
        if self.maq.counter<self.maq.enact {
            self.maq.counter+=1;
            floor.maqs.insert(pos,self.maq);
        } else {
            self.maq.counter=0;
            //get one position
            let target1 = if self.right {
                if pos.0==floor.wid {
                    return
                }
                (pos.0+1,pos.1)
            } else {
                if pos.1==floor.len {
                    return
                }
                (pos.0,pos.1+1)
            };
            //get the other position
            let target2 = if self.right {
                if pos.0==0 {
                    return
                }
                (pos.0-1,pos.1)
            } else {
                if pos.1==0 {
                    return
                }
                (pos.0,pos.1-1)
            };
            //swap them
            floor.swap(target1,target2);
            floor.maqs.insert(pos,self.maq);
        }
    }
}

impl Worker for Walker {
    fn work(&mut self, floor: &mut MaqFloor, pos: (usize, usize)) {
        if self.maq.counter<self.maq.enact {
            self.maq.counter+=1;
            floor.maqs.insert(pos,self.maq);
        } else {
            self.maq.counter = 0;
            floor.maqs.insert(pos,self.maq);
            //move in direction
            floor.shift(pos, self.dir==0 || self.dir == 2,
                if self.dir==0 || self.dir==1 {
                    1
                } else {
                    -1
                });
        }
    }
}

impl Worker for Rotator {
    fn work(&mut self, floor: &mut MaqFloor, pos: (usize, usize)) {
        if self.maq.counter<self.maq.enact {
            self.maq.counter+=1;
            floor.maqs.insert(pos,self.maq);
        } else {
            self.maq.counter = 0;
            floor.maqs.insert(pos,self.maq);
            //find position to rotate and set it depending on direction
            let mut pos_new = (pos.0 as i8, pos.1 as i8);
            if self.dir==0||self.dir==2 {pos_new.0 += if self.dir==0 {1} else {-1};}
            if self.dir==1||self.dir==3 {pos_new.1 += if self.dir==1 {1} else {-1};}
            //if position doesn't exist, stop doing this code or it'll crash
            if pos_new.0 < 0 || pos_new.1 < 0 || pos_new.0 >= floor.wid as i8 || pos_new.1 >= floor.len as i8 {
                return
            }
            //get the Maq at the position to rotate, if it exists
            let maq_get = floor.maqs.get(&(pos_new.0 as usize,pos_new.1 as usize));
            match maq_get {
                //if there is one, increment the id of the maq to be rotated
                Some(x) => {
                    let new_id = match x.id-tile_count(){
                        2|3|4 => x.id+1,
                        5 => 2,
                        6|7|8 => x.id+1,
                        9 => 6,
                        //if non-rotatable, return
                        _ => return,
                    };
                    //get the counter and enact of the existing maq, so that the data is preserved
                    let counter_new = x.counter;
                    let enact_new = x.enact;
                    //place everthing as it should be
                    floor.place(new_id, (pos_new.0 as usize,pos_new.1 as usize));
                    floor.maqs.insert((pos_new.0 as usize, pos_new.1 as usize),Maq{
                        counter: counter_new,
                        enact: enact_new,
                        id: new_id
                    });
                }
                //if no maq, then nothing to rotate
                None => return,
            }
        }
    }
}

//Returns a Tile for a given Maq.
// id is the MaqID (subtract tilecount!)
pub fn get_maq_tile(id: usize) -> Tile {
    let tile = Tile {
        name: match id {
            0 => "Mover (H)",
            1 => "Mover (V)",
            2 => "Walker (R)",
            3 => "Walker (D)",
            4 => "Walker (L)",
            5 => "Walker (U)",
            6 => "Rotator (R)",
            7 => "Rotator (D)",
            8 => "Rotator (L)",
            9 => "Rotator (U)",
            _ => "Block",
        },
        color: match id {
            0 => ORANGE,
            1 => PURPLE,
            2|4 => RED,
            3|5 => YELLOW,
            6|8 => WHITE,
            7|9 => BROWN,
            _ => GRAY,
        },
        passable: false,
        gravity: 0,
        movable: true
    };
    tile
}

const MC: usize = 10;
pub const fn maq_count() -> usize {MC}
use macroquad::prelude::*;

use crate::floor::MaqFloor;
use crate::tiles::*;
use crate::maqs::*;

pub fn advance(floor: &mut MaqFloor) {
    let maqs = floor.maqs.clone();
    for k in maqs.keys() {
        enact(floor,*k);
    }
}

pub fn pass_time(floor: &mut MaqFloor) {
    let states = floor.states.clone();
    let mut x = 0;
    while x < (floor.wid*floor.len) {
        let id = states[x];
        let mut tile = get_tile(0);
        if id < tile_count()  {
            tile = get_tile(id);
        } else if id < maq_count() {
            tile = get_maq_tile(id);
        }
        let pos:(usize,usize) = (x / floor.wid, x % floor.len);
        let mut cont = false;
        if tile.gravity >= 1 && !cont {
            cont = floor.shift(pos, false, 1);
        }
        if tile.gravity >= 2 && !cont {
            let mut dir = 1;
            if rand::gen_range(0,2)==0 {
                dir = -1;
            }
            cont = floor.shift(pos, true, dir);
        }
        x+=1;
    }
}

pub fn enact(floor: &mut MaqFloor, pos: (usize, usize)) {
    let id = floor.states[(pos.0 * floor.wid)+pos.1];
    const TC: usize = tile_count();
    //If id doesn't apply to any known MaqID, remove this pos from the list
    if id < TC {
        floor.maqs.remove(&pos);
    } else {
        match floor.maqs.get(&pos) {
            Some(x) => {
                let mut worker: Box<dyn Worker> = if id == TC || id == TC+1 {
                    Box::new(Mover {
                        maq: *x,
                        right: if id == TC+1 {
                            false
                        } else {
                            true
                        }
                    })
                } else if id >= TC+2 && id < TC+6 {
                    Box::new(Walker {
                        maq: *x,
                        dir: (id-6) as u8
                    })
                } else if id >= TC+6 && id < TC+10 {
                    Box::new(Rotator {
                        maq: *x,
                        dir: (id-10) as u8
                    })
                } else {
                    Box::new(Mover {
                        maq: *x,
                        right: true,
                    })
                };
                worker.work(floor,pos);
            },
            None => return
        }
    }
}
use macroquad::prelude::*;

use crate::maqs::get_maq_tile;

//Tile
// contains a name and color
pub struct Tile {
    pub name: &'static str,
    pub color: Color,
    pub passable: bool,
    pub gravity: usize,
    pub movable: bool,
}

//returns a Tile for a given TileID
pub fn get_tile(id: usize) -> Tile {
    if id > tile_count() {
        return get_maq_tile(id)
    }
    let tile = Tile{
        name: match id {
            0 => "Nothing",
            1 => "Grass",
            2 => "Water",
            _ => "Block"
        },
        color: match id {
            0 => BLACK,
            1 => GREEN,
            2 => BLUE,
            _ => GRAY
        },
        passable: match id {
            0|2 => true,
            1|_ => false
        },
        gravity: match id {
            0|1 => 0,
            2 => 2,
            _ => 0
        },
        movable: match id {
            0|1|2 => true,
            _ => false,
        }
    };
    tile
}

const TC: usize = 4;
pub const fn tile_count() -> usize {TC}
use macroquad::prelude::*;

use crate::display::Drawable;
use crate::inputs::Clickable;

use crate::tiles::*;
use crate::maqs::*;

//Toolbar for selecting Maquinitas
// currently only contains an offset from the start
#[derive(Debug, Clone, Copy)]
pub struct Toolbar {
    pub offset: usize,
    pub selected: isize,
}
impl Drawable for Toolbar {
    fn draw(&self){
        let mut i = self.offset * (screen_height() / (screen_width()*0.05)) as usize;
        let n = ((screen_height() / (screen_width()*0.05))) as usize+i;
        let x = screen_width()*0.95;
        let mut y = screen_height() - screen_width()*0.05;
        draw_rectangle(x+screen_width()*0.00225, y + screen_width()*0.02, screen_width()*0.02, screen_width()*0.02, BLACK);
        draw_rectangle(x+screen_width()*0.0275, y + screen_width()*0.02, screen_width()*0.02, screen_width()*0.02, BLACK);
        y -= screen_width()*0.03;
        while i < (n) {
            let mut tile = get_tile(i as usize);
            if i >= tile_count() {
                tile = get_maq_tile(i-tile_count());
            }
            draw_rectangle(x+screen_width()*0.005, y+screen_width()*0.005, screen_width()*0.04, screen_width()*0.04, tile.color);
            i+=1;
            y-=screen_width()*0.05;
        }
    }
}
impl Clickable for Toolbar {
    fn click(&mut self, mouse: MouseButton, pos: (f32, f32), _dat: isize) {
        if mouse==MouseButton::Left {
            if pos.1>screen_height()*0.975 {
                if pos.0>screen_width()*0.975 {
                    if self.offset<1 {self.offset+=1;}
                } else {
                    if self.offset>0 {self.offset-=1;}
                }
            } else {
                let n = (screen_height() / (screen_width()*0.05)) as usize;
                let i = (self.offset*n)+((screen_height() - screen_width()*0.03 - pos.1)/(screen_width()*0.05))as usize;
                self.selected = i as isize;
            }
        }
    }
}

pub fn init_toolbar() -> Toolbar {
    let toolbar = Toolbar {
        offset: 0,
        selected: -1,
    };
    toolbar
}
 ➡ cargo run
warning: value assigned to `cont` is never read
  --> src\pheno.rs:35:13
   |
35 |             cont = floor.shift(pos, true, dir);
   |             ^^^^
   |
   = help: maybe it is overwritten before being read?
   = note: `#[warn(unused_assignments)]` on by default

warning: `maquinitas` (bin "maquinitas") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.15s
     Running `target\debug\maquinitas.exe`
thread 'main' panicked at core\src\panicking.rs:221:5:
unsafe precondition(s) violated: NonZero::new_unchecked requires the argument to be non-zero
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread caused non-unwinding panic. aborting.
error: process didn't exit successfully: `target\debug\maquinitas.exe` (exit code: 0xc0000409, STATUS_STACK_BUFFER_OVERRUN)