extern crate cairo;
extern crate palette;
extern crate rand;

use rand::Rng;
use cairo::Gradient;

const WIDTH: i32 = 3840;
const HEIGHT: i32 = 2160;

fn cloud(ctx: &cairo::Context, rng: &mut rand::ThreadRng) {
    let x_scale = rng.gen::<f64>() * 2.0 + 1.0;
    let x = (rng.gen::<f64>() * WIDTH as f64) / x_scale;
    let y = rng.gen::<f64>() * HEIGHT as f64;
    let num_circles = 2 + (rng.gen::<isize>() % 2);
    let vec: Vec<(f64, f64)> = (0..num_circles).map(|_| {
        let width = 60.0 + rng.gen::<f64>() * 200.0;
        let dist = 30.0 + rng.gen::<f64>() * 30.0;
        (width, dist)
    }).collect();

    let surf = cairo::Context::new(&ctx.get_target().create_similar(cairo::Content::Alpha, WIDTH, HEIGHT));

    surf.save();
    let mut max_y = 0.0;
    let mut offset = x;
    surf.scale(x_scale, 1.0);
    surf.set_source_rgba(1.0, 1.0, 1.0, 1.0);
    for (width, dist) in vec {
        surf.arc(offset, y, width, 0.0, 3.14159 * 2.0);
        surf.fill();
        offset = offset + width + dist;
        max_y = if width > max_y { width } else { max_y };
    }
    surf.set_source_rgba(0.0, 0.0, 0.0, 0.0);
    surf.set_operator(cairo::Operator::Source);
    surf.rectangle(x - 260.0, y, offset + 260.0, max_y);
    surf.fill();
    surf.restore();

    surf.set_source_rgba(1.0, 1.0, 1.0, 0.8);
    ctx.mask(&cairo::SurfacePattern::create(&surf.get_target()));
}

fn shinedies(ctx: &cairo::Context, rng: &mut rand::ThreadRng) {
    let r = rng.gen::<f64>();
    for _ in 0..20 {
        let surf = cairo::Context::new(&ctx.get_target().create_similar(cairo::Content::Alpha, WIDTH, HEIGHT));

        surf.save();
        surf.scale(0.5, 0.5);
        surf.translate(rng.gen::<f64>() * WIDTH as f64 * 2.0, rng.gen::<f64>() * HEIGHT as f64 * 2.0);
        surf.rotate(r);
        surf.set_source_rgba(1.0, 1.0, 1.0, 1.0);
        let w = 300.0 + rng.gen::<f64>() * 1200.0;
        let r = rng.gen::<f64>() * 300.0;
        surf.arc(0.0, 0.0, r, 0.0, 3.14159 * 2.0);
        surf.fill();
        surf.arc(w, 0.0, r, 0.0, 3.14159 * 2.0);
        surf.fill();
        surf.rectangle(0.0, -r, w, r * 2.0);
        surf.fill();
        surf.restore();

        ctx.set_source_rgba(1.0, 1.0, 1.0, 0.3);
        ctx.mask(&cairo::SurfacePattern::create(&surf.get_target()));
    }
}

fn stars(ctx: &cairo::Context, rng: &mut rand::ThreadRng) {
    ctx.set_source_rgba(1.0, 1.0, 1.0, 0.8);
    for _ in 0..200 {
        let x = rng.gen::<f64>() * WIDTH as f64;
        let y = rng.gen::<f64>() * HEIGHT as f64;
        let s = 4.0 + rng.gen::<f64>() * 6.0 as f64;
        ctx.arc(x, y, s, 0.0, 3.14159 * 2.0);
        ctx.fill();
    }
}

fn get_sky_colors(rng: &mut rand::ThreadRng) -> (palette::Rgb, palette::Rgb) {
    use palette::{RgbHue, Hsv, Hue};
    let default_dark: Hsv = Hsv::from(palette::Rgb::new(0.188, 0.333, 0.51));
    let default_light: Hsv = Hsv::from(palette::Rgb::new(0.482, 0.749, 0.839));
    let shift = RgbHue::from_radians(rng.gen::<f32>() * 3.14159 * 2.0);
    (default_dark.shift_hue(shift).into(), default_light.shift_hue(shift).into())
}

fn main() {
    let surf = cairo::ImageSurface::create(
        cairo::Format::Rgb24,
        WIDTH,
        HEIGHT
    ).unwrap();

    let mut rng = rand::thread_rng();
    let ctx = cairo::Context::new(&surf);
    let w = rng.gen::<u8>() % 3;
    ctx.set_source_rgb(
        if w == 0 { 0.4 } else { 0.2 },
        if w == 1 { 0.4 } else { 0.2 },
        if w == 2 { 0.4 } else { 0.2 },
    );
    ctx.paint();

    let g = cairo::LinearGradient::new(
        0.0, 0.0, 0.0, HEIGHT as f64
    );
    let (dark, light) = get_sky_colors(&mut rng);
    g.add_color_stop_rgb(0.0, dark.red as f64, dark.green as f64, dark.blue as f64);
    g.add_color_stop_rgb(1.0, light.red as f64, light.green as f64, light.blue as f64);
    ctx.set_source(&g);
    ctx.paint();

    stars(&ctx, &mut rng);
    for _ in 0..10 {
        cloud(&ctx, &mut rng);
    }
    shinedies(&ctx, &mut rng);

    {
        let mut f = std::fs::File::create("image.png").unwrap();
        surf.write_to_png(&mut f).unwrap();
    }
}
