use super::active::ActiveKnowledgeBase;
use super::base::{KnowledgeBase, KnowledgeBaseTrait};
use super::stats::KnowledgeBaseStats;
use crate::letter::Letter;
use crate::query::OutputQuery;
use crate::word::Word;
use std::io::{Read, Write};
use std::net::TcpStream;
use std::time::Duration;
pub struct NetworkActiveKnowledgeBase {
base: KnowledgeBase,
target_host: String,
target_port: u16,
timeout: Duration,
target_running: bool,
}
impl NetworkActiveKnowledgeBase {
pub fn new(target_host: String, target_port: u16, timeout: Duration) -> Self {
NetworkActiveKnowledgeBase {
base: KnowledgeBase::new(),
target_host,
target_port,
timeout,
target_running: false,
}
}
pub fn target_host(&self) -> &str {
&self.target_host
}
pub fn target_port(&self) -> u16 {
self.target_port
}
pub fn timeout(&self) -> Duration {
self.timeout
}
pub fn set_timeout(&mut self, timeout: Duration) {
self.timeout = timeout;
}
fn connection_addr(&self) -> String {
format!("{}:{}", self.target_host, self.target_port)
}
fn send_and_receive(&self, stream: &mut TcpStream, data: &[u8]) -> Result<Vec<u8>, String> {
stream
.write_all(data)
.map_err(|e| format!("Failed to send data: {}", e))?;
let mut buffer = vec![0; 1024];
let n = stream
.read(&mut buffer)
.map_err(|e| format!("Failed to receive data: {}", e))?;
buffer.truncate(n);
Ok(buffer)
}
fn submit_letter(&self, stream: &mut TcpStream, letter: &Letter) -> Result<Letter, String> {
let data = letter.symbols().into_bytes();
let response = self.send_and_receive(stream, &data)?;
match String::from_utf8(response) {
Ok(response_str) => {
let trimmed = response_str.trim();
Ok(Letter::new(trimmed))
}
Err(_) => Ok(Letter::new("")),
}
}
}
impl ActiveKnowledgeBase for NetworkActiveKnowledgeBase {
fn start_target(&mut self) -> Result<(), String> {
self.target_running = true;
Ok(())
}
fn stop_target(&mut self) -> Result<(), String> {
self.target_running = false;
Ok(())
}
fn submit_word(&mut self, word: &Word) -> Result<Word, String> {
let addr = self.connection_addr();
let mut stream = TcpStream::connect(&addr)
.map_err(|e| format!("Failed to connect to {}: {}", addr, e))?;
stream
.set_read_timeout(Some(self.timeout))
.map_err(|e| format!("Failed to set read timeout: {}", e))?;
stream
.set_write_timeout(Some(self.timeout))
.map_err(|e| format!("Failed to set write timeout: {}", e))?;
let mut output_letters = Vec::new();
for letter in word.letters() {
output_letters.push(self.submit_letter(&mut stream, letter)?);
}
Ok(Word::from_letters(output_letters))
}
fn is_target_running(&self) -> bool {
self.target_running
}
}
impl KnowledgeBaseTrait for NetworkActiveKnowledgeBase {
fn resolve_query(&mut self, query: &mut OutputQuery) -> Result<(), String> {
match self.base.resolve_query(query) {
Ok(_) => Ok(()),
Err(_) => {
self.start_target()?;
let submit_result = self.submit_word(&query.input_word);
let stop_result = self.stop_target();
let output = submit_result?;
stop_result?;
self.base.add_word(&query.input_word, &output)?;
query.set_result(output);
Ok(())
}
}
}
fn add_word(&mut self, input_word: &Word, output_word: &Word) -> Result<(), String> {
self.base.add_word(input_word, output_word)
}
}
impl NetworkActiveKnowledgeBase {
pub fn stats(&self) -> &KnowledgeBaseStats {
self.base.stats()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_creation() {
let kb =
NetworkActiveKnowledgeBase::new("localhost".to_string(), 3000, Duration::from_secs(5));
assert_eq!(kb.target_host(), "localhost");
assert_eq!(kb.target_port(), 3000);
assert!(!kb.is_target_running());
}
#[test]
fn test_connection_addr() {
let kb = NetworkActiveKnowledgeBase::new(
"example.com".to_string(),
8080,
Duration::from_secs(5),
);
assert_eq!(kb.connection_addr(), "example.com:8080");
}
#[test]
fn test_set_timeout() {
let mut kb =
NetworkActiveKnowledgeBase::new("localhost".to_string(), 3000, Duration::from_secs(5));
assert_eq!(kb.timeout(), Duration::from_secs(5));
kb.set_timeout(Duration::from_secs(10));
assert_eq!(kb.timeout(), Duration::from_secs(10));
}
}