1use crate::bridge::LanguageBridge;
2use crate::constraints::ConstraintSet;
3use crate::domain::DomainModel;
4use crate::error::{SolverForgeError, SolverForgeResult};
5use crate::solver::{
6 HttpSolverService, SolveHandle, SolveRequest, SolveResponse, SolveStatus, SolverConfig,
7 SolverService,
8};
9use crate::ObjectHandle;
10use std::marker::PhantomData;
11use std::sync::Arc;
12
13pub struct SolverFactory<B: LanguageBridge> {
14 config: SolverConfig,
15 service: Arc<dyn SolverService>,
16 domain_model: DomainModel,
17 constraints: ConstraintSet,
18 wasm_module: String,
19 _bridge: PhantomData<B>,
20}
21
22impl<B: LanguageBridge> SolverFactory<B> {
23 pub fn create(
24 config: SolverConfig,
25 service_url: impl Into<String>,
26 domain_model: DomainModel,
27 constraints: ConstraintSet,
28 wasm_module: String,
29 ) -> Self {
30 let service = Arc::new(HttpSolverService::new(service_url));
31 Self {
32 config,
33 service,
34 domain_model,
35 constraints,
36 wasm_module,
37 _bridge: PhantomData,
38 }
39 }
40
41 pub fn with_service(
42 config: SolverConfig,
43 service: Arc<dyn SolverService>,
44 domain_model: DomainModel,
45 constraints: ConstraintSet,
46 wasm_module: String,
47 ) -> Self {
48 Self {
49 config,
50 service,
51 domain_model,
52 constraints,
53 wasm_module,
54 _bridge: PhantomData,
55 }
56 }
57
58 pub fn build_solver(&self, bridge: Arc<B>) -> Solver<B> {
59 Solver {
60 config: self.config.clone(),
61 service: self.service.clone(),
62 domain_model: self.domain_model.clone(),
63 constraints: self.constraints.clone(),
64 wasm_module: self.wasm_module.clone(),
65 bridge,
66 }
67 }
68
69 pub fn config(&self) -> &SolverConfig {
70 &self.config
71 }
72
73 pub fn domain_model(&self) -> &DomainModel {
74 &self.domain_model
75 }
76
77 pub fn constraints(&self) -> &ConstraintSet {
78 &self.constraints
79 }
80
81 pub fn is_service_available(&self) -> bool {
82 self.service.is_available()
83 }
84}
85
86pub struct Solver<B: LanguageBridge> {
87 config: SolverConfig,
88 service: Arc<dyn SolverService>,
89 domain_model: DomainModel,
90 constraints: ConstraintSet,
91 wasm_module: String,
92 bridge: Arc<B>,
93}
94
95impl<B: LanguageBridge> Solver<B> {
96 pub fn solve(&self, problem: ObjectHandle) -> SolverForgeResult<SolveResponse> {
97 let request = self.build_request(problem)?;
98 self.service.solve(&request)
99 }
100
101 pub fn solve_async(&self, problem: ObjectHandle) -> SolverForgeResult<SolveHandle> {
102 let request = self.build_request(problem)?;
103 self.service.solve_async(&request)
104 }
105
106 pub fn get_status(&self, handle: &SolveHandle) -> SolverForgeResult<SolveStatus> {
107 self.service.get_status(handle)
108 }
109
110 pub fn get_best_solution(
111 &self,
112 handle: &SolveHandle,
113 ) -> SolverForgeResult<Option<SolveResponse>> {
114 self.service.get_best_solution(handle)
115 }
116
117 pub fn stop(&self, handle: &SolveHandle) -> SolverForgeResult<()> {
118 self.service.stop(handle)
119 }
120
121 pub fn config(&self) -> &SolverConfig {
122 &self.config
123 }
124
125 pub fn bridge(&self) -> &Arc<B> {
126 &self.bridge
127 }
128
129 pub fn service(&self) -> &Arc<dyn SolverService> {
130 &self.service
131 }
132
133 pub fn domain_model(&self) -> &DomainModel {
134 &self.domain_model
135 }
136
137 pub fn constraints(&self) -> &ConstraintSet {
138 &self.constraints
139 }
140
141 pub fn wasm_module(&self) -> &str {
142 &self.wasm_module
143 }
144
145 fn build_request(&self, problem: ObjectHandle) -> SolverForgeResult<SolveRequest> {
146 let problem_json = self
147 .bridge
148 .serialize_object(problem)
149 .map_err(|e| SolverForgeError::Bridge(format!("Failed to serialize problem: {}", e)))?;
150
151 let domain_dto = self.domain_model.to_dto();
152 let constraints_dto = self.constraints.to_dto();
153
154 let list_accessor = crate::solver::ListAccessorDto::new(
155 "newList", "getItem", "setItem", "size", "append", "insert", "remove", "dealloc",
156 );
157
158 let mut request = SolveRequest::new(
159 domain_dto,
160 constraints_dto,
161 self.wasm_module.clone(),
162 "alloc".to_string(),
163 "dealloc".to_string(),
164 list_accessor,
165 problem_json,
166 );
167
168 if let Some(mode) = &self.config.environment_mode {
169 request = request.with_environment_mode(format!("{:?}", mode).to_uppercase());
170 }
171
172 if let Some(termination) = &self.config.termination {
173 request = request.with_termination(termination.clone());
174 }
175
176 Ok(request)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::bridge::tests::MockBridge;
184 use crate::constraints::Constraint;
185 use crate::domain::DomainModelBuilder;
186
187 fn create_test_domain() -> DomainModel {
188 DomainModelBuilder::new()
189 .solution_class("Timetable")
190 .entity_class("Lesson")
191 .build()
192 }
193
194 fn create_test_constraints() -> ConstraintSet {
195 ConstraintSet::new().with_constraint(Constraint::new("testConstraint"))
196 }
197
198 #[test]
199 fn test_solver_factory_create() {
200 let factory = SolverFactory::<MockBridge>::create(
201 SolverConfig::new(),
202 "http://localhost:8080",
203 create_test_domain(),
204 create_test_constraints(),
205 "AGFzbQ==".to_string(),
206 );
207
208 assert!(factory.config().solution_class.is_none());
209 assert_eq!(factory.domain_model().solution_class(), Some("Timetable"));
210 assert_eq!(factory.constraints().len(), 1);
211 }
212
213 #[test]
214 fn test_solver_factory_build_solver() {
215 let factory = SolverFactory::create(
216 SolverConfig::new().with_solution_class("Timetable"),
217 "http://localhost:8080",
218 create_test_domain(),
219 create_test_constraints(),
220 "AGFzbQ==".to_string(),
221 );
222
223 let bridge = Arc::new(MockBridge::new());
224 let solver = factory.build_solver(bridge);
225
226 assert_eq!(
227 solver.config().solution_class,
228 Some("Timetable".to_string())
229 );
230 }
231
232 #[test]
233 fn test_solver_factory_is_service_available_offline() {
234 let factory = SolverFactory::<MockBridge>::create(
235 SolverConfig::new(),
236 "http://localhost:19999",
237 create_test_domain(),
238 create_test_constraints(),
239 "AGFzbQ==".to_string(),
240 );
241
242 assert!(!factory.is_service_available());
243 }
244
245 #[test]
246 fn test_solver_config_access() {
247 let config = SolverConfig::new()
248 .with_solution_class("Timetable")
249 .with_random_seed(42);
250
251 let factory = SolverFactory::<MockBridge>::create(
252 config,
253 "http://localhost:8080",
254 create_test_domain(),
255 create_test_constraints(),
256 "AGFzbQ==".to_string(),
257 );
258
259 assert_eq!(factory.config().random_seed, Some(42));
260 }
261
262 #[test]
263 fn test_solver_bridge_access() {
264 let factory = SolverFactory::create(
265 SolverConfig::new(),
266 "http://localhost:8080",
267 create_test_domain(),
268 create_test_constraints(),
269 "AGFzbQ==".to_string(),
270 );
271
272 let bridge = Arc::new(MockBridge::new());
273 let solver = factory.build_solver(bridge.clone());
274
275 assert!(Arc::ptr_eq(solver.bridge(), &bridge));
276 }
277}