/*
 * Copyright 2011 ZXing authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use rxing_one_d_proc_derive::OneDWriter;

use crate::BarcodeFormat;
use crate::common::Result;

use super::{CodaBarReader, OneDimensionalCodeWriter};

const START_END_CHARS: [char; 4] = ['A', 'B', 'C', 'D'];
const ALT_START_END_CHARS: [char; 4] = ['T', 'N', '*', 'E'];
const CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED: [char; 4] = ['/', ':', '+', '.'];
const DEFAULT_GUARD: char = START_END_CHARS[0];

/**
 * This class renders CodaBar as {@code boolean[]}.
 *
 * @author dsbnatut@gmail.com (Kazuki Nishiura)
 */
#[derive(OneDWriter, Default)]
pub struct CodaBarWriter;

impl OneDimensionalCodeWriter for CodaBarWriter {
    fn encode_oned(&self, contents: &str) -> Result<Vec<bool>> {
        let contents = if contents.chars().count() < 2 {
            // Can't have a start/end guard, so tentatively add default guards
            format!("{DEFAULT_GUARD}{contents}{DEFAULT_GUARD}")
        } else {
            // Verify input and calculate decoded length.
            let firstChar = contents
                .chars()
                .next()
                .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
                .to_ascii_uppercase();
            let lastChar = contents
                .chars()
                .nth(contents.chars().count() - 1)
                .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
                .to_ascii_uppercase();
            let startsNormal = START_END_CHARS.contains(&firstChar);
            let endsNormal = START_END_CHARS.contains(&lastChar);
            let startsAlt = ALT_START_END_CHARS.contains(&firstChar);
            let endsAlt = ALT_START_END_CHARS.contains(&lastChar);
            if startsNormal {
                if !endsNormal {
                    return Err(Exceptions::illegal_argument_with(format!(
                        "Invalid start/end guards: {contents}"
                    )));
                }
                // else already has valid start/end
                contents.to_owned()
            } else if startsAlt {
                if !endsAlt {
                    return Err(Exceptions::illegal_argument_with(format!(
                        "Invalid start/end guards: {contents}"
                    )));
                }
                // else already has valid start/end
                contents.to_owned()
            } else {
                // Doesn't start with a guard
                if endsNormal || endsAlt {
                    return Err(Exceptions::illegal_argument_with(format!(
                        "Invalid start/end guards: {contents}"
                    )));
                }
                // else doesn't end with guard either, so add a default
                format!("{DEFAULT_GUARD}{contents}{DEFAULT_GUARD}")
            }
        };

        // The start character and the end character are decoded to 10 length each.
        let mut resultLength = 20;
        for ch in contents[1..contents.chars().count() - 1].chars() {
            if ch.is_ascii_digit() || ch == '-' || ch == '$' {
                resultLength += 9;
            } else if CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED.contains(&ch) {
                resultLength += 10;
            } else {
                return Err(Exceptions::illegal_argument_with(format!(
                    "Cannot encode : '{ch}'"
                )));
            }
        }
        // A blank is placed between each character.
        resultLength += contents.chars().count() - 1;

        let mut result = vec![false; resultLength];
        let mut position = 0;
        for index in 0..contents.chars().count() {
            // for (int index = 0; index < contents.length(); index++) {
            let mut c = contents
                .chars()
                .nth(index)
                .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
                .to_ascii_uppercase();
            if index == 0 || index == contents.chars().count() - 1 {
                // The start/end chars are not in the CodaBarReader.ALPHABET.
                c = match c {
                    'T' => 'A',
                    'N' => 'B',
                    '*' => 'C',
                    'E' => 'D',
                    _ => c,
                }
            }
            let mut code = 0;
            for i in 0..CodaBarReader::ALPHABET.len() {
                // Found any, because I checked above.
                if c == CodaBarReader::ALPHABET[i] {
                    code = CodaBarReader::CHARACTER_ENCODINGS[i];
                    break;
                }
            }
            let mut color = true;
            let mut counter = 0;
            let mut bit = 0;
            while bit < 7 {
                // A character consists of 7 digit.
                result[position] = color;
                position += 1;
                if ((code >> (6 - bit)) & 1) == 0 || counter == 1 {
                    color = !color; // Flip the color.
                    bit += 1;
                    counter = 0;
                } else {
                    counter += 1;
                }
            }
            if index < contents.chars().count() - 1 {
                result[position] = false;
                position += 1;
            }
        }
        Ok(result)
    }

    fn getSupportedWriteFormats(&self) -> Option<Vec<crate::BarcodeFormat>> {
        Some(vec![BarcodeFormat::CODABAR])
    }
}

/**
 * @author dsbnatut@gmail.com (Kazuki Nishiura)
 * @author Sean Owen
 */
#[cfg(test)]
mod CodaBarWriterTestCase {
    use crate::{
        BarcodeFormat, Writer,
        common::{BitMatrix, bit_matrix_test_case},
    };

    use super::CodaBarWriter;

    #[test]
    fn testEncode() {
        doTest(
            "B515-3/B",
            concat!(
                "00000",
                "1001001011",
                "0110101001",
                "0101011001",
                "0110101001",
                "0101001101",
                "0110010101",
                "01101101011",
                "01001001011",
                "00000"
            ),
        );
    }

    #[test]
    fn testEncode2() {
        doTest(
            "T123T",
            concat!(
                "00000",
                "1011001001",
                "0101011001",
                "0101001011",
                "0110010101",
                "01011001001",
                "00000"
            ),
        );
    }

    #[test]
    fn testAltStartEnd() {
        assert_eq!(encode("T123456789-$T"), encode("A123456789-$A"));
    }

    fn doTest(input: &str, expected: &str) {
        let result = encode(input);
        assert_eq!(expected, bit_matrix_test_case::matrix_to_string(&result));
    }

    fn encode(input: &str) -> BitMatrix {
        CodaBarWriter
            .encode(input, &BarcodeFormat::CODABAR, 0, 0)
            .expect("must encode")
    }
}
