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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
use crate::{dstv_element::DstvElement, get_f64_from_str, validate_flange};

/// A struct representing the outer border of a DSTV file
/// A DSTV file can have multiple outer borders
pub struct OuterBorder {
    /// A vector of border points, representing the contour of the outer border
    pub contour: Vec<BorderPoint>,
}

/// A struct representing the inner border of a DSTV file
/// A DSTV file can have multiple inner borders
pub struct InnerBorder {
    /// A vector of border points, representing the contour of the inner border
    pub contour: Vec<BorderPoint>,
}

/// A struct representing a border point
/// A border point is a point on the contour of a border
/// It has an x and y coordinate and a radius
#[derive(Clone, Debug, Default)]
pub struct BorderPoint {
    /// The flange code of the border point
    pub fl_code: String,
    /// The x coordinate of the border point
    pub x_coord: f64,
    /// The y coordinate of the border point
    pub y_coord: f64,
    /// The radius of the border point
    pub radius: f64,

    pub bevel: f64,
}

/// Reads the contour of a border from a DSTV file.
/// The contour is represented by a vector of BorderPoints
/// # Arguments
/// * `lines` - A vector of strings, representing the lines of the DSTV file
/// # Returns
/// A vector of BorderPoints, representing the contour of the border
/// # Panics
/// Panics if the flange code of a border point is invalid
/// Panics if the x coordinate of a border point is invalid
/// Panics if the y coordinate of a border point is invalid
/// Panics if the radius of a border point is invalid
fn read_contour(lines: &[&str]) -> Vec<BorderPoint> {
    lines
        .iter()
        .map(|line| {
            let mut iter = line.split_whitespace().peekable();
            let first = iter.peek();
            let fl_code = match validate_flange(first.unwrap_or(&"x")) {
                true => iter.next().unwrap(),
                false => "x",
            };

            let x_coord = get_f64_from_str(iter.next(), "x_coord");
            let y_coord = get_f64_from_str(iter.next(), "y_coord");
            let radius = get_f64_from_str(iter.next(), "radius");
            let bevel = match iter.peek().is_some() {
                true => get_f64_from_str(iter.next(), "bevel"),
                false => 0.0,
            };

            BorderPoint {
                fl_code: fl_code.to_string(),
                x_coord,
                y_coord,
                radius,
                bevel,
            }
        })
        .collect()
}

/// calculates the bend between two border points if the previous border point has a radius.
/// # Arguments
/// * `point` - The current border point
/// * `prev` - The previous border point
/// # Returns
/// A tuple of four f64 values representing the bend
fn get_bend(point: &BorderPoint, prev: &BorderPoint) -> (f64, f64, f64, f64) {
    match (prev.y_coord > point.y_coord, point.x_coord > prev.x_coord) {
        (true, true) => (prev.x_coord, point.y_coord, point.x_coord, point.y_coord), // left-top corner
        (false, true) => (point.x_coord, prev.y_coord, point.x_coord, point.y_coord), // top-right corner
        (false, false) => (prev.x_coord, point.y_coord, point.x_coord, point.y_coord), // right-bottom corner
        (true, false) => (point.x_coord, prev.y_coord, point.x_coord, point.y_coord), // bottom-left corner
    }
}

/// Converts a contour to an SVG path
/// # Arguments
/// * `contour` - A vector of BorderPoints, representing the contour of a border
/// * `color` - A string representing the color of the border
/// # Returns
/// A string representing the SVG path of the border
fn contour_to_svg(contour: &Vec<BorderPoint>, color: &str) -> String {
    let mut bevel_lines = Vec::new();
    let (path_str, _) = contour.iter().enumerate().fold(
        (String::new(), BorderPoint::default()),
        |(mut path, prev), (i, point)| {
            let segment = if i == 0 {
                format!("M{},{} ", point.x_coord - point.radius, point.y_coord)
            } else if prev.radius > 0.0 {
                let (x1, y1, x2, y2) = get_bend(point, &prev);
                format!("Q{},{},{},{} ", x1, y1, x2, y2)
            } else {
                format!("L{},{} ", point.x_coord, point.y_coord)
            };
            if prev.bevel > 0.0 {
                let bevel_line = format!(
                    "<line x1=\"{}\" y1=\"{}\" x2=\"{}\" y2=\"{}\" stroke=\"red\" stroke-width=\"4\" />",
                    prev.x_coord, prev.y_coord, point.x_coord, point.y_coord
                );
                bevel_lines.push(bevel_line);
            }
            path.push_str(&segment);
            (path, point.clone())
        },
    );

    format!(
        "<path d=\"{}\" fill=\"{}\" stroke=\"black\" stroke-width=\"0.5\" />{}",
        path_str,
        color,
        bevel_lines.join("")
    )
}

impl OuterBorder {
    /// Creates a new OuterBorder from a vector of BorderPoints
    /// # Arguments
    /// * `lines` - A vector of string slices, representing the contour of the border
    pub fn from_lines(lines: &[&str]) -> Self {
        Self {
            contour: read_contour(lines),
        }
    }
}

impl InnerBorder {
    /// Creates a new InnerBorder from a vector of BorderPoints
    /// # Arguments
    /// * `lines` - A vector of string slices, representing the contour of the border
    pub fn from_lines(lines: &[&str]) -> Self {
        Self {
            contour: read_contour(lines),
        }
    }
}
impl DstvElement for OuterBorder {
    /// Converts the outer border to an SVG path
    /// # Returns
    /// A string representing the SVG path of the outer border
    fn to_svg(&self) -> String {
        contour_to_svg(&self.contour, "grey")
    }

    fn from_str(_line: &str) -> Result<Self, &'static str> {
        todo!("Find out how to split traits and casts when when calling in a idiomatic way");
    }

    fn get_index(&self) -> usize {
        0
    }
}

impl DstvElement for InnerBorder {
    /// Converts the inner border to an SVG path
    /// # Returns
    /// A string representing the SVG path of the inner border
    fn to_svg(&self) -> String {
        contour_to_svg(&self.contour, "white")
    }

    fn from_str(_line: &str) -> Result<Self, &'static str> {
        todo!("Find out how to split traits and casts when when calling in a idiomatic way");
    }

    fn get_index(&self) -> usize {
        1
    }
}