1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
use nom::IResult;
use nom::multi::count;
use nom::number::complete::{be_f64, be_u16, be_u32, be_u8};
use crate::common::parser::entity_id;
use crate::entity_state::parser::entity_appearance;
use crate::enumerations::{EntityKind, IsGroupOfGroupedEntityCategory, PlatformDomain};
use crate::is_group_of::model::{GEDEntityLocation, GEDEntityOrientation, GEDRecord1, GEDRecord2, GEDRecord3, GEDRecord4, GEDRecord5, GEDRecord6, GEDRecord7, GEDRecord8, GEDRecord9, GroupEntityDescription, GroupReferencePoint, IsGroupOf};
use crate::model::{EntityType, PduBody};

pub(crate) fn is_group_of_body(input: &[u8]) -> IResult<&[u8], PduBody> {
    let (input, group_id) = entity_id(input)?;
    let (input, category) = be_u8(input)?;
    let category = IsGroupOfGroupedEntityCategory::from(category);
    let (input, number_of_entities) = be_u8(input)?;
    let (input, _padding) = be_u32(input)?;
    let (input, ref_point) = group_reference_point(input)?;
    let (input, descriptions) = count(
        group_entity_description(&category), number_of_entities.into())(input)?;

    Ok((input, IsGroupOf::builder()
        .with_group_id(group_id)
        .with_grouped_entity_category(category)
        .with_group_reference_point(ref_point)
        .with_descriptions(descriptions)
        .build()
        .into_pdu_body()))
}

fn group_reference_point(input: &[u8]) -> IResult<&[u8], GroupReferencePoint> {
    let (input, lat) = be_f64(input)?;
    let (input, lon) = be_f64(input)?;

    Ok((input, GroupReferencePoint::default()
        .with_latitude(lat)
        .with_longitude(lon)))
}

fn group_entity_description(category: &IsGroupOfGroupedEntityCategory) -> impl Fn(&[u8]) -> IResult<&[u8], GroupEntityDescription> + '_ {
    move |input: &[u8]| {
        let (input, ged) = match category {
            IsGroupOfGroupedEntityCategory::Undefined =>
                { (input, GroupEntityDescription::Undefined) }
            IsGroupOfGroupedEntityCategory::BasicGroundCombatVehicle =>
                { ged_record_1(input)? }
            IsGroupOfGroupedEntityCategory::EnhancedGroundCombatVehicle =>
                { ged_record_2(input)? }
            IsGroupOfGroupedEntityCategory::BasicGroundCombatSoldier =>
                { ged_record_3(input)? }
            IsGroupOfGroupedEntityCategory::EnhancedGroundCombatSoldier =>
                { ged_record_4(input)? }
            IsGroupOfGroupedEntityCategory::BasicRotorWingAircraft =>
                { ged_record_5(input)? }
            IsGroupOfGroupedEntityCategory::EnhancedRotorWingAircraft =>
                { ged_record_6(input)? }
            IsGroupOfGroupedEntityCategory::BasicFixedWingAircraft =>
                { ged_record_7(input)? }
            IsGroupOfGroupedEntityCategory::EnhancedFixedWingAircraft =>
                { ged_record_8(input)? }
            IsGroupOfGroupedEntityCategory::GroundLogisticsVehicle =>
                { ged_record_9(input)? }
            IsGroupOfGroupedEntityCategory::Unspecified(_) =>
                { (input, GroupEntityDescription::Undefined) }
        };

        Ok((input, ged))
    }
}

fn ged_record_1(input: &[u8]) -> IResult<&[u8], GroupEntityDescription> {
    let (input, entity_id) = be_u16(input)?;
    let (input, location) = ged_entity_location(input)?;
    let (input, appearance) = entity_appearance(
        EntityType::default()
            .with_kind(EntityKind::Platform)
            .with_domain(PlatformDomain::Land))(input)?;
    let (input, orientation) = ged_entity_orientation(input)?;
    let (input, speed) = be_u8(input)?;
    let (input, turret_azimuth) = be_u8(input)?;
    let (input, gun_elevation) = be_u8(input)?;
    let (input, turret_slew_rate) = be_u8(input)?;
    let (input, gun_elevation_rate) = be_u8(input)?;

    Ok((input, GroupEntityDescription::BasicGroundCombatVehicle(
        GEDRecord1 {
            entity_id,
            location,
            appearance,
            orientation,
            speed,
            turret_azimuth,
            gun_elevation,
            turret_slew_rate,
            gun_elevation_rate,
        }
    )))
}

fn ged_record_2(input: &[u8]) -> IResult<&[u8], GroupEntityDescription> {
    let (input, ged_record) = ged_record_1(input)?;
    let ged_record = if let GroupEntityDescription::BasicGroundCombatVehicle(ged_record) = ged_record { ged_record } else { GEDRecord1::default() };
    let (input, fuel_status) = be_u8(input)?;
    let (input, ground_maintenance_status) = be_u8(input)?;
    let (input, primary_ammunition) = be_u8(input)?;
    let (input, secondary_ammunition) = be_u8(input)?;

    Ok((input, GroupEntityDescription::EnhancedGroundCombatVehicle(
        GEDRecord2 {
            basic_ground_combat_vehicle: ged_record,
            fuel_status,
            ground_maintenance_status,
            primary_ammunition,
            secondary_ammunition,
        }
    )))
}

fn ged_record_3(input: &[u8]) -> IResult<&[u8], GroupEntityDescription> {
    let (input, entity_id) = be_u16(input)?;
    let (input, location) = ged_entity_location(input)?;
    let (input, appearance) = entity_appearance(
        EntityType::default()
            .with_kind(EntityKind::Platform)
            .with_domain(PlatformDomain::Land))(input)?;
    let (input, orientation) = ged_entity_orientation(input)?;
    let (input, speed) = be_u8(input)?;
    let (input, head_azimuth) = be_u8(input)?;
    let (input, head_elevation) = be_u8(input)?;
    let (input, head_scan_rate) = be_u8(input)?;
    let (input, head_elevation_rate) = be_u8(input)?;

    Ok((input, GroupEntityDescription::BasicGroundCombatSoldier(
        GEDRecord3 {
            entity_id,
            location,
            appearance,
            orientation,
            speed,
            head_azimuth,
            head_elevation,
            head_scan_rate,
            head_elevation_rate,
        }
    )))
}

fn ged_record_4(input: &[u8]) -> IResult<&[u8], GroupEntityDescription> {
    let (input, ged_record) = ged_record_3(input)?;
    let ged_record = if let GroupEntityDescription::BasicGroundCombatSoldier(ged_record) = ged_record { ged_record } else { GEDRecord3::default() };
    let (input, water_status) = be_u8(input)?;
    let (input, reset_status) = be_u8(input)?;
    let (input, primary_ammunition) = be_u8(input)?;
    let (input, secondary_ammunition) = be_u8(input)?;

    Ok((input, GroupEntityDescription::EnhancedGroundCombatSoldier(
        GEDRecord4 {
            basic_ground_combat_soldier: ged_record,
            water_status,
            reset_status,
            primary_ammunition,
            secondary_ammunition,
        }
    )))
}

fn ged_record_5(input: &[u8]) -> IResult<&[u8], GroupEntityDescription> {
    let (input, entity_id) = be_u16(input)?;
    let (input, location) = ged_entity_location(input)?;
    let (input, appearance) = entity_appearance(
        EntityType::default()
            .with_kind(EntityKind::Platform)
            .with_domain(PlatformDomain::Air))(input)?;
    let (input, orientation) = ged_entity_orientation(input)?;
    let (input, fuel_status) = be_u8(input)?;
    let (input, movement_horizontal_deviation) = be_u8(input)?;
    let (input, movement_vertical_deviation) = be_u8(input)?;
    let (input, movement_speed) = be_u16(input)?;
    let (input, turret_azimuth) = be_u8(input)?;
    let (input, gun_elevation) = be_u8(input)?;
    let (input, turret_scan_rate) = be_u8(input)?;
    let (input, gun_elevation_rate) = be_u8(input)?;

    Ok((input, GroupEntityDescription::BasicRotorWingAircraft(
        GEDRecord5 {
            entity_id,
            location,
            appearance,
            orientation,
            fuel_status,
            movement_horizontal_deviation,
            movement_vertical_deviation,
            movement_speed,
            turret_azimuth,
            gun_elevation,
            turret_scan_rate,
            gun_elevation_rate,
        }
    )))
}

fn ged_record_6(input: &[u8]) -> IResult<&[u8], GroupEntityDescription> {
    let (input, ged_record) = ged_record_5(input)?;
    let ged_record = if let GroupEntityDescription::BasicRotorWingAircraft(ged_record) = ged_record { ged_record } else { GEDRecord5::default() };
    let (input, supplemental_fuel_status) = be_u8(input)?;
    let (input, air_maintenance_status) = be_u8(input)?;
    let (input, primary_ammunition) = be_u8(input)?;
    let (input, secondary_ammunition) = be_u8(input)?;

    Ok((input, GroupEntityDescription::EnhancedRotorWingAircraft(
        GEDRecord6 {
            basic_rotor_wing_aircraft: ged_record,
            supplemental_fuel_status,
            air_maintenance_status,
            primary_ammunition,
            secondary_ammunition,
        }
    )))
}

fn ged_record_7(input: &[u8]) -> IResult<&[u8], GroupEntityDescription> {
    let (input, entity_id) = be_u16(input)?;
    let (input, location) = ged_entity_location(input)?;
    let (input, appearance) = entity_appearance(
        EntityType::default()
            .with_kind(EntityKind::Platform)
            .with_domain(PlatformDomain::Air))(input)?;
    let (input, orientation) = ged_entity_orientation(input)?;
    let (input, fuel_status) = be_u8(input)?;
    let (input, movement_horizontal_deviation) = be_u8(input)?;
    let (input, movement_vertical_deviation) = be_u8(input)?;
    let (input, movement_speed) = be_u16(input)?;

    Ok((input, GroupEntityDescription::BasicFixedWingAircraft(
        GEDRecord7 {
            entity_id,
            location,
            appearance,
            orientation,
            fuel_status,
            movement_horizontal_deviation,
            movement_vertical_deviation,
            movement_speed,
        }
    )))
}

fn ged_record_8(input: &[u8]) -> IResult<&[u8], GroupEntityDescription> {
    let (input, ged_record) = ged_record_7(input)?;
    let ged_record = if let GroupEntityDescription::BasicFixedWingAircraft(ged_record) = ged_record { ged_record } else { GEDRecord7::default() };
    let (input, supplemental_fuel_status) = be_u8(input)?;
    let (input, air_maintenance_status) = be_u8(input)?;
    let (input, primary_ammunition) = be_u8(input)?;
    let (input, secondary_ammunition) = be_u8(input)?;

    Ok((input, GroupEntityDescription::EnhancedFixedWingAircraft(
        GEDRecord8 {
            basic_fixed_wing_aircraft: ged_record,
            supplemental_fuel_status,
            air_maintenance_status,
            primary_ammunition,
            secondary_ammunition,
        }
    )))
}

fn ged_record_9(input: &[u8]) -> IResult<&[u8], GroupEntityDescription> {
    let (input, entity_id) = be_u16(input)?;
    let (input, location) = ged_entity_location(input)?;
    let (input, appearance) = entity_appearance(
        EntityType::default()
            .with_kind(EntityKind::Platform)
            .with_domain(PlatformDomain::Land))(input)?;
    let (input, orientation) = ged_entity_orientation(input)?;
    let (input, speed) = be_u16(input)?;

    Ok((input, GroupEntityDescription::GroundLogisticsVehicle(
        GEDRecord9 {
            entity_id,
            location,
            appearance,
            orientation,
            speed,
        }
    )))
}

fn ged_entity_location(input: &[u8]) -> IResult<&[u8], GEDEntityLocation> {
    let (input, x_offset) = be_u16(input)?;
    let (input, y_offset) = be_u16(input)?;
    let (input, z_offset) = be_u16(input)?;

    Ok((input, GEDEntityLocation {
        x_offset,
        y_offset,
        z_offset,
    }))
}

fn ged_entity_orientation(input: &[u8]) -> IResult<&[u8], GEDEntityOrientation> {
    let (input, psi) = be_u8(input)?;
    let (input, theta) = be_u8(input)?;
    let (input, phi) = be_u8(input)?;

    Ok((input, GEDEntityOrientation {
        psi,
        theta,
        phi,
    } ))
}