#[allow(patterns_in_fns_without_body)]

use std::fs::OpenOptions;
use std::os::unix::fs::OpenOptionsExt;

use std::fs::File;
use std::io::Write;
use std::error::Error;

use std::str;


struct PrintJob {
    chars: Vec<u8>
}

struct ThermalPrinter {
    port: String,
    jobQueue: Vec<PrintJob>
}

impl PrintJob {
    fn new() -> PrintJob {
        let mut chars: Vec<u8> = vec![];
        PrintJob {
            chars: chars,
        }
    }
}

trait PrintJobBuilder {
    fn header(mut self, text: String) -> PrintJob;
    fn set_underline(mut self, active: bool) -> PrintJob;
    fn select_font(mut self, font: String) -> PrintJob;
    fn select_table(mut self, table: String) -> PrintJob;
    fn set_rot90(mut self, active: bool) -> PrintJob;
    fn print(mut self, to_print: String) -> PrintJob;
    fn feed(mut self, n: u32) -> PrintJob;
    fn cut(mut self) -> PrintJob;
    fn build(mut self) -> Option<PrintJob>;
}

impl PrintJobBuilder for PrintJob {
    fn header(mut self, text: String) -> PrintJob {
        // TODO big font
        // append text
        // self.chars.append()
        self
    }

    // select and deselct underline
    fn set_underline(mut self, active: bool) -> PrintJob {
        if active {
            self.chars.append(&mut vec![0x1b]);
            self.chars.append(&mut "-1".to_string().into_bytes());
        } else {
            self.chars.append(&mut vec![0x1b]);
            self.chars.append(&mut "-0".to_string().into_bytes());
        }
        self
    }

    // select and font
    fn select_font(mut self, font: String) -> PrintJob {
        self.chars.append(&mut vec![0x1b]);
        self.chars.append(&mut "M".to_string().into_bytes());
        self.chars.append(&mut font.to_string().into_bytes());
        self
    }

    // select and print table
    fn select_table(mut self, table: String) -> PrintJob {
        self.chars.append(&mut vec![0x1b]);
        self.chars.append(&mut "t".to_string().into_bytes());
        self.chars.append(&mut table.to_string().into_bytes());
        self
    }

    // rotate text 90 degrees
    fn set_rot90(mut self, active: bool) -> PrintJob {
        if active {
            self.chars.append(&mut vec![0x1b]);
            self.chars.append(&mut "V1".to_string().into_bytes());
        } else {
            self.chars.append(&mut vec![0x1b]);
            self.chars.append(&mut "V0".to_string().into_bytes());
        }
        self
    }

    fn print(mut self, to_print: String) -> PrintJob {
        self.chars.append(&mut to_print.to_string().into_bytes());
        self
    }

    // append n newlines
    fn feed(mut self, n: u32) -> PrintJob {
        for _ in 0..n {
            self.chars.append(&mut "\n".to_string().into_bytes())
        }
        self
    }


    // append 0x1b i to cut the paper
    fn cut(mut self) -> PrintJob {
        self.chars.append(&mut vec![0x1b, 0x69]);
        self
    }

    fn build(self) -> Option<PrintJob> {
        if self.chars.len() != 0 {
            return Some(PrintJob {
                chars: self.chars.clone()
            })
        } else {
            return None;
        }
    }

}

impl ThermalPrinter {
    fn new(port: String) -> ThermalPrinter {
        let mut jobQueue: Vec<PrintJob> = vec![];
        ThermalPrinter {
            port: port,
            jobQueue: jobQueue,
        }
    }

    fn set_port(&mut self, portaddr: String) {
        self.port = portaddr
    }

    fn get_port(&self) -> &str {
        &self.port
    }

    fn add_job(&mut self, job: PrintJob) {
        self.jobQueue.push(job);
    }

    fn print(&self) {
        let mut options = OpenOptions::new();
        let mut file = options.open("/dev/usb/lp0").unwrap();
        for job in &self.jobQueue {
            let s = match str::from_utf8(&*job.chars) {
                Ok(v) => match file.write(v.as_bytes()) {
                    Ok(..) => { 
                        file.flush().unwrap();
                    },
                    Err(e) => println!("{}", e),
                },
                Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
            };
        }
    }
}

fn main() {
    let mut printer = ThermalPrinter::new("/dev/usb/lp0".to_string());
    let pj = PrintJob::new()
        .header("".to_string())
        .print("Dies ist ein Test".to_string())
        .feed(10)
        .cut()
        .build();
    printer.add_job(pj.unwrap());
    printer.print();
}