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
use crate::jasc_palette::PaletteError::*;
use crate::jasc_palette::ParseIssue::*;
use crate::prelude::*;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::str::FromStr;

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum PaletteError {
    InvalidFileType,
    UnsupportedVersion,
    IncorrectNumberOfColors,
    ParseError(ParseIssue),
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ParseIssue {
    FileDesc,
    Version,
    ColorCount,
    ColorSplitting(usize),
    ColorNumbers(usize),
}

impl Display for PaletteError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            InvalidFileType => write!(f, "Invalid file type"),
            UnsupportedVersion => write!(f, "Palette file version is not supported"),
            IncorrectNumberOfColors => write!(f, "Palette file has the wrong number of colors"),
            ParseError(reason) => match reason {
                FileDesc => write!(f, "Error parsing the file type descriptor"),
                Version => write!(f, "Error parsing the version"),
                ColorCount => write!(f, "Error parsing the color count"),
                ColorSplitting(num) => write!(f, "Error splitting color {num}"),
                ColorNumbers(num) => write!(f, "Error parsing color {num}"),
            },
        }
    }
}

impl Error for PaletteError {}

#[derive(Debug, Eq, PartialEq, Clone)]
pub struct JascPalette {
    pub colors: Vec<Color>,
}

impl JascPalette {
    pub const fn new(colors: Vec<Color>) -> Self {
        Self { colors }
    }

    pub fn from(colors: &[Color]) -> Self {
        Self {
            colors: colors.to_vec(),
        }
    }
}

const FILE_HEADER: &str = "JASC-PAL";
const FILE_VER: &str = "0100";

impl JascPalette {
    pub fn to_file_contents(&self) -> String {
        let mut output = String::new();
        output.push_str(FILE_HEADER);
        output.push('\n');
        output.push_str(FILE_VER);
        output.push('\n');
        output.push_str(&self.colors.len().to_string());
        output.push('\n');
        for color in &self.colors {
            output.push_str(&color.r.to_string());
            output.push(' ');
            output.push_str(&color.g.to_string());
            output.push(' ');
            output.push_str(&color.b.to_string());
            if color.a != 255 {
                output.push(' ');
                output.push_str(&color.a.to_string());
            }
            output.push('\n');
        }

        output
    }

    pub fn from_file_contents(text: &str) -> Result<JascPalette, PaletteError> {
        let mut lines = text.lines();
        if let Some(line) = lines.next() {
            if line != FILE_HEADER {
                return Err(InvalidFileType);
            }
        } else {
            return Err(ParseError(FileDesc));
        }
        if let Some(line) = lines.next() {
            if line != FILE_VER {
                return Err(UnsupportedVersion);
            }
        } else {
            return Err(ParseError(Version));
        }
        let count = if let Some(line) = lines.next() {
            match u8::from_str(line) {
                Ok(num) => num,
                Err(_) => return Err(ParseError(ColorCount)),
            }
        } else {
            return Err(ParseError(ColorCount));
        };
        let colors: Vec<&str> = lines.collect();
        if colors.len() as u8 != count {
            return Err(IncorrectNumberOfColors);
        }
        let mut output = vec![];
        for (i, color) in colors.iter().enumerate() {
            let values: Vec<&str> = color.split_whitespace().collect();
            if values.len() != 3 && values.len() != 4 {
                return Err(ParseError(ColorSplitting(i)));
            }
            let r = u8::from_str(values[0]).map_err(|_| ParseError(ColorNumbers(i)))?;
            let g = u8::from_str(values[1]).map_err(|_| ParseError(ColorNumbers(i)))?;
            let b = u8::from_str(values[2]).map_err(|_| ParseError(ColorNumbers(i)))?;
            let mut a = 255;
            if values.len() == 4 {
                a = u8::from_str(values[3]).map_err(|_| ParseError(ColorNumbers(i)))?;
            }
            output.push(Color::new(r, g, b, a))
        }
        Ok(JascPalette::new(output))
    }
}