// SPDX-License-Identifier:BSL-1.0

// Boost.Polygon library detail/robust_fpt.hpp header file

//          Copyright Andrii Sydorchuk 2010-2012.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

// See http://www.boost.org for updates, documentation, and revision history of C++ code..

// Ported from C++ boost 1.76.0 to Rust in 2020/2021 by Eadf (github.com/eadf)

//! Utility for reading example files.

use crate::{BvError, cast, geometry};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;

#[derive(Debug)]
enum InputData<T: crate::InputType> {
    Number(u32),
    Point(geometry::Point<T>),
    Line(geometry::Line<T>),
}

#[derive(Eq, PartialEq, Debug)]
enum StateMachine {
    StartingPoints,
    ExpectPoints,
    StartingLines,
    ExpectLines,
}

fn line_to_data<I: crate::InputType>(line: &str) -> Option<InputData<I>> {
    let line = line.split(' ').collect::<Vec<&str>>();
    //tln!("line split: {:?}", line);
    match line.len() as u32 {
        1 => {
            if let Ok(n) = line[0].parse::<u32>() {
                return Some(InputData::Number(n));
            } else {
                eprintln!("failed to parse {}, ignoring line", line[0]);
            }
        }
        2 => {
            if let Ok(x1) = line[0].parse::<i32>() {
                if let Ok(y1) = line[1].parse::<i32>() {
                    return Some(InputData::Point(geometry::Point::<I> {
                        x: cast::<i32, I>(x1),
                        y: cast::<i32, I>(y1),
                    }));
                } else {
                    println!("failed to parse {}, ignoring line", line[1]);
                }
            } else {
                println!("failed to parse {}, ignoring line", line[0]);
            }
        }
        4 => {
            if let Ok(x1) = line[0].parse::<i32>() {
                if let Ok(y1) = line[1].parse::<i32>() {
                    if let Ok(x2) = line[2].parse::<i32>() {
                        if let Ok(y2) = line[3].parse::<i32>() {
                            return Some(InputData::Line(geometry::Line::<I>::from([
                                cast::<i32, I>(x1),
                                cast::<i32, I>(y1),
                                cast::<i32, I>(x2),
                                cast::<i32, I>(y2),
                            ])));
                        } else {
                            println!("failed to parse {}, ignoring line", line[3]);
                        }
                    } else {
                        println!("failed to parse {}, ignoring line", line[2]);
                    }
                } else {
                    println!("failed to parse {}, ignoring line", line[1]);
                }
            } else {
                println!("failed to parse {}, ignoring line", line[0]);
            }
        }
        _ => println!("failed to parse {line:?}, ignoring line"),
    }
    None
}

/// Reads an example file in the file format used by C++ boost voronoi:
///
/// `[number of points]`<br>
/// `[X] [Y] (repeats)`<br>
/// `[number of lines]`<br>
/// `[X1] [Y1] [X2] [Y2](repeats)`
// This entire module is implemented in about 20 lines of code in C++ boost :/
#[allow(clippy::type_complexity)]
pub fn read_boost_input_file<I: crate::InputType>(
    filename: &Path,
) -> Result<(Vec<geometry::Point<I>>, Vec<geometry::Line<I>>), BvError> {
    if !filename.is_file() {
        return Err(BvError::ValueError(format!("{filename:?} not a file")));
    }
    // Open the file in read-only mode
    let file = File::open(filename)?;
    let reader = BufReader::new(file);
    read_boost_input_buffer::<I, _>(reader)
}

/// Reads an example from a buffer using the format used by C++ boost voronoi:
///
/// `[number of points]`<br>
/// `[X] [Y] (repeats)`<br>
/// `[number of lines]`<br>
/// `[X1] [Y1] [X2] [Y2](repeats)`
#[allow(clippy::type_complexity)]
pub fn read_boost_input_buffer<I: crate::InputType, F: std::io::Read>(
    reader: BufReader<F>,
) -> Result<(Vec<geometry::Point<I>>, Vec<geometry::Line<I>>), BvError> {
    let mut points = Vec::<geometry::Point<I>>::default();
    let mut lines = Vec::<geometry::Line<I>>::default();
    let mut state = StateMachine::StartingPoints;
    let mut expected_points = 0;
    let mut expected_lines = 0;

    // Read the file line by line using the lines() iterator from std::io::BufRead.
    for (index, line) in reader.lines().enumerate() {
        if let Ok(line) = line {
            //println!("got {:?}, state:{:?}", line, state);
            if let Some(data) = line_to_data::<I>(&line) {
                if state == StateMachine::StartingPoints {
                    if let InputData::Number(n) = data {
                        expected_points = n;
                        if expected_points == 0 {
                            state = StateMachine::StartingLines
                        } else {
                            state = StateMachine::ExpectPoints
                        }
                        continue;
                    } else {
                        println!(
                            "#{}: can't read line {}. state:{:?} ignoring line",
                            index + 1,
                            line,
                            state
                        );
                        break;
                    }
                } else if state == StateMachine::ExpectPoints {
                    if expected_points as usize > points.len() {
                        if let InputData::Point(geometry::Point::<I> { x, y }) = data {
                            points.push(geometry::Point { x, y });
                            if expected_points as usize == points.len() {
                                state = StateMachine::StartingLines;
                            }
                        } else {
                            println!(
                                "#{}: can't read line {}. state:{:?} ignoring line",
                                index + 1,
                                line,
                                state
                            );
                            break;
                        }
                        continue;
                    } else {
                        println!(
                            "#{}: Got too many points {}. state:{:?} ignoring it",
                            index + 1,
                            line,
                            state
                        );
                        break;
                    }
                } else if state == StateMachine::StartingLines {
                    if let InputData::Number(n) = data {
                        expected_lines = n;
                        state = StateMachine::ExpectLines;
                        continue;
                    } else {
                        println!(
                            "#{}: can't read line {}. state:{:?} ignoring line",
                            index + 1,
                            line,
                            state
                        );
                        break;
                    }
                } else if state == StateMachine::ExpectLines {
                    if expected_lines as usize > lines.len() {
                        if let InputData::Line(a_line) = data {
                            lines.push(a_line);
                            if expected_lines as usize == lines.len() {
                                break;
                            }
                            continue;
                        } else {
                            println!(
                                "#{}: can't read line {} state:{:?} ignoring line",
                                index + 1,
                                line,
                                state
                            );
                            break;
                        }
                    } else {
                        println!(
                            "#{}: Got too many lines {}. state:{:?} ignoring line",
                            index + 1,
                            line,
                            state
                        );
                        break;
                    }
                }
            };
            println!(
                "#{}: can't parse line {}. state:{:?} ignoring line",
                index + 1,
                line,
                state
            );
            break;
        }
    }
    if (expected_lines as usize) < lines.len() {
        println!(
            "Expected to get {} lines, got {}",
            expected_lines,
            lines.len()
        );
    }
    if (expected_points as usize) < points.len() {
        println!(
            "Expected to get {} points, got {}",
            expected_points,
            points.len()
        );
    }
    Ok((points, lines))
}
