#![allow(dead_code)]
use std::convert::Into;
use std::path::PathBuf;
use std::io::{Read};
use std::fs::File;

use byteorder::{LittleEndian, ReadBytesExt};
use rand::Rng;
use thiserror::Error;

use crate::ship::monster::MonsterType;
use crate::ship::room::Episode;

#[derive(Debug, Copy, Clone)]
struct RawMapEnemy {
    id: u32,
    _unknown1: u16,
    children: u16,
    map_area: u16,
    _unknown4: u16,
    section: u16,
    wave_idd: u16,
    wave_id: u32,
    x: f32,
    y: f32,
    z: f32,
    xrot: u32,
    yrot: u32,
    zrot: u32,
    _field1: u32,
    field2: u32,
    _field3: u32,
    _field4: u32,
    _field5: u32,
    skin: u32,
    _field6: u32
}

impl RawMapEnemy {
    fn from_byte_stream<R: Read>(cursor: &mut R) -> Result<RawMapEnemy, std::io::Error> {
        Ok(RawMapEnemy {
            id: cursor.read_u32::<LittleEndian>()?,
            _unknown1: cursor.read_u16::<LittleEndian>()?,
            children: cursor.read_u16::<LittleEndian>()?,
            map_area: cursor.read_u16::<LittleEndian>()?,
            _unknown4: cursor.read_u16::<LittleEndian>()?,
            section: cursor.read_u16::<LittleEndian>()?,
            wave_idd: cursor.read_u16::<LittleEndian>()?,
            wave_id: cursor.read_u32::<LittleEndian>()?,
            x: cursor.read_f32::<LittleEndian>()?,
            y: cursor.read_f32::<LittleEndian>()?,
            z: cursor.read_f32::<LittleEndian>()?,
            xrot: cursor.read_u32::<LittleEndian>()?,
            yrot: cursor.read_u32::<LittleEndian>()?,
            zrot: cursor.read_u32::<LittleEndian>()?,
            _field1: cursor.read_u32::<LittleEndian>()?,
            field2: cursor.read_u32::<LittleEndian>()?,
            _field3: cursor.read_u32::<LittleEndian>()?,
            _field4: cursor.read_u32::<LittleEndian>()?,
            _field5: cursor.read_u32::<LittleEndian>()?,
            skin: cursor.read_u32::<LittleEndian>()?,
            _field6: cursor.read_u32::<LittleEndian>()?,
        })
    }
}


#[derive(Error, Debug)]
#[error("")]
enum MapEnemyError {
    UnknownEnemyId(u32),
    MapAreaError(#[from] MapAreaError),
}


#[derive(Debug, Copy, Clone)]
pub struct MapEnemy {
    pub monster: MonsterType,
    pub map_area: MapArea,
    hp: u32,
    // TODO: other stats from battleparam
    pub dropped_item: bool,
    pub gave_exp: bool,
}

impl MapEnemy {
    fn from_raw(enemy: RawMapEnemy, episode: &Episode, map_area: &MapArea /*, battleparam */) -> Result<MapEnemy, MapEnemyError> {
        let monster = match (enemy, episode) {
            (RawMapEnemy {id: 64, ..}, _) => MonsterType::Hildebear,
            (RawMapEnemy {id: 65, ..}, Episode::Four) => MonsterType::SandRappy,
            (RawMapEnemy {id: 65, ..}, _) => MonsterType::RagRappy,
            (RawMapEnemy {id: 66, ..}, _) => MonsterType::Monest,
            (RawMapEnemy {id: 67, field2: 0, ..}, _) => MonsterType::SavageWolf,
            (RawMapEnemy {id: 67, ..}, _) => MonsterType::BarbarousWolf,
            (RawMapEnemy {id: 68, skin: 0, ..}, _) => MonsterType::Booma,
            (RawMapEnemy {id: 68, skin: 1, ..}, _) => MonsterType::Gobooma,
            (RawMapEnemy {id: 68, skin: 2, ..}, _) => MonsterType::Gigobooma,
            (RawMapEnemy {id: 96, ..}, _) => MonsterType::GrassAssassin,
            (RawMapEnemy {id: 97, ..}, Episode::Two) => MonsterType::DelLily,
            (RawMapEnemy {id: 97, ..}, _) => MonsterType::PoisonLily,
            (RawMapEnemy {id: 98, ..}, _) => MonsterType::NanoDragon,
            (RawMapEnemy {id: 99, skin: 0, ..}, _) => MonsterType::EvilShark,
            (RawMapEnemy {id: 99, skin: 1, ..}, _) => MonsterType::PalShark,
            (RawMapEnemy {id: 99, skin: 2, ..}, _) => MonsterType::GuilShark,
            (RawMapEnemy {id: 100, ..}, _) => MonsterType::PofuillySlime,
            (RawMapEnemy {id: 101, ..}, _) => MonsterType::PanArms,
            (RawMapEnemy {id: 128, skin: 0, ..}, _) => MonsterType::Dubchic,
            (RawMapEnemy {id: 128, skin: 1, ..}, _) => MonsterType::Gillchic,
            (RawMapEnemy {id: 129, ..}, _) => MonsterType::Garanz,
            (RawMapEnemy {id: 130, field2: 0, ..}, _) => MonsterType::SinowBeat,
            (RawMapEnemy {id: 130, ..}, _) => MonsterType::SinowGold,
            (RawMapEnemy {id: 131, ..}, _) => MonsterType::Canadine,
            (RawMapEnemy {id: 132, ..}, _) => MonsterType::Canane,
            (RawMapEnemy {id: 133, ..}, _) => MonsterType::DubchicSwitch,
            (RawMapEnemy {id: 160, ..}, _) => MonsterType::Delsaber,
            (RawMapEnemy {id: 161, ..}, _) => MonsterType::ChaosSorcerer,
            (RawMapEnemy {id: 162, ..}, _) => MonsterType::DarkGunner,
            (RawMapEnemy {id: 164, ..}, _) => MonsterType::ChaosBringer,
            (RawMapEnemy {id: 165, ..}, _) => MonsterType::DarkBelra,
            (RawMapEnemy {id: 166, skin: 0, ..}, _) => MonsterType::Dimenian,
            (RawMapEnemy {id: 166, skin: 1, ..}, _) => MonsterType::LaDimenian,
            (RawMapEnemy {id: 166, skin: 2, ..}, _) => MonsterType::SoDimenian,
            (RawMapEnemy {id: 167, ..}, _) => MonsterType::Bulclaw,
            (RawMapEnemy {id: 168, ..}, _) => MonsterType::Claw,
            (RawMapEnemy {id: 192, ..}, Episode::One) => MonsterType::Dragon,
            (RawMapEnemy {id: 192, ..}, Episode::Two) => MonsterType::GalGryphon,
            (RawMapEnemy {id: 193, ..}, _) => MonsterType::DeRolLe,
            (RawMapEnemy {id: 194, ..}, _) => MonsterType::VolOptPartA,
            (RawMapEnemy {id: 197, ..}, _) => MonsterType::VolOpt,
            (RawMapEnemy {id: 200, ..}, _) => MonsterType::DarkFalz,
            (RawMapEnemy {id: 202, ..}, _) => MonsterType::OlgaFlow,
            (RawMapEnemy {id: 203, ..}, _) => MonsterType::BarbaRay,
            (RawMapEnemy {id: 204, ..}, _) => MonsterType::GolDragon,
            (RawMapEnemy {id: 212, skin: 0, ..}, _) => MonsterType::SinowBerill,
            (RawMapEnemy {id: 212, skin: 1, ..}, _) => MonsterType::SinowSpigell,
            (RawMapEnemy {id: 213, skin: 0, ..}, _) => MonsterType::Merillia,
            (RawMapEnemy {id: 213, skin: 1, ..}, _) => MonsterType::Meriltas,
            (RawMapEnemy {id: 214, skin: 0, ..}, _) => MonsterType::Mericarol,
            (RawMapEnemy {id: 214, skin: 1, ..}, _) => MonsterType::Merikle,
            (RawMapEnemy {id: 214, skin: 2, ..}, _) => MonsterType::Mericus,
            (RawMapEnemy {id: 215, skin: 0, ..}, _) => MonsterType::UlGibbon,
            (RawMapEnemy {id: 215, skin: 1, ..}, _) => MonsterType::ZolGibbon,
            (RawMapEnemy {id: 216, ..}, _) => MonsterType::Gibbles,
            (RawMapEnemy {id: 217, ..}, _) => MonsterType::Gee,
            (RawMapEnemy {id: 218, ..}, _) => MonsterType::GiGue,
            (RawMapEnemy {id: 219, ..}, _) => MonsterType::Deldepth,
            (RawMapEnemy {id: 220, ..}, _) => MonsterType::Delbiter,
            (RawMapEnemy {id: 221, skin: 0, ..}, _) => MonsterType::Dolmdarl,
            (RawMapEnemy {id: 221, skin: 1, ..}, _) => MonsterType::Dolmolm,
            (RawMapEnemy {id: 222, ..}, _) => MonsterType::Morfos,
            (RawMapEnemy {id: 223, ..}, _) => MonsterType::Recobox,
            (RawMapEnemy {id: 224, skin: 0, ..}, _) => MonsterType::SinowZoa,
            (RawMapEnemy {id: 224, skin: 1, ..}, _) => MonsterType::SinowZele,
            (RawMapEnemy {id: 224, ..}, _) => MonsterType::Epsilon,
            (RawMapEnemy {id: 225, ..}, _) => MonsterType::IllGill,
            (RawMapEnemy {id: 272, ..}, _) => MonsterType::Astark,
            (RawMapEnemy {id: 273, field2: 0, ..}, _) => MonsterType::SatelliteLizard,
            (RawMapEnemy {id: 273, ..}, _) => MonsterType::Yowie,
            (RawMapEnemy {id: 274, ..}, _) => MonsterType::MerissaA,
            (RawMapEnemy {id: 275, ..}, _) => MonsterType::Girtablulu,
            (RawMapEnemy {id: 276, ..}, _) => MonsterType::Zu,
            (RawMapEnemy {id: 277, skin: 0, ..}, _) => MonsterType::Boota,
            (RawMapEnemy {id: 277, skin: 1, ..}, _) => MonsterType::ZeBoota,
            (RawMapEnemy {id: 277, skin: 2, ..}, _) => MonsterType::BaBoota,
            (RawMapEnemy {id: 278, ..}, _) => MonsterType::Dorphon,
            (RawMapEnemy {id: 279, skin: 0, ..}, _) => MonsterType::Goran,
            (RawMapEnemy {id: 279, skin: 1, ..}, _) => MonsterType::PyroGoran,
            (RawMapEnemy {id: 279, skin: 2, ..}, _) => MonsterType::GoranDetonator,
            (RawMapEnemy {id: 281, skin: 0, ..}, _) => MonsterType::SaintMillion,
            (RawMapEnemy {id: 281, skin: 1, ..}, _) => MonsterType::Shambertin,
            _ => return Err(MapEnemyError::UnknownEnemyId(enemy.id))
        };

        Ok(MapEnemy {
            monster: monster,
            map_area: map_area.clone(),
            hp: 0,
            dropped_item: false,
            gave_exp: false,
        })
    }

    fn new(monster: MonsterType, map_area: MapArea) -> MapEnemy {
        MapEnemy {
            monster: monster,
            map_area: map_area,
            hp: 0,
            dropped_item: false,
            gave_exp: false,
        }
    }
}

#[derive(Debug, Copy, Clone)]
struct RawMapObject {
    otype: u16,
    unknown1: u16,
    unknown2:  u32,
    id: u16,
    group: u16,
    section: u16,
    unknown3: u16,
    x: f32,
    y: f32,
    z: f32,
    xrot: u32,
    yrot: u32,
    zrot: u32,
    field1: f32,
    field2: f32,
    field3: f32,
    field4: u32,
    field5: u32,
    field6: u32,
    field7: u32,
}

impl RawMapObject {
    fn from_byte_stream<R: Read>(cursor: &mut R) -> Result<RawMapObject, std::io::Error> {
        let o = RawMapObject {
            otype: cursor.read_u16::<LittleEndian>()?,
            unknown1: cursor.read_u16::<LittleEndian>()?,
            unknown2: cursor.read_u32::<LittleEndian>()?,
            id: cursor.read_u16::<LittleEndian>()?,
            group: cursor.read_u16::<LittleEndian>()?,
            section: cursor.read_u16::<LittleEndian>()?,
            unknown3: cursor.read_u16::<LittleEndian>()?,
            x: cursor.read_f32::<LittleEndian>()?,
            y: cursor.read_f32::<LittleEndian>()?,
            z: cursor.read_f32::<LittleEndian>()?,
            xrot: cursor.read_u32::<LittleEndian>()?,
            yrot: cursor.read_u32::<LittleEndian>()?,
            zrot: cursor.read_u32::<LittleEndian>()?,
            field1: cursor.read_f32::<LittleEndian>()?,
            field2: cursor.read_f32::<LittleEndian>()?,
            field3: cursor.read_f32::<LittleEndian>()?,
            field4: cursor.read_u32::<LittleEndian>()?,
            field5: cursor.read_u32::<LittleEndian>()?,
            field6: cursor.read_u32::<LittleEndian>()?,
            field7: cursor.read_u32::<LittleEndian>()?,
        };

        println!("{:?}", o);

        Ok(o)
    }
}


#[derive(Debug, Copy, Clone)]
pub enum FixedBoxDropType {
    Weapon,
    Armor,
    Tool,
    Meseta,
    Random,
    Specific(u32), // TODO: ItemDropType
}

impl FixedBoxDropType {
    fn from_object(field1: f32, field2: f32, field3: f32, field4: u32) -> FixedBoxDropType {
        match (field1.round() as i32, field2.round() as i32, field3.round() as i32, field4) {
            (0, 1, 1, 0) => {
                FixedBoxDropType::Random
            },
            (0, 1, _, 0x4000000) => {
                FixedBoxDropType::Meseta
            },
            (0, 1, 1, _) => {
                FixedBoxDropType::Specific(field4)
            },
            (-1, 1, 1, _) => { // ???????
                FixedBoxDropType::Specific(field4)
            },
            (0, 1, 0, 0) => {
                FixedBoxDropType::Weapon
            },
            (0, 1, 0, 0x1000000) => {
                FixedBoxDropType::Armor
            },
            (0, 1, 0, 0x3000000) => {
                FixedBoxDropType::Tool
            },
            (1, _, _, _) => {
                FixedBoxDropType::Random
            },
            _ => {
                println!("this box state should not occur? {} {} {} {}", field1.round() as i32, field2.round() as i32, field3.round() as i32, field4);
                FixedBoxDropType::Random
            }
        }
    }
}


#[derive(Debug, Copy, Clone)]
pub enum MapObjectType {
    Box,
    FixedBox(FixedBoxDropType),
    EnemyBox,
    EnemyFixedBox(FixedBoxDropType),
    RuinsBox,
    RuinsFixedBox(FixedBoxDropType),
    RuinsEnemyBox,
    RuinsEnemyFixedBox(FixedBoxDropType),
    CcaBox,
    CcaFixedBox(FixedBoxDropType),
    EmptyBox,
    EmptyFixedBox(FixedBoxDropType),
    RuinsEmptyBox,
    RuinsEmptyFixedBox,
}

#[derive(Debug, Copy, Clone)]
pub struct MapObject {
    pub object: MapObjectType,
    pub map: MapArea,
    pub dropped_item: bool,
    //id: u32,
}

#[derive(Debug, Copy, Clone)]
enum MapObjectError {
    UnknownObjectType(u16, RawMapObject),
}


impl MapObject {
    fn from_raw(raw: RawMapObject, episode: Episode, map_area: &MapArea) -> Result<MapObject, MapObjectError> {
        let object = match (raw, episode) {
            (RawMapObject {otype: 136, ..}, _) => MapObjectType::Box,
            (RawMapObject {otype: 145, ..}, _) => MapObjectType::EnemyBox,
            (RawMapObject {otype: 146, ..}, _) => MapObjectType::FixedBox(FixedBoxDropType::from_object(raw.field1, raw.field2, raw.field3, raw.field4)),
            (RawMapObject {otype: 147, ..}, _) => MapObjectType::EnemyFixedBox(FixedBoxDropType::from_object(raw.field1, raw.field2, raw.field3, raw.field4)),
            (RawMapObject {otype: 149, ..}, _) => MapObjectType::EmptyFixedBox(FixedBoxDropType::from_object(raw.field1, raw.field2, raw.field3, raw.field4)),
            (RawMapObject {otype: 353, ..}, _) => MapObjectType::RuinsFixedBox(FixedBoxDropType::from_object(raw.field1, raw.field2, raw.field3, raw.field4)),
            (RawMapObject {otype: 354, ..}, _) => MapObjectType::RuinsBox,
            (RawMapObject {otype: 355, ..}, _) => MapObjectType::RuinsEnemyFixedBox(FixedBoxDropType::from_object(raw.field1, raw.field2, raw.field3, raw.field4)),
            (RawMapObject {otype: 356, ..}, _) => MapObjectType::RuinsEnemyBox,
            (RawMapObject {otype: 357, ..}, _) => MapObjectType::RuinsEmptyBox,
            _ => return Err(MapObjectError::UnknownObjectType(raw.otype, raw))
        };

        println!("{:#?}", object);
        Ok(MapObject {
            object: object,
            map: map_area.clone(),
            dropped_item: false,
        })
    }
}


#[derive(Debug)]
enum MapVariantMode {
    Online,
    Offline,
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum MapArea {
    Pioneer2Ep1,
    Forest1,
    Forest2,
    Caves1,
    Caves2,
    Caves3,
    Mines1,
    Mines2,
    Ruins1,
    Ruins2,
    Ruins3,
    Dragon,
    DeRolLe,
    VolOpt,
    DarkFalz,
}

#[derive(Error, Debug)]
#[error("")]
pub enum MapAreaError {
    UnknownMapArea(u32),
}

impl MapArea {
    pub fn from_value(episode: &Episode, area: u32) -> Result<MapArea, MapAreaError> {
        match (episode, area) {
            (Episode::One, 0) => Ok(MapArea::Pioneer2Ep1),
            (Episode::One, 1) => Ok(MapArea::Forest1),
            (Episode::One, 2) => Ok(MapArea::Forest2),
            (Episode::One, 3) => Ok(MapArea::Caves1),
            (Episode::One, 4) => Ok(MapArea::Caves2),
            (Episode::One, 5) => Ok(MapArea::Caves3),
            (Episode::One, 6) => Ok(MapArea::Mines1),
            (Episode::One, 7) => Ok(MapArea::Mines2),
            (Episode::One, 8) => Ok(MapArea::Ruins1),
            (Episode::One, 9) => Ok(MapArea::Ruins2),
            (Episode::One, 10) => Ok(MapArea::Ruins3),
            (Episode::One, 11) => Ok(MapArea::Dragon),
            (Episode::One, 12) => Ok(MapArea::DeRolLe),
            (Episode::One, 13) => Ok(MapArea::VolOpt),
            (Episode::One, 14) => Ok(MapArea::DarkFalz),
            _ => Err(MapAreaError::UnknownMapArea(area))
        }
    }

    pub fn drop_area_value(&self) -> Option<u32> {
        match self {
            MapArea::Forest1 => Some(0),
            MapArea::Forest2 => Some(1),
            MapArea::Caves1 => Some(2),
            MapArea::Caves2 => Some(3),
            MapArea::Caves3 => Some(4),
            MapArea::Mines1 => Some(5),
            MapArea::Mines2 => Some(6),
            MapArea::Ruins1 => Some(7),
            MapArea::Ruins2 => Some(8),
            MapArea::Ruins3 => Some(9),
            MapArea::Dragon => Some(2),
            MapArea::DeRolLe => Some(5),
            MapArea::VolOpt => Some(7),
            MapArea::DarkFalz => Some(9),
            _ => None
        }
    }

    pub fn area_value(&self) -> u8 {
        match self {
            MapArea::Pioneer2Ep1 => 0,
            MapArea::Forest1 => 1,
            MapArea::Forest2 => 2,
            MapArea::Caves1 => 3,
            MapArea::Caves2 => 4,
            MapArea::Caves3 => 5,
            MapArea::Mines1 => 6,
            MapArea::Mines2 => 7,
            MapArea::Ruins1 => 8,
            MapArea::Ruins2 => 9,
            MapArea::Ruins3 => 10,
            MapArea::Dragon => 11,
            MapArea::DeRolLe => 12,
            MapArea::VolOpt => 13,
            MapArea::DarkFalz => 14,
        }
    }
}


#[derive(Debug)]
struct MapVariant {
    map: MapArea,
    mode: MapVariantMode,
    major: u8,
    minor: u8,
}

impl MapVariant {
    fn new(map: MapArea, mode: MapVariantMode) -> MapVariant {
        let major = match map {
            MapArea::Pioneer2Ep1 => 0,
            MapArea::Forest1 | MapArea::Forest2 => 0,
            MapArea::Caves1 | MapArea::Caves2 | MapArea::Caves3 => rand::thread_rng().gen_range(0, 3),
            MapArea::Mines1 | MapArea::Mines2 => rand::thread_rng().gen_range(0, 3),
            MapArea::Ruins1 | MapArea::Ruins2 | MapArea::Ruins3 => rand::thread_rng().gen_range(0, 3),
            MapArea::Dragon | MapArea::DeRolLe | MapArea::VolOpt | MapArea::DarkFalz => 0,
        };

        let minor = match map {
            MapArea::Pioneer2Ep1 => 0,
            MapArea::Forest1 => rand::thread_rng().gen_range(0, 5),
            MapArea::Forest2 => rand::thread_rng().gen_range(0, 5),
            MapArea::Caves1 | MapArea::Caves2 | MapArea::Caves3 => rand::thread_rng().gen_range(0, 2),
            MapArea::Mines1 | MapArea::Mines2 => rand::thread_rng().gen_range(0, 2),
            MapArea::Ruins1 | MapArea::Ruins2 | MapArea::Ruins3 => rand::thread_rng().gen_range(0, 2),
            MapArea::Dragon | MapArea::DeRolLe | MapArea::VolOpt | MapArea::DarkFalz => 0,
        };

        MapVariant {
            map: map,
            mode: mode,
            major: major,
            minor: minor,
        }
    }
    // TODO: rename to npc_file
    fn dat_file(&self) -> String {
        match self.map {
            MapArea::Pioneer2Ep1 => "data/maps/map_city00_00e.dat".into(),
            MapArea::Forest1 => format!("data/maps/map_forest01_0{}e.dat", self.minor),
            MapArea::Forest2 => format!("data/maps/map_forest02_0{}e.dat", self.minor),
            MapArea::Caves1 => format!("data/maps/map_cave01_0{}_0{}e.dat", self.major, self.minor),
            MapArea::Caves2 => format!("data/maps/map_cave02_0{}_0{}e.dat", self.major, self.minor),
            MapArea::Caves3 => format!("data/maps/map_cave03_0{}_0{}e.dat", self.major, self.minor),
            MapArea::Mines1 => format!("data/maps/map_machine01_0{}_0{}e.dat", self.major, self.minor),
            MapArea::Mines2 => format!("data/maps/map_machine02_0{}_0{}e.dat", self.major, self.minor),
            MapArea::Ruins1 => format!("data/maps/map_ancient01_0{}_0{}e.dat", self.major, self.minor),
            MapArea::Ruins2 => format!("data/maps/map_ancient02_0{}_0{}e.dat", self.major, self.minor),
            MapArea::Ruins3 => format!("data/maps/map_ancient03_0{}_0{}e.dat", self.major, self.minor),
            MapArea::Dragon => "data/maps/map_boss01e.dat".into(),
            MapArea::DeRolLe => "data/maps/map_boss02e.dat".into(),
            MapArea::VolOpt => "data/maps/map_boss03e.dat".into(),
            MapArea::DarkFalz => "data/maps/map_boss04e.dat".into(),
        }
    }

    fn obj_file(&self) -> String {
        match self.map {
            MapArea::Pioneer2Ep1 => "data/maps/map_city00_00o.dat".into(),
            MapArea::Forest1 => format!("data/maps/map_forest01_0{}o.dat", self.minor),
            MapArea::Forest2 => format!("data/maps/map_forest02_0{}o.dat", self.minor),
            MapArea::Caves1 => format!("data/maps/map_cave01_0{}_0{}o.dat", self.major, self.minor),
            MapArea::Caves2 => format!("data/maps/map_cave02_0{}_0{}o.dat", self.major, self.minor),
            MapArea::Caves3 => format!("data/maps/map_cave03_0{}_0{}o.dat", self.major, self.minor),
            MapArea::Mines1 => format!("data/maps/map_machine01_0{}_0{}o.dat", self.major, self.minor),
            MapArea::Mines2 => format!("data/maps/map_machine02_0{}_0{}o.dat", self.major, self.minor),
            MapArea::Ruins1 => format!("data/maps/map_ancient01_0{}_0{}o.dat", self.major, self.minor),
            MapArea::Ruins2 => format!("data/maps/map_ancient02_0{}_0{}o.dat", self.major, self.minor),
            MapArea::Ruins3 => format!("data/maps/map_ancient03_0{}_0{}o.dat", self.major, self.minor),
            MapArea::Dragon => "data/maps/map_boss01o.dat".into(),
            MapArea::DeRolLe => "data/maps/map_boss02o.dat".into(),
            MapArea::VolOpt => "data/maps/map_boss03o.dat".into(),
            MapArea::DarkFalz => "data/maps/map_boss04o.dat".into(),
        }
    }

    fn pkt_header(&self) -> [u8; 2] {
        [self.major, self.minor]
    }
}


fn objects_from_map_data(path: PathBuf, episode: &Episode, map_variant: &MapVariant) -> Vec<Option<MapObject>> {
    let mut cursor = File::open(path.clone()).unwrap();
    let mut object_data = Vec::new();

    while let Ok(raw_object) = RawMapObject::from_byte_stream(&mut cursor) {
        let object = MapObject::from_raw(raw_object.clone(), *episode, &map_variant.map);
        object_data.push(object.ok());

    }
    object_data
}


fn enemy_data_from_map_data(map_variant: &MapVariant, episode: &Episode) -> Vec<Option<MapEnemy>> {
    let path = map_variant.dat_file();
    let mut cursor = File::open(path).unwrap();
    let mut enemy_data = Vec::new();
    while let Ok(enemy) = RawMapEnemy::from_byte_stream(&mut cursor) {
        let new_enemy = MapEnemy::from_raw(enemy, episode, &map_variant.map);
        enemy_data.append(&mut new_enemy
                               .map_or(vec![None], |monster| {
                                   let mut monsters = Vec::new();
                                   monsters.push(Some(monster));

                                   match monster.monster {
                                       MonsterType::Monest => {
                                           for _ in 0..30 {
                                               monsters.push(Some(MapEnemy::new(MonsterType::Mothmant, monster.map_area)));
                                           }
                                       },
                                       MonsterType::PofuillySlime => {
                                           for _ in 0..4 {
                                               monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area)));
                                           }
                                       },
                                       MonsterType::PanArms => {
                                           monsters.push(Some(MapEnemy::new(MonsterType::Hidoom, monster.map_area)));
                                           monsters.push(Some(MapEnemy::new(MonsterType::Migium, monster.map_area)));
                                       },
                                       MonsterType::SinowBeat => {
                                           for _ in 0..4 {
                                               monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat, monster.map_area)));
                                           }
                                       },
                                       MonsterType::SinowGold => {
                                           for _ in 0..4 {
                                               monsters.push(Some(MapEnemy::new(MonsterType::SinowGold, monster.map_area)));
                                           }
                                       },
                                       MonsterType::Canane => {
                                           for _ in 0..8 {
                                               monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine, monster.map_area)));
                                           }
                                       },
                                       MonsterType::ChaosSorcerer => {
                                           monsters.push(Some(MapEnemy::new(MonsterType::BeeR, monster.map_area)));
                                           monsters.push(Some(MapEnemy::new(MonsterType::BeeL, monster.map_area)));
                                       },
                                       MonsterType::Bulclaw => {
                                           for _ in 0..4 {
                                               monsters.push(Some(MapEnemy::new(MonsterType::Claw, monster.map_area)));
                                           }
                                       },
                                       MonsterType::DeRolLe => {
                                           for _ in 0..10 {
                                               monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody, monster.map_area)));
                                           }
                                           for _ in 0..9 {
                                               monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine, monster.map_area)));
                                           }
                                       },
                                       MonsterType::VolOptPartA => {
                                           for _ in 0..6 {
                                               monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar, monster.map_area)));
                                           }
                                           for _ in 0..24 {
                                               monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor, monster.map_area)));
                                           }
                                           for _ in 0..2 {
                                               monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area)));
                                           }
                                           monsters.push(Some(MapEnemy::new(MonsterType::VolOptAmp, monster.map_area)));
                                           monsters.push(Some(MapEnemy::new(MonsterType::VolOptCore, monster.map_area)));
                                           monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area)));
                                       },
                                       // TOOD: this cares about difficulty (theres an ult-specific darvant?)
                                       MonsterType::DarkFalz => {
                                           for _ in 0..509 {
                                               monsters.push(Some(MapEnemy::new(MonsterType::Darvant, monster.map_area)));
                                           }
                                           monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz3, monster.map_area)));
                                           monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz2, monster.map_area)));
                                           monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz1, monster.map_area)));
                                       },
                                       _ => {
                                           for _ in 0..enemy.children {
                                               monsters.push(Some(MapEnemy::new(monster.monster, monster.map_area)));
                                           }
                                       }
                                   }
                                   monsters
                               }));
    }
    enemy_data
}


#[derive(Error, Debug)]
#[error("")]
pub enum MapsError {
    InvalidMonsterId(usize),
    InvalidObjectId(usize),
}

#[derive(Debug)]
pub struct Maps {
    map_variants: [MapVariant; 15],
    enemy_data: Vec<Option<MapEnemy>>,
    object_data: Vec<Option<MapObject>>,
}

impl Maps {
    pub fn new(episode: Episode) -> Maps {
        let map_variants = match episode {
            Episode::One => {
                [MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online),
                 MapVariant::new(MapArea::Forest1, MapVariantMode::Online),
                 MapVariant::new(MapArea::Forest2, MapVariantMode::Online),
                 MapVariant::new(MapArea::Caves1, MapVariantMode::Online),
                 MapVariant::new(MapArea::Caves2, MapVariantMode::Online),
                 MapVariant::new(MapArea::Caves3, MapVariantMode::Online),
                 MapVariant::new(MapArea::Mines1, MapVariantMode::Online),
                 MapVariant::new(MapArea::Mines2, MapVariantMode::Online),
                 MapVariant::new(MapArea::Ruins1, MapVariantMode::Online),
                 MapVariant::new(MapArea::Ruins2, MapVariantMode::Online),
                 MapVariant::new(MapArea::Ruins3, MapVariantMode::Online),
                 MapVariant::new(MapArea::Dragon, MapVariantMode::Online),
                 MapVariant::new(MapArea::DeRolLe, MapVariantMode::Online),
                 MapVariant::new(MapArea::VolOpt, MapVariantMode::Online),
                 MapVariant::new(MapArea::DarkFalz, MapVariantMode::Online),
                ]
            },
            _ => panic!()
        };

        let maps = Maps {
            enemy_data: map_variants.iter().fold(Vec::new(), |mut enemy_data, map_variant| {
                enemy_data.append(&mut enemy_data_from_map_data(&map_variant, &episode));
                enemy_data
            }),
            object_data: map_variants.iter().map(|map_variant| {
                objects_from_map_data(map_variant.obj_file().into(), &episode, &map_variant)
            }).flatten().collect(),
            map_variants: map_variants,
        };

        maps
    }

    pub fn enemy_by_id(&self, id: usize) -> Result<MapEnemy, MapsError> {
        self.enemy_data[id].ok_or(MapsError::InvalidMonsterId(id))
    }

    pub fn object_by_id(&self, id: usize) -> Result<MapObject, MapsError> {
        self.object_data[id].ok_or(MapsError::InvalidObjectId(id))
    }

    pub fn map_headers(&self) -> [u32; 0x20] {
        self.map_variants.iter()
            .enumerate()
            .fold([0; 0x20], |mut header, (i, map_variant)| {
                let [major, minor] = map_variant.pkt_header();

                header[i*2] = major as u32;
                header[i*2 + 1] = minor as u32;
                header
            })
    }
}