mod app;
mod pipeline;
mod output;

use std::os::fd::AsFd;
use std::os::unix::io::AsRawFd;
use std::time::Duration;
use pipeline::Pipeline;
use output::{FileOutput, CommandOutput};
use libc::{pollfd, poll, POLLIN};

#[derive(Debug, Clone)]
pub struct Config {
    pub files: Vec<String>,
    pub execs: Vec<String>,
    pub min: f64,
    pub max: f64,
    pub step: f64,
    pub rate: Duration,
    pub target: Option<f64>,
    pub duration: Duration,
    pub anim: String,
    pub curve: String,
    pub no_ui: bool,
    pub width: u32,
    pub height: u32,
    pub initial: Option<f64>,
    pub anchor: String,
    pub slot: usize,
    pub gap: u32,
    pub label: Option<String>,
    pub persist: bool,
    pub vertical: bool,
    pub fill_color: u32,
    pub track_color: u32,
    pub bg_color: u32,
    pub text_color: u32,
    pub touchable: bool,
}

fn main() {
    let config = parse_args();
    ctrlc::set_handler(|| std::process::exit(0)).expect("ctrlc");

    let watch_file = config.files.iter().find_map(|f| {
        if f.contains('=') && f.contains('{') { None } else { Some(f.clone()) }
    });

    let mut outputs: Vec<Box<dyn output::Output>> = Vec::new();

    for entry in &config.files {
        if let Some((path, tpl)) = parse_file_entry(entry) {
            outputs.push(Box::new(FileOutput::new(path, Some(tpl), config.min, config.max, false)));
        } else {
            let is_watched = watch_file.as_deref() == Some(entry.as_str());
            outputs.push(Box::new(FileOutput::new(entry.clone(), None, config.min, config.max, is_watched)));
        }
    }
    for cmd in &config.execs {
        outputs.push(Box::new(CommandOutput::new(cmd.clone(), config.min, config.max)));
    }

    let mut pipeline = if outputs.is_empty() {
        Pipeline::new(config.min, config.max, config.step, config.rate,
                      Box::new(output::NoopOutput))
    } else if outputs.len() == 1 {
        Pipeline::new(config.min, config.max, config.step, config.rate,
                      outputs.pop().unwrap())
    } else {
        Pipeline::new(config.min, config.max, config.step, config.rate,
                      Box::new(output::MultiOutput { outputs }))
    };

    if let Some(path) = &watch_file {
        if let Ok(s) = std::fs::read_to_string(path) {
            if let Ok(v) = s.trim().parse::<f64>() {
                pipeline.current_value = v;
            }
        }
    }

    if let Some(init_val) = config.initial {
        pipeline.current_value = init_val.clamp(config.min, config.max);
    }

    if config.no_ui || config.target.is_some() {
        if let Some(target) = config.target {
            pipeline.run_animation(target, config.duration, config.anim);
        } else {
            eprintln!("headless 模式需要 --target 参数");
            std::process::exit(1);
        }
    } else {
        let conn = wayland_client::Connection::connect_to_env().expect("无法连接 Wayland");
        let (globals, mut eq) =
            wayland_client::globals::registry_queue_init::<app::Ticker>(&conn)
                .expect("无法初始化 Wayland");
        let qh = eq.handle();

        let mut ticker = app::Ticker::new(&globals, &qh, config.clone(), pipeline, watch_file);
        eq.roundtrip(&mut ticker).unwrap();
        ticker.process();
        eq.flush().unwrap();

        let wayland_fd = conn.as_fd().as_raw_fd();
        let inotify_fd = ticker.get_inotify_fd();

        loop {
            eq.dispatch_pending(&mut ticker).unwrap();
            ticker.process();
            eq.flush().unwrap();

            let mut fds = vec![
                pollfd { fd: wayland_fd, events: POLLIN, revents: 0 },
            ];
            if inotify_fd >= 0 {
                fds.push(pollfd { fd: inotify_fd, events: POLLIN, revents: 0 });
            }

            unsafe { poll(fds.as_mut_ptr(), fds.len() as libc::nfds_t, 16) };

            if fds[0].revents & POLLIN != 0 {
                eq.blocking_dispatch(&mut ticker).unwrap();
            }

            if inotify_fd >= 0 && fds.len() > 1 && fds[1].revents & POLLIN != 0 {
                ticker.handle_inotify_event();
            }
        }
    }
}

fn parse_file_entry(s: &str) -> Option<(String, String)> {
    if let Some((path, tpl)) = s.split_once('=') {
        if tpl.contains('{') && !path.is_empty() {
            return Some((path.to_string(), tpl.to_string()));
        }
    }
    None
}

fn parse_args() -> Config {
    let args: Vec<String> = std::env::args().collect();
    let mut config = Config {
        files: Vec::new(),
        execs: Vec::new(),
        min: 0.0, max: 100.0, step: 1.0,
        rate: Duration::from_millis(33), target: None, duration: Duration::from_millis(200),
        anim: "linear".to_string(), curve: "linear".to_string(), no_ui: false, width: 400, height: 32,
        initial: None,
        anchor: "top-center".to_string(),
        slot: 0, gap: 4,
        label: None, persist: false, vertical: false,
        touchable: true,
        fill_color: parse_color("#C0392B"),
        track_color: parse_color("#2A2A40"),
        bg_color: parse_color("#1C1C2C"),
        text_color: parse_color("#F0ECE4"),
    };

    let mut i = 1;
    while i < args.len() {
        let arg = args[i].as_str();
        let mut next = || -> Option<&str> {
            i += 1;
            args.get(i).map(|s| s.as_str())
        };

        match arg {
            "--file"       => { if let Some(v) = next() { config.files.push(v.to_string()); } }
            "--exec"       => { if let Some(v) = next() { config.execs.push(v.to_string()); } }
            "--min"        => { if let Some(v) = next() { config.min = v.parse().unwrap_or(0.0); } }
            "--max"        => { if let Some(v) = next() { config.max = v.parse().unwrap_or(100.0); } }
            "--step"       => { if let Some(v) = next() { config.step = v.parse().unwrap_or(1.0); } }
            "--rate"       => { if let Some(v) = next() { config.rate = Duration::from_millis(v.parse().unwrap_or(33)); } }
            "--target"     => { if let Some(v) = next() { config.target = Some(v.parse().unwrap_or(0.0)); } }
            "--duration"   => { if let Some(v) = next() { config.duration = Duration::from_millis(v.parse().unwrap_or(200)); } }
            "--anim"       => { if let Some(v) = next() { config.anim = v.to_string(); } }
            "--curve"      => { if let Some(v) = next() { config.curve = v.to_string(); } }
            "--initial"    => { if let Some(v) = next() { config.initial = Some(v.parse().unwrap_or(0.0)); } }
            "--no-ui"      => { config.no_ui = true; }
            "--no-touch"   => { config.touchable = false; }
            "--width"      => { if let Some(v) = next() { config.width = v.parse().unwrap_or(400); } }
            "--height"     => { if let Some(v) = next() { config.height = v.parse().unwrap_or(32); } }
            "--anchor"     => { if let Some(v) = next() { config.anchor = v.to_string(); } }
            "--slot"       => { if let Some(v) = next() { config.slot = v.parse().unwrap_or(0); } }
            "--gap"        => { if let Some(v) = next() { config.gap = v.parse().unwrap_or(4); } }
            "--label"      => { if let Some(v) = next() { config.label = Some(v.to_string()); } }
            "--persist"    => { config.persist = true; }
            "--vertical"   => { config.vertical = true; }
            "--fill-color" => { if let Some(v) = next() { config.fill_color = parse_color(v); } }
            "--track-color"=> { if let Some(v) = next() { config.track_color = parse_color(v); } }
            "--bg-color"   => { if let Some(v) = next() { config.bg_color = parse_color(v); } }
            "--text-color" => { if let Some(v) = next() { config.text_color = parse_color(v); } }
            other => {
                eprintln!("未知选项: {}", other);
                std::process::exit(1);
            }
        }
        i += 1;
    }

    if !config.execs.is_empty() && config.rate < Duration::from_millis(100) {
        config.rate = Duration::from_millis(100);
    }

    config
}

fn parse_color(s: &str) -> u32 {
    let hex = s.trim_start_matches('#');
    match hex.len() {
        6 => u32::from_str_radix(hex, 16).map(|v| 0xFF000000 | v).unwrap_or(0xFF000000),
        8 => u32::from_str_radix(hex, 16).unwrap_or(0xFF000000),
        _ => 0xFF000000,
    }
}
