// Copyright 2015 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use base::CGFloat;
use color_space::CGColorSpace;
use core_foundation::base::{ToVoid, CFRelease, CFRetain, CFTypeID};
use font::{CGFont, CGGlyph};
use geometry::CGPoint;
use color::CGColor;
use path::CGPathRef;
use libc::{c_int, size_t};
use std::os::raw::c_void;

use std::cmp;
use std::ptr;
use std::slice;
use geometry::{CGAffineTransform, CGRect};
use image::CGImage;
use foreign_types::{ForeignType, ForeignTypeRef};

#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub enum CGBlendMode {
    Normal = 0,
    Multiply,
    Screen,
    Overlay,
    Darken,
    Lighten,
    ColorDodge,
    ColorBurn,
    SoftLight,
    HardLight,
    Difference,
    Exclusion,
    Hue,
    Saturation,
    Color,
    Luminosity,
    // 10.5 and up:
    Clear,
    Copy,
    SourceIn,
    SourceOut,
    SourceAtop,
    DestinationOver,
    DestinationIn,
    DestinationOut,
    DestinationAtop,
    Xor,
    PlusDarker,
    PlusLighter,
}

#[repr(C)]
pub enum CGTextDrawingMode {
    CGTextFill,
    CGTextStroke,
    CGTextFillStroke,
    CGTextInvisible,
    CGTextFillClip,
    CGTextStrokeClip,
    CGTextClip
}

foreign_type! {
    #[doc(hidden)]
    type CType = ::sys::CGContext;
    fn drop = |cs| CFRelease(cs as *mut _);
    fn clone = |p| CFRetain(p as *const _) as *mut _;
    pub struct CGContext;
    pub struct CGContextRef;
}

impl CGContext {
    pub fn type_id() -> CFTypeID {
        unsafe {
            CGContextGetTypeID()
        }
    }

    pub fn create_bitmap_context(data: Option<*mut c_void>,
                                 width: size_t,
                                 height: size_t,
                                 bits_per_component: size_t,
                                 bytes_per_row: size_t,
                                 space: &CGColorSpace,
                                 bitmap_info: u32)
                                 -> CGContext {
        unsafe {
            let result = CGBitmapContextCreate(data.unwrap_or(ptr::null_mut()),
                                               width,
                                               height,
                                               bits_per_component,
                                               bytes_per_row,
                                               space.as_ptr(),
                                               bitmap_info);
            assert!(!result.is_null());
            Self::from_ptr(result)
        }
    }

    pub fn data(&mut self) -> &mut [u8] {
        unsafe {
            slice::from_raw_parts_mut(
                    CGBitmapContextGetData(self.as_ptr()) as *mut u8,
                    (self.height() * self.bytes_per_row()) as usize)
        }
    }
}

impl CGContextRef {
    pub fn flush(&self) {
        unsafe {
            CGContextFlush(self.as_ptr())
        }
    }

    pub fn width(&self) -> size_t {
        unsafe {
            CGBitmapContextGetWidth(self.as_ptr())
        }
    }

    pub fn height(&self) -> size_t {
        unsafe {
            CGBitmapContextGetHeight(self.as_ptr())
        }
    }

    pub fn bytes_per_row(&self) -> size_t {
        unsafe {
            CGBitmapContextGetBytesPerRow(self.as_ptr())
        }
    }

    pub fn set_fill_color(&self, color: &CGColor) {
        unsafe {
            CGContextSetFillColorWithColor(self.as_ptr(), color.to_void());
        }
    }

    pub fn set_rgb_fill_color(&self, red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        unsafe {
            CGContextSetRGBFillColor(self.as_ptr(), red, green, blue, alpha)
        }
    }

    pub fn set_gray_fill_color(&self, gray: CGFloat, alpha: CGFloat) {
        unsafe {
            CGContextSetGrayFillColor(self.as_ptr(), gray, alpha)
        }
    }

    pub fn set_blend_mode(&self, blend_mode: CGBlendMode) {
        unsafe {
            CGContextSetBlendMode(self.as_ptr(), blend_mode)
        }
    }

    pub fn set_allows_font_smoothing(&self, allows_font_smoothing: bool) {
        unsafe {
            CGContextSetAllowsFontSmoothing(self.as_ptr(), allows_font_smoothing)
        }
    }

    pub fn set_font_smoothing_style(&self, style: i32) {
        unsafe {
            CGContextSetFontSmoothingStyle(self.as_ptr(), style as _);
        }
    }

    pub fn set_should_smooth_fonts(&self, should_smooth_fonts: bool) {
        unsafe {
            CGContextSetShouldSmoothFonts(self.as_ptr(), should_smooth_fonts)
        }
    }

    pub fn set_allows_antialiasing(&self, allows_antialiasing: bool) {
        unsafe {
            CGContextSetAllowsAntialiasing(self.as_ptr(), allows_antialiasing)
        }
    }

    pub fn set_should_antialias(&self, should_antialias: bool) {
        unsafe {
            CGContextSetShouldAntialias(self.as_ptr(), should_antialias)
        }
    }

    pub fn set_allows_font_subpixel_quantization(&self, allows_font_subpixel_quantization: bool) {
        unsafe {
            CGContextSetAllowsFontSubpixelQuantization(self.as_ptr(), allows_font_subpixel_quantization)
        }
    }

    pub fn set_should_subpixel_quantize_fonts(&self, should_subpixel_quantize_fonts: bool) {
        unsafe {
            CGContextSetShouldSubpixelQuantizeFonts(self.as_ptr(), should_subpixel_quantize_fonts)
        }
    }

    pub fn set_allows_font_subpixel_positioning(&self, allows_font_subpixel_positioning: bool) {
        unsafe {
            CGContextSetAllowsFontSubpixelPositioning(self.as_ptr(), allows_font_subpixel_positioning)
        }
    }

    pub fn set_should_subpixel_position_fonts(&self, should_subpixel_position_fonts: bool) {
        unsafe {
            CGContextSetShouldSubpixelPositionFonts(self.as_ptr(), should_subpixel_position_fonts)
        }
    }

    pub fn set_text_drawing_mode(&self, mode: CGTextDrawingMode) {
        unsafe {
            CGContextSetTextDrawingMode(self.as_ptr(), mode)
        }
    }

    pub fn add_path(&self, path: &CGPathRef) {
        unsafe {
            CGContextAddPath(self.as_ptr(), path.as_ptr());
        }
    }

    pub fn close_path(&self) {
        unsafe {
            CGContextClosePath(self.as_ptr());
        }
    }

    pub fn fill_path(&self) {
        unsafe {
            CGContextFillPath(self.as_ptr());
        }
    }

    pub fn fill_rect(&self, rect: CGRect) {
        unsafe {
            CGContextFillRect(self.as_ptr(), rect)
        }
    }

    pub fn draw_image(&self, rect: CGRect, image: &CGImage) {
        unsafe {
            CGContextDrawImage(self.as_ptr(), rect, image.as_ptr());
        }
    }

    pub fn create_image(&self) -> Option<CGImage> {
        let image = unsafe { CGBitmapContextCreateImage(self.as_ptr()) };
        if !image.is_null() {
            Some(unsafe { CGImage::from_ptr(image) })
        } else {
            None
        }
    }

    pub fn set_font(&self, font: &CGFont) {
        unsafe {
            CGContextSetFont(self.as_ptr(), font.as_ptr())
        }
    }

    pub fn set_font_size(&self, size: CGFloat) {
        unsafe {
            CGContextSetFontSize(self.as_ptr(), size)
        }
    }

    pub fn set_text_matrix(&self, t: &CGAffineTransform) {
        unsafe {
            CGContextSetTextMatrix(self.as_ptr(), *t)
        }
    }

    pub fn show_glyphs_at_positions(&self, glyphs: &[CGGlyph], positions: &[CGPoint]) {
        unsafe {
            let count = cmp::min(glyphs.len(), positions.len());
            CGContextShowGlyphsAtPositions(self.as_ptr(),
                                           glyphs.as_ptr(),
                                           positions.as_ptr(),
                                           count)
        }
    }

    pub fn save(&self) {
        unsafe {
            CGContextSaveGState(self.as_ptr());
        }
    }

    pub fn restore(&self) {
        unsafe {
            CGContextRestoreGState(self.as_ptr());
        }
    }

    pub fn translate(&self, tx: CGFloat, ty: CGFloat) {
        unsafe {
            CGContextTranslateCTM(self.as_ptr(), tx, ty);
        }
    }

    pub fn scale(&self, sx: CGFloat, sy: CGFloat) {
        unsafe {
            CGContextScaleCTM(self.as_ptr(), sx, sy);
        }
    }
}

#[test]
fn create_bitmap_context_test() {
    use geometry::*;

    let cs = CGColorSpace::create_device_rgb();
    let ctx = CGContext::create_bitmap_context(None,
                                16, 8,
                                8, 0,
                                &cs,
                                ::base::kCGImageAlphaPremultipliedLast);
    ctx.set_rgb_fill_color(1.,0.,1.,1.);
    ctx.fill_rect(CGRect::new(&CGPoint::new(0.,0.), &CGSize::new(8.,8.)));
    let img = ctx.create_image().unwrap();
    assert_eq!(16, img.width());
    assert_eq!(8, img.height());
    assert_eq!(8, img.bits_per_component());
    assert_eq!(32, img.bits_per_pixel());
    let data = img.data();
    assert_eq!(255, data.bytes()[0]);
    assert_eq!(0, data.bytes()[1]);
    assert_eq!(255, data.bytes()[2]);
    assert_eq!(255, data.bytes()[3]);
}

#[link(name = "CoreGraphics", kind = "framework")]
extern {
    fn CGBitmapContextCreate(data: *mut c_void,
                             width: size_t,
                             height: size_t,
                             bitsPerComponent: size_t,
                             bytesPerRow: size_t,
                             space: ::sys::CGColorSpaceRef,
                             bitmapInfo: u32)
                             -> ::sys::CGContextRef;
    fn CGBitmapContextGetData(context: ::sys::CGContextRef) -> *mut c_void;
    fn CGBitmapContextGetWidth(context: ::sys::CGContextRef) -> size_t;
    fn CGBitmapContextGetHeight(context: ::sys::CGContextRef) -> size_t;
    fn CGBitmapContextGetBytesPerRow(context: ::sys::CGContextRef) -> size_t;
    fn CGBitmapContextCreateImage(context: ::sys::CGContextRef) -> ::sys::CGImageRef;
    fn CGContextGetTypeID() -> CFTypeID;
    fn CGContextFlush(c: ::sys::CGContextRef);
    fn CGContextSetBlendMode(c: ::sys::CGContextRef, blendMode: CGBlendMode);
    fn CGContextSetAllowsFontSmoothing(c: ::sys::CGContextRef, allowsFontSmoothing: bool);
    fn CGContextSetShouldSmoothFonts(c: ::sys::CGContextRef, shouldSmoothFonts: bool);
    fn CGContextSetFontSmoothingStyle(c: ::sys::CGContextRef, style: c_int);
    fn CGContextSetAllowsAntialiasing(c: ::sys::CGContextRef, allowsAntialiasing: bool);
    fn CGContextSetShouldAntialias(c: ::sys::CGContextRef, shouldAntialias: bool);
    fn CGContextSetAllowsFontSubpixelQuantization(c: ::sys::CGContextRef,
                                                  allowsFontSubpixelQuantization: bool);
    fn CGContextSetShouldSubpixelQuantizeFonts(c: ::sys::CGContextRef,
                                               shouldSubpixelQuantizeFonts: bool);
    fn CGContextSetAllowsFontSubpixelPositioning(c: ::sys::CGContextRef,
                                                 allowsFontSubpixelPositioning: bool);
    fn CGContextSetShouldSubpixelPositionFonts(c: ::sys::CGContextRef,
                                               shouldSubpixelPositionFonts: bool);
    fn CGContextSetTextDrawingMode(c: ::sys::CGContextRef, mode: CGTextDrawingMode);
    fn CGContextSetFillColorWithColor(c: ::sys::CGContextRef, color: *const c_void);
    fn CGContextAddPath(c: ::sys::CGContextRef, path: ::sys::CGPathRef);
    fn CGContextClosePath(c: ::sys::CGContextRef);
    fn CGContextFillPath(c: ::sys::CGContextRef);
    fn CGContextSetRGBFillColor(context: ::sys::CGContextRef,
                                red: CGFloat,
                                green: CGFloat,
                                blue: CGFloat,
                                alpha: CGFloat);
    fn CGContextSetGrayFillColor(context: ::sys::CGContextRef, gray: CGFloat, alpha: CGFloat);
    fn CGContextFillRect(context: ::sys::CGContextRef,
                         rect: CGRect);
    fn CGContextDrawImage(c: ::sys::CGContextRef, rect: CGRect, image: ::sys::CGImageRef);
    fn CGContextSetFont(c: ::sys::CGContextRef, font: ::sys::CGFontRef);
    fn CGContextSetFontSize(c: ::sys::CGContextRef, size: CGFloat);
    fn CGContextSetTextMatrix(c: ::sys::CGContextRef, t: CGAffineTransform);
    fn CGContextShowGlyphsAtPositions(c: ::sys::CGContextRef,
                                      glyphs: *const CGGlyph,
                                      positions: *const CGPoint,
                                      count: size_t);

    fn CGContextSaveGState(c: ::sys::CGContextRef);
    fn CGContextRestoreGState(c: ::sys::CGContextRef);
    fn CGContextTranslateCTM(c: ::sys::CGContextRef, tx: CGFloat, ty: CGFloat);
    fn CGContextScaleCTM(c: ::sys::CGContextRef, sx: CGFloat, sy: CGFloat);
}

