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
// TODO This doesn't really belong in gameplay/freeform

use anyhow::Result;
use serde::Deserialize;

use abstutil::Timer;
use map_gui::tools::{find_exe, FilePicker, RunCommand};
use map_model::Map;
use synthpop::{ExternalPerson, Scenario};
use widgetry::tools::PopupMsg;
use widgetry::EventCtx;

use crate::app::Transition;
use crate::sandbox::gameplay::GameplayMode;
use crate::sandbox::SandboxMode;

pub fn import_grid2demand(ctx: &mut EventCtx) -> Transition {
    Transition::Push(FilePicker::new_state(
        ctx,
        None,
        Box::new(|ctx, app, maybe_path| {
            if let Ok(Some(path)) = maybe_path {
                Transition::Replace(RunCommand::new_state(
                    ctx,
                    true,
                    vec![
                        find_exe("cli"),
                        "import-grid2-demand".to_string(),
                        format!("--map={}", app.primary.map.get_name().path()),
                        format!("--input={}", path),
                    ],
                    Box::new(|_, app, success, _| {
                        if success {
                            // Clear out the cached scenario. If we repeatedly use this import, the
                            // scenario name is always the same, but the file is changing.
                            app.primary.scenario = None;
                            Transition::Replace(SandboxMode::simple_new(
                                app,
                                GameplayMode::PlayScenario(
                                    app.primary.map.get_name().clone(),
                                    "grid2demand".to_string(),
                                    Vec::new(),
                                ),
                            ))
                        } else {
                            // The popup already explained the failure
                            Transition::Keep
                        }
                    }),
                ))
            } else {
                // The user didn't pick a file, so stay on the scenario picker
                Transition::Pop
            }
        }),
    ))
}

pub fn import_json(ctx: &mut EventCtx) -> Transition {
    Transition::Push(FilePicker::new_state(
        ctx,
        None,
        Box::new(|ctx, app, maybe_path| {
            if let Ok(Some(path)) = maybe_path {
                let result = ctx.loading_screen("import JSON scenario", |_, timer| {
                    import_json_scenario(&app.primary.map, path, timer)
                });
                match result {
                    Ok(scenario_name) => {
                        // Clear out the cached scenario. If we repeatedly use this import, the
                        // scenario name is always the same, but the file is changing.
                        app.primary.scenario = None;
                        Transition::Replace(SandboxMode::simple_new(
                            app,
                            GameplayMode::PlayScenario(
                                app.primary.map.get_name().clone(),
                                scenario_name,
                                Vec::new(),
                            ),
                        ))
                    }
                    Err(err) => Transition::Replace(PopupMsg::new_state(
                        ctx,
                        "Error",
                        vec![err.to_string()],
                    )),
                }
            } else {
                // The user didn't pick a file, so stay on the scenario picker
                Transition::Pop
            }
        }),
    ))
}

// This works the same as importer/src/bin/import_traffic.rs. We should decide how to share
// behavior between UI and CLI tools.
fn import_json_scenario(map: &Map, input: String, timer: &mut Timer) -> Result<String> {
    let skip_problems = true;
    let input: Input = abstio::maybe_read_json(input, timer)?;

    let mut s = Scenario::empty(map, &input.scenario_name);
    // Include all buses/trains
    s.only_seed_buses = None;
    s.people = ExternalPerson::import(map, input.people, skip_problems)?;
    // Always clean up people with no-op trips (going between the same buildings)
    s = s.remove_weird_schedules(true);
    s.save();
    Ok(s.scenario_name)
}

#[derive(Deserialize)]
struct Input {
    scenario_name: String,
    people: Vec<ExternalPerson>,
}