1use std::borrow::Cow;
2
3use bevy::ecs::entity::EntityHashMap;
4use bevy::prelude::*;
5use either::Either;
6use encoding_rs::SHIFT_JIS;
7use paiagram_oudia::{SerializeToOud, Structure, pair, structure};
8use smallvec::{SmallVec, smallvec};
9
10use crate::class::ClassQuery;
11use crate::entry::{EntryQuery, EntryQueryItem, TravelMode};
12use crate::route::{Route, RouteByDirectionTrips};
13use crate::station::{ParentStationOrStation, Station};
14use crate::trip::{TripQuery, TripQueryItem};
15
16fn make_disp_prop() -> Structure<'static> {
17 structure!("DispProp" =>
18 pair!("JikokuhyouFont" => "PointTextHeight=9;Facename=MS ゴシック"),
19 pair!("JikokuhyouFont" => "PointTextHeight=9;Facename=MS ゴシック;Bold=1"),
20 pair!("JikokuhyouFont" => "PointTextHeight=9;Facename=MS ゴシック;Itaric=1"),
21 pair!("JikokuhyouFont" => "PointTextHeight=9;Facename=MS ゴシック;Bold=1;Itaric=1"),
22 pair!("JikokuhyouFont" => "PointTextHeight=9;Facename=MS ゴシック"),
23 pair!("JikokuhyouFont" => "PointTextHeight=9;Facename=MS ゴシック"),
24 pair!("JikokuhyouFont" => "PointTextHeight=9;Facename=MS ゴシック"),
25 pair!("JikokuhyouFont" => "PointTextHeight=9;Facename=MS ゴシック"),
26 pair!("JikokuhyouVFont" => "PointTextHeight=9;Facename=@MS ゴシック"),
27 pair!("DiaEkimeiFont" => "PointTextHeight=9;Facename=MS ゴシック"),
28 pair!("DiaJikokuFont" => "PointTextHeight=9;Facename=MS ゴシック"),
29 pair!("DiaRessyaFont" => "PointTextHeight=9;Facename=MS ゴシック"),
30 pair!("CommentFont" => "PointTextHeight=9;Facename=MS ゴシック"),
31 pair!("DiaMojiColor" => "00000000"),
32 pair!("DiaHaikeiColor" => "00FFFFFF"),
33 pair!("DiaRessyaColor" => "00000000"),
34 pair!("DiaJikuColor" => "00C0C0C0"),
35 pair!("EkimeiLength" => "6"),
36 pair!("JikokuhyouRessyaWidth" => "5"),
37 )
38}
39
40pub struct OuDia<'a> {
41 pub route_entity: Entity,
42 pub world: &'a mut World,
43}
44
45impl<'a> super::ExportObject for OuDia<'a> {
46 fn extension(&self) -> impl AsRef<str> {
47 ".oud"
48 }
49 fn export_to_buffer(&mut self, buffer: &mut Vec<u8>) {
50 let mut route_buf = vec![pair!(
51 "Rosenmei" =>
52 self.world
53 .get::<Name>(self.route_entity)
54 .unwrap()
55 .to_string()
56 )];
57 self.world
58 .run_system_cached_with(make_stations, (self.route_entity, &mut route_buf))
59 .unwrap();
60 let class_map = self
61 .world
62 .run_system_cached_with(make_classes, &mut route_buf)
63 .unwrap();
64 self.world
65 .run_system_cached_with(
66 make_diagram,
67 (&mut route_buf, self.route_entity, &class_map),
68 )
69 .unwrap();
70 route_buf.extend_from_slice(&[
71 pair!("KitenJikoku" => "200"),
72 pair!("DiagramDgrYZahyouKyoriDefault" => "60"),
73 pair!("Comment" => concat!("Exported by Paiagram ", env!("CARGO_PKG_VERSION"))),
74 ]);
75 let root = vec![
76 pair!("FileType" => "OuDia.1.02"),
77 structure!("Rosen" => ..route_buf),
78 make_disp_prop(),
79 pair!("FileTypeAppComment" =>
80 concat!("Exported by Paiagram ", env!("CARGO_PKG_VERSION")),
81 ),
82 ];
83 let mut utf8_buf = Vec::new();
84 root.serialize_oud_to(&mut utf8_buf).unwrap();
85 let s = String::from_utf8(utf8_buf).unwrap();
86 let (cow, _, _) = SHIFT_JIS.encode(s.as_str());
88 *buffer = cow.into_owned();
89 }
90}
91
92fn split_first_middle_last<T>(slice: &[T]) -> Option<(&T, &[T], &T)> {
93 let (first, rest) = slice.split_first()?;
94 let (last, middle) = rest.split_last().map_or((first, &[][..]), |(l, m)| (l, m));
95 Some((first, middle, last))
96}
97
98fn make_stations(
99 (In(route_entity), InMut(buf)): (In<Entity>, InMut<Vec<Structure<'static>>>),
100 route_q: Query<&Route>,
101 station_name_q: Query<&Name, With<Station>>,
102) {
103 let route = route_q.get(route_entity).unwrap();
104 let Some((first, rest, last)) = split_first_middle_last(&route.stops) else {
105 return;
106 };
107 let make_station = |e: Entity, departure_display: &'static str| -> Structure<'static> {
108 let name = station_name_q.get(e).unwrap().to_string();
109 structure!("Eki" =>
110 pair!("Ekimei" => name), pair!("Ekijikokukeisiki" => departure_display), pair!("Ekikibo" => "Ekikibo_Ippan"), )
114 };
115
116 let first_iter = std::iter::once(make_station(*first, "Jikokukeisiki_NoboriChaku"));
117 let mid_iter = rest
118 .iter()
119 .copied()
120 .map(|e| make_station(e, "Jikokukeisiki_Hatsu"));
121 let last_iter = std::iter::once(make_station(*last, "Jikokukeisiki_KudariChaku"));
122 buf.extend(first_iter);
123 buf.extend(mid_iter);
124 buf.extend(last_iter);
125}
126
127fn make_classes(
128 InMut(buf): InMut<Vec<Structure<'static>>>,
129 class_q: Query<ClassQuery>,
130) -> EntityHashMap<usize> {
131 let mut class_map = EntityHashMap::<usize>::new();
132 let iter = class_q.iter().map(|it| {
133 let len = class_map.len();
135 class_map.insert(it.entity, len);
136 let color = it.stroke.color.get(true);
137 let color_string = format!(
138 "00{:02X}{:02X}{:02X}",
139 color.b(),
141 color.g(),
142 color.r(),
143 );
144 structure!("Ressyasyubetsu" =>
145 pair!("Syubetsumei" => it.name.to_string()),
146 pair!("Ryakusyou" => it.name.to_string()),
147 pair!("JikokuhyouMojiColor" => color_string.clone()),
148 pair!("JikokuhyouFontIndex" => "0"),
149 pair!("DiagramSenColor" => color_string),
150 pair!("DiagramSenStyle" => "SenStyle_Jissen"),
151 pair!("StopMarkDrawType" => "EStopMarkDrawType_DrawOnStop"),
152 )
153 });
154 buf.extend(iter);
155 class_map
156}
157
158fn make_diagram(
159 (InMut(buf), In(route_entity), InRef(class_map)): (
160 InMut<Vec<Structure<'static>>>,
161 In<Entity>,
162 InRef<EntityHashMap<usize>>,
163 ),
164 route_q: Query<(&Route, &RouteByDirectionTrips)>,
165 entry_q: Query<EntryQuery>,
166 trip_q: Query<TripQuery>,
167 parent_station_or_station: Query<ParentStationOrStation>,
168) {
169 let (route, RouteByDirectionTrips { downward, upward }) = route_q.get(route_entity).unwrap();
171 let mut dia_buf = Vec::new();
172 dia_buf.push(pair!("DiaName" => "Paiagram Exported"));
173 dia_buf.push(make_trainset_by_direction(
174 true,
175 trip_q.iter_many(downward.as_slice()),
176 route.stops.as_slice(),
177 class_map,
178 &entry_q,
179 &parent_station_or_station,
180 ));
181 dia_buf.push(make_trainset_by_direction(
182 false,
183 trip_q.iter_many(upward.as_slice()),
184 route.stops.as_slice(),
185 class_map,
186 &entry_q,
187 &parent_station_or_station,
188 ));
189 buf.push(structure!("Dia" => ..dia_buf));
190}
191
192fn make_trainset_by_direction<'a>(
193 downwards: bool,
194 trips_iter: impl Iterator<Item = TripQueryItem<'a, 'a>>,
195 stops: &[Entity],
196 class_map: &EntityHashMap<usize>,
197 entry_q: &Query<EntryQuery>,
198 parent_station_or_station: &Query<ParentStationOrStation>,
199) -> Structure<'static> {
200 let format_time = |it: EntryQueryItem| -> String {
201 match (it.mode.arr, it.mode.dep) {
202 (Some(TravelMode::At(at)), TravelMode::At(dt)) => {
204 let (ah, am, ..) = at.to_hmsd();
205 let (dh, dm, ..) = dt.to_hmsd();
206 format!("{};{}{:02}/{}{:02}", STOP, ah, am, dh, dm)
207 }
208 (Some(TravelMode::At(at)), TravelMode::For(d)) => {
209 let (ah, am, ..) = at.to_hmsd();
210 let (dh, dm, ..) = (at + d).to_hmsd();
211 format!("{};{}{:02}/{}{:02}", STOP, ah, am, dh, dm)
212 }
213 (Some(TravelMode::At(at)), TravelMode::Flexible) => {
214 let (ah, am, ..) = at.to_hmsd();
215 format!("{};{}{:02}/", STOP, ah, am)
216 }
217 (Some(TravelMode::For(_)), TravelMode::At(dt)) => {
219 let (dh, dm, ..) = dt.to_hmsd();
220 let Some(e) = it.estimate else {
221 return format!("{};{}{:02}", STOP, dh, dm);
222 };
223 let (ah, am, ..) = e.arr.to_hmsd();
224 format!("{};{}{:02}/{}{:02}", STOP, ah, am, dh, dm)
225 }
226 (Some(TravelMode::For(_)), TravelMode::For(_)) => {
227 let Some(e) = it.estimate else {
228 return STOP.to_string();
229 };
230 let (ah, am, ..) = e.arr.to_hmsd();
231 let (dh, dm, ..) = e.dep.to_hmsd();
232 format!("{};{}{:02}/{}{:02}", STOP, ah, am, dh, dm)
233 }
234 (Some(TravelMode::For(_)), TravelMode::Flexible) => {
235 let Some(e) = it.estimate else {
236 return STOP.to_string();
237 };
238 let (ah, am, ..) = e.arr.to_hmsd();
239 format!("{};{}{:02}/", STOP, ah, am)
240 }
241 (Some(TravelMode::Flexible), TravelMode::At(t)) => {
243 let (dh, dm, ..) = t.to_hmsd();
244 format!("{};{}{:02}", STOP, dh, dm)
245 }
246 (Some(TravelMode::Flexible), TravelMode::For(_)) => {
247 let Some(e) = it.estimate else {
248 return STOP.to_string();
249 };
250 let (ah, am, ..) = e.arr.to_hmsd();
251 let (dh, dm, ..) = e.dep.to_hmsd();
252 format!("{};{}{:02}/{}{:02}", STOP, ah, am, dh, dm)
253 }
254 (Some(TravelMode::Flexible), TravelMode::Flexible) => STOP.to_string(),
255 (None, TravelMode::At(t)) => {
257 let (h, m, ..) = t.to_hmsd();
258 format!("{};{}{:02}", BYPASS, h, m)
259 }
260 (None, TravelMode::For(_)) => {
262 let Some(e) = it.estimate else {
263 return BYPASS.to_string();
264 };
265 let (h, m, ..) = e.dep.to_hmsd();
266 format!("{};{}{:02}", BYPASS, h, m)
267 }
268 (None, TravelMode::Flexible) => BYPASS.to_string(),
269 }
270 };
271 let magic_word = if downwards { "Kudari" } else { "Nobori" };
272 let mut trips = Vec::new();
273 const STOP: &str = "1";
274 const BYPASS: &str = "2";
275 const NO_OPERATION: &str = "";
276 for it in trips_iter {
277 let a = class_map.get(&it.class.entity());
278 let mut v: SmallVec<[Cow<'static, str>; 1]> = smallvec![NO_OPERATION.into(); stops.len()];
279 let schedule_it = entry_q.iter_many(it.schedule.iter());
280 let mut next_abs_idx = 0;
281 let mut stations = if downwards {
282 Either::Left(stops.iter())
283 } else {
284 Either::Right(stops.iter().rev())
285 };
286 for it in schedule_it {
287 let station_entity = parent_station_or_station.get(it.stop()).unwrap().parent();
288 if let Some(found_pos) = stations.position(|it| *it == station_entity) {
291 let abs_idx = next_abs_idx + found_pos;
292 v[abs_idx] = format_time(it).into();
293 next_abs_idx = abs_idx + 1;
294 }
295 }
296 trips.push(structure!("Ressya" =>
297 pair!("Houkou" => magic_word),
298 pair!("Syubetsu" => a.unwrap().to_string()),
299 pair!("Ressyabangou" => it.name.to_string()),
300 pair!("EkiJikoku" => ..v)
301 ));
302 }
303 structure!(magic_word => ..trips)
304}