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 "create_list",
156 "get_item",
157 "set_item",
158 "get_size",
159 "append",
160 "insert",
161 "remove",
162 "deallocate_list",
163 );
164
165 let mut request = SolveRequest::new(
166 domain_dto,
167 constraints_dto,
168 self.wasm_module.clone(),
169 "allocate".to_string(),
170 "deallocate".to_string(),
171 list_accessor,
172 problem_json,
173 );
174
175 if let Some(mode) = &self.config.environment_mode {
176 request = request.with_environment_mode(format!("{:?}", mode).to_uppercase());
177 }
178
179 if let Some(termination) = &self.config.termination {
180 request = request.with_termination(termination.clone());
181 }
182
183 Ok(request)
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use crate::bridge::tests::MockBridge;
191 use crate::constraints::Constraint;
192 use crate::domain::DomainModelBuilder;
193
194 fn create_test_domain() -> DomainModel {
195 DomainModelBuilder::new()
196 .solution_class("Timetable")
197 .entity_class("Lesson")
198 .build()
199 }
200
201 fn create_test_constraints() -> ConstraintSet {
202 ConstraintSet::new().with_constraint(Constraint::new("testConstraint"))
203 }
204
205 #[test]
206 fn test_solver_factory_create() {
207 let factory = SolverFactory::<MockBridge>::create(
208 SolverConfig::new(),
209 "http://localhost:8080",
210 create_test_domain(),
211 create_test_constraints(),
212 "AGFzbQ==".to_string(),
213 );
214
215 assert!(factory.config().solution_class.is_none());
216 assert_eq!(factory.domain_model().solution_class(), Some("Timetable"));
217 assert_eq!(factory.constraints().len(), 1);
218 }
219
220 #[test]
221 fn test_solver_factory_build_solver() {
222 let factory = SolverFactory::create(
223 SolverConfig::new().with_solution_class("Timetable"),
224 "http://localhost:8080",
225 create_test_domain(),
226 create_test_constraints(),
227 "AGFzbQ==".to_string(),
228 );
229
230 let bridge = Arc::new(MockBridge::new());
231 let solver = factory.build_solver(bridge);
232
233 assert_eq!(
234 solver.config().solution_class,
235 Some("Timetable".to_string())
236 );
237 }
238
239 #[test]
240 fn test_solver_factory_is_service_available_offline() {
241 let factory = SolverFactory::<MockBridge>::create(
242 SolverConfig::new(),
243 "http://localhost:19999",
244 create_test_domain(),
245 create_test_constraints(),
246 "AGFzbQ==".to_string(),
247 );
248
249 assert!(!factory.is_service_available());
250 }
251
252 #[test]
253 fn test_solver_config_access() {
254 let config = SolverConfig::new()
255 .with_solution_class("Timetable")
256 .with_random_seed(42);
257
258 let factory = SolverFactory::<MockBridge>::create(
259 config,
260 "http://localhost:8080",
261 create_test_domain(),
262 create_test_constraints(),
263 "AGFzbQ==".to_string(),
264 );
265
266 assert_eq!(factory.config().random_seed, Some(42));
267 }
268
269 #[test]
270 fn test_solver_bridge_access() {
271 let factory = SolverFactory::create(
272 SolverConfig::new(),
273 "http://localhost:8080",
274 create_test_domain(),
275 create_test_constraints(),
276 "AGFzbQ==".to_string(),
277 );
278
279 let bridge = Arc::new(MockBridge::new());
280 let solver = factory.build_solver(bridge.clone());
281
282 assert!(Arc::ptr_eq(solver.bridge(), &bridge));
283 }
284}