use super::core::*;
use assembly_core::nom::{
    call, cond, do_parse, fold_many_m_n, length_count, map_opt, map_res, named, named_args,
    number::complete::{le_f32, le_u32, le_u8},
    pair, switch, value, IResult,
};
use assembly_core::parser::{
    parse_object_id, parse_object_template, parse_quat, parse_quat_wxyz, parse_u32_wstring,
    parse_u8_bool, parse_u8_wstring, parse_vec3f, parse_world_id,
};
use num_traits::FromPrimitive;
use std::collections::HashMap;
use std::convert::TryFrom;

named!(pub parse_zone_paths_version<ZonePathsVersion>,
    map_opt!(le_u32, ZonePathsVersion::from_u32)
);

named!(pub parse_path_version<PathVersion>,
    map_opt!(le_u32, PathVersion::from_u32)
);

named!(pub parse_path_type<PathType>,
    map_opt!(le_u32, PathType::from_u32)
);

named!(pub parse_path_composition<PathComposition>,
    map_opt!(le_u32, PathComposition::from_u32)
);

named!(pub parse_path_data_movement<PathDataMovement>,
    value!(PathDataMovement{})
);

named_args!(pub parse_path_data_moving_platform(version: PathVersion)<PathDataMovingPlatform>,
    do_parse!(
        a: cond!(version.min(18), call!(le_u8)) >>
        b: cond!(version.min(13) && !version.min(18), call!(parse_u8_wstring)) >>
        (PathDataMovingPlatform{
            something: a,
            platform_travel_sound: b,
        })
    )
);

named!(pub parse_property_rental_time_unit<PropertyRentalTimeUnit>,
    map_opt!(le_u32, PropertyRentalTimeUnit::from_u32)
);

named!(pub parse_property_achievement_required<PropertyAchievementRequired>,
    map_opt!(le_u32, PropertyAchievementRequired::from_u32)
);

named!(pub parse_path_data_property<PathDataProperty>,
    do_parse!(
        value_1: le_u32 >>
        price: le_u32 >>
        rental_time: le_u32 >>
        associated_map: parse_world_id >>
        value_2: le_u32 >>
        display_name: parse_u8_wstring >>
        display_description: parse_u32_wstring >>
        value_3: le_u32 >>
        clone_limit: le_u32 >>
        reputation_multiplier: le_f32 >>
        rental_time_unit: parse_property_rental_time_unit >>
        achievement_required: parse_property_achievement_required >>
        player_zone_coordinate: parse_vec3f >>
        max_build_height: le_f32 >>
        (PathDataProperty {
            value_1,
            price, rental_time, associated_map,
            value_2,
            display_name, display_description,
            value_3,
            clone_limit, reputation_multiplier,
            rental_time_unit, achievement_required,
            player_zone_coordinate, max_build_height,
        })
    )
);

named_args!(pub parse_path_data_camera(version: PathVersion)<PathDataCamera>,
    do_parse!(
        next_path: parse_u8_wstring >>
        value_1: cond!(version.min(14), le_u8) >>
        (PathDataCamera{next_path, value_1})
    )
);

named!(pub parse_path_data_spawner<PathDataSpawner>,
    do_parse!(
        spawned_lot: parse_object_template >>
        respawn_time: le_u32 >>
        max_to_spawn: le_u32 >>
        min_to_spawn: le_u32 >>
        spawner_obj_id: parse_object_id >>
        activate_network_on_load: parse_u8_bool >>
        (PathDataSpawner{
            spawned_lot,
            respawn_time,
            max_to_spawn,
            min_to_spawn,
            spawner_obj_id,
            activate_network_on_load,
        })
    )
);

named!(pub parse_path_data_showcase<PathDataShowcase>,
    value!(PathDataShowcase{})
);

named!(pub parse_path_data_race<PathDataRace>,
    value!(PathDataRace{})
);

named!(pub parse_path_data_rail<PathDataRail>,
    value!(PathDataRail{})
);

named!(pub parse_path_waypoint_data_movement<PathWaypointDataMovement>,
    do_parse!(
        config: parse_waypoint_config >>
        (PathWaypointDataMovement{config})
    )
);

named!(pub parse_path_waypoint_data_moving_platform_sounds<PathWaypointDataMovingPlatformSounds>,
    do_parse!(
        depart_sound: parse_u8_wstring >>
        arrive_sound: parse_u8_wstring >>
        (PathWaypointDataMovingPlatformSounds{ depart_sound, arrive_sound })
    )
);

named_args!(pub parse_path_waypoint_data_moving_platform(version: PathVersion)<PathWaypointDataMovingPlatform>,
    do_parse!(
        rotation: parse_quat >>
        lock_player: parse_u8_bool >>
        speed: le_f32 >>
        wait: le_f32 >>
        sounds: cond!(version.min(13), call!(parse_path_waypoint_data_moving_platform_sounds)) >>
        (PathWaypointDataMovingPlatform {
            rotation, lock_player, speed, wait, sounds
        })
    )
);

named!(pub parse_path_waypoint_data_property<PathWaypointDataProperty>,
    value!(PathWaypointDataProperty{})
);

named!(pub parse_path_waypoint_data_camera<PathWaypointDataCamera>,
    do_parse!(
        rotation: parse_quat >>
        time: le_f32 >>
        value_5: le_f32 >>
        tension: le_f32 >>
        continuity: le_f32 >>
        bias: le_f32 >>
        (PathWaypointDataCamera {
            rotation, time, value_5,
            tension, continuity, bias,
        })
    )
);

named!(pub parse_path_waypoint_data_spawner<PathWaypointDataSpawner>,
    do_parse!(
        rotation: parse_quat >>
        config: parse_waypoint_config >>
        (PathWaypointDataSpawner{ rotation, config })
    )
);

named!(pub parse_path_waypoint_data_showcase<PathWaypointDataShowcase>,
    value!(PathWaypointDataShowcase{})
);

named!(pub parse_path_waypoint_data_race<PathWaypointDataRace>,
    do_parse!(
        rotation: parse_quat >>
        value_1: le_u8 >>
        value_2: le_u8 >>
        value_3: le_f32 >>
        value_4: le_f32 >>
        value_5: le_f32 >>
        (PathWaypointDataRace{ rotation, value_1, value_2, value_3, value_4, value_5 })
    )
);

named!(
    parse_path_waypoint_variant_movement<PathWaypointVariantMovement>,
    do_parse!(
        position: parse_vec3f
            >> data: parse_path_waypoint_data_movement
            >> (PathWaypointVariantMovement { position, data })
    )
);

named_args!(parse_path_variant_movement(header: PathHeader)<PathVariantMovement>,
    do_parse!(
        path_data: parse_path_data_movement >>
        waypoints: length_count!(le_u32, parse_path_waypoint_variant_movement) >>
        (PathVariantMovement {
            header, path_data, waypoints,
        })
    )
);

named_args!(parse_path_waypoint_variant_moving_platform(version: PathVersion)<PathWaypointVariantMovingPlatform>,
    do_parse!(
        position: parse_vec3f >>
        data: call!(parse_path_waypoint_data_moving_platform, version) >>
        (PathWaypointVariantMovingPlatform{position, data})
    )
);

named_args!(parse_path_variant_moving_platform(header: PathHeader)<PathVariantMovingPlatform>,
    do_parse!(
        path_data: call!(parse_path_data_moving_platform, header.version) >>
        waypoints: length_count!(le_u32, call!(parse_path_waypoint_variant_moving_platform, header.version)) >>
        (PathVariantMovingPlatform {
            header, path_data, waypoints,
        })
    )
);

named!(
    parse_path_waypoint_variant_property<PathWaypointVariantProperty>,
    do_parse!(
        position: parse_vec3f
            >> data: parse_path_waypoint_data_property
            >> (PathWaypointVariantProperty { position, data })
    )
);

named_args!(parse_path_variant_property(header: PathHeader)<PathVariantProperty>,
    do_parse!(
        path_data: parse_path_data_property >>
        waypoints: length_count!(le_u32, parse_path_waypoint_variant_property) >>
        (PathVariantProperty {
            header, path_data, waypoints,
        })
    )
);

named!(
    parse_path_waypoint_variant_camera<PathWaypointVariantCamera>,
    do_parse!(
        position: parse_vec3f
            >> data: parse_path_waypoint_data_camera
            >> (PathWaypointVariantCamera { position, data })
    )
);

named_args!(parse_path_variant_camera(header: PathHeader)<PathVariantCamera>,
    do_parse!(
        path_data: call!(parse_path_data_camera, header.version) >>
        waypoints: length_count!(le_u32, parse_path_waypoint_variant_camera) >>
        (PathVariantCamera {
            header, path_data, waypoints,
        })
    )
);

named!(
    parse_path_waypoint_variant_spawner<PathWaypointVariantSpawner>,
    do_parse!(
        position: parse_vec3f
            >> data: parse_path_waypoint_data_spawner
            >> (PathWaypointVariantSpawner { position, data })
    )
);

named_args!(parse_path_variant_spawner(header: PathHeader)<PathVariantSpawner>,
    do_parse!(
        path_data: parse_path_data_spawner >>
        waypoints: length_count!(le_u32, parse_path_waypoint_variant_spawner) >>
        (PathVariantSpawner {
            header, path_data, waypoints,
        })
    )
);

named!(
    parse_path_waypoint_variant_showcase<PathWaypointVariantShowcase>,
    do_parse!(
        position: parse_vec3f
            >> data: parse_path_waypoint_data_showcase
            >> (PathWaypointVariantShowcase { position, data })
    )
);

named_args!(parse_path_variant_showcase(header: PathHeader)<PathVariantShowcase>,
    do_parse!(
        path_data: parse_path_data_showcase >>
        waypoints: length_count!(le_u32, parse_path_waypoint_variant_showcase) >>
        (PathVariantShowcase {
            header, path_data, waypoints,
        })
    )
);

named!(
    parse_path_waypoint_variant_race<PathWaypointVariantRace>,
    do_parse!(
        position: parse_vec3f
            >> data: parse_path_waypoint_data_race
            >> (PathWaypointVariantRace { position, data })
    )
);

named_args!(parse_path_variant_race(header: PathHeader)<PathVariantRace>,
    do_parse!(
        path_data: parse_path_data_race >>
        waypoints: length_count!(le_u32, parse_path_waypoint_variant_race) >>
        (PathVariantRace {
            header, path_data, waypoints,
        })
    )
);

named!(
    parse_path_waypoint_variant_rail<PathWaypointVariantRail>,
    do_parse!(
        position: parse_vec3f
            >> rotation: parse_quat_wxyz
            >> config: parse_waypoint_config
            >> (PathWaypointVariantRail {
                position,
                data: PathWaypointDataRail {
                    rotation,
                    speed: None,
                    config
                }
            })
    )
);

named!(
    parse_path_waypoint_variant_rail_17<PathWaypointVariantRail>,
    do_parse!(
        position: parse_vec3f
            >> rotation: parse_quat_wxyz
            >> speed: le_f32
            >> config: parse_waypoint_config
            >> (PathWaypointVariantRail {
                position,
                data: PathWaypointDataRail {
                    rotation,
                    speed: Some(speed),
                    config
                }
            })
    )
);

fn parse_path_variant_rail(i: &[u8], header: PathHeader) -> IResult<&[u8], PathVariantRail> {
    let version = header.version;
    do_parse!(
        i,
        path_data: parse_path_data_rail
            >> waypoints:
                switch!(value!(version.min(17)),
                    true => length_count!(le_u32, parse_path_waypoint_variant_rail_17) |
                    false => length_count!(le_u32, parse_path_waypoint_variant_rail)
                )
            >> (PathVariantRail {
                header,
                path_data,
                waypoints,
            })
    )
}

fn parse_path_data(inp: &[u8], path_type: PathType, header: PathHeader) -> IResult<&[u8], Path> {
    match path_type {
        PathType::Movement => {
            let (inp, var) = parse_path_variant_movement(inp, header)?;
            Ok((inp, Path::Movement(var)))
        }
        PathType::MovingPlatform => {
            let (inp, var) = parse_path_variant_moving_platform(inp, header)?;
            Ok((inp, Path::MovingPlatform(var)))
        }
        PathType::Property => {
            let (inp, var) = parse_path_variant_property(inp, header)?;
            Ok((inp, Path::Property(var)))
        }
        PathType::Camera => {
            let (inp, var) = parse_path_variant_camera(inp, header)?;
            Ok((inp, Path::Camera(var)))
        }
        PathType::Spawner => {
            let (inp, var) = parse_path_variant_spawner(inp, header)?;
            Ok((inp, Path::Spawner(var)))
        }
        PathType::Showcase => {
            let (inp, var) = parse_path_variant_showcase(inp, header)?;
            Ok((inp, Path::Showcase(var)))
        }
        PathType::Race => {
            let (inp, var) = parse_path_variant_race(inp, header)?;
            Ok((inp, Path::Race(var)))
        }
        PathType::Rail => {
            let (inp, var) = parse_path_variant_rail(inp, header)?;
            Ok((inp, Path::Rail(var)))
        }
    }
}

named!(pub parse_path<Path>,
    do_parse!(
        version: parse_path_version >>
        path_name: parse_u8_wstring >>
        path_type: parse_path_type >>
        value_1: le_u32 >>
        path_composition: parse_path_composition >>
        header: value!(PathHeader{version, path_name, value_1, path_composition}) >>
        path: call!(parse_path_data, path_type, header) >>
        (path)
    )
);

named!(pub parse_waypoint_config_entry<(String, String)>,
    pair!(parse_u8_wstring, parse_u8_wstring)
);

fn extend_config_map(
    mut map: HashMap<String, String>,
    entry: (String, String),
) -> HashMap<String, String> {
    map.insert(entry.0, entry.1);
    map
}

named!(pub parse_waypoint_config<WaypointConfig>,
    do_parse!(
        count: map_res!(le_u32, usize::try_from) >>
        map: fold_many_m_n!(count, count, parse_waypoint_config_entry, HashMap::new(), extend_config_map) >>
        (map)
    )
);

named!(pub parse_zone_paths<ZonePaths>,
    do_parse!(
        version: parse_zone_paths_version >>
        paths: length_count!(le_u32, parse_path) >>
        (ZonePaths{version,paths})
    )
);
