/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use geom::{Point2D, Rect, Size2D};
use std::borrow::ToOwned;
use std::mem;
use std::slice;
use std::rc::Rc;
use std::cell::RefCell;
use util::cache::HashCache;
use util::smallvec::SmallVec8;
use style::computed_values::{font_stretch, font_variant, font_weight};
use style::properties::style_structs::Font as FontStyle;
use std::sync::Arc;

use platform::font_context::FontContextHandle;
use platform::font::{FontHandle, FontTable};
use util::geometry::Au;
use text::glyph::{GlyphStore, GlyphId};
use text::shaping::ShaperMethods;
use text::Shaper;
use font_template::FontTemplateDescriptor;
use platform::font_template::FontTemplateData;

// FontHandle encapsulates access to the platform's font API,
// e.g. quartz, FreeType. It provides access to metrics and tables
// needed by the text shaper as well as access to the underlying font
// resources needed by the graphics layer to draw glyphs.

pub trait FontHandleMethods {
    fn new_from_template(fctx: &FontContextHandle, template: Arc<FontTemplateData>, pt_size: Option<Au>)
                    -> Result<Self,()>;
    fn template(&self) -> Arc<FontTemplateData>;
    fn family_name(&self) -> String;
    fn face_name(&self) -> String;
    fn is_italic(&self) -> bool;
    fn boldness(&self) -> font_weight::T;
    fn stretchiness(&self) -> font_stretch::T;

    fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
    fn glyph_h_advance(&self, GlyphId) -> Option<FractionalPixel>;
    fn glyph_h_kerning(&self, GlyphId, GlyphId) -> FractionalPixel;
    fn metrics(&self) -> FontMetrics;
    fn get_table_for_tag(&self, FontTableTag) -> Option<FontTable>;
}

// Used to abstract over the shaper's choice of fixed int representation.
pub type FractionalPixel = f64;

pub type FontTableTag = u32;

pub trait FontTableTagConversions {
    fn tag_to_str(&self) -> String;
}

impl FontTableTagConversions for FontTableTag {
    fn tag_to_str(&self) -> String {
        unsafe {
            let pointer = mem::transmute::<&u32, *const u8>(self);
            let mut bytes = slice::from_raw_parts(pointer, 4).to_vec();
            bytes.reverse();
            String::from_utf8_unchecked(bytes)
        }
    }
}

pub trait FontTableMethods {
    fn with_buffer<F>(&self, F) where F: FnOnce(*const u8, usize);
}

#[derive(Clone, Debug)]
pub struct FontMetrics {
    pub underline_size:   Au,
    pub underline_offset: Au,
    pub strikeout_size:   Au,
    pub strikeout_offset: Au,
    pub leading:          Au,
    pub x_height:         Au,
    pub em_size:          Au,
    pub ascent:           Au,
    pub descent:          Au,
    pub max_advance:      Au,
    pub average_advance:  Au,
    pub line_gap:         Au,
}

pub type SpecifiedFontStyle = FontStyle;
pub type UsedFontStyle = FontStyle;

pub struct Font {
    pub handle: FontHandle,
    pub metrics: FontMetrics,
    pub variant: font_variant::T,
    pub descriptor: FontTemplateDescriptor,
    pub requested_pt_size: Au,
    pub actual_pt_size: Au,
    pub shaper: Option<Shaper>,
    pub shape_cache: HashCache<ShapeCacheEntry,Arc<GlyphStore>>,
    pub glyph_advance_cache: HashCache<u32,FractionalPixel>,
}

bitflags! {
    flags ShapingFlags: u8 {
        #[doc="Set if the text is entirely whitespace."]
        const IS_WHITESPACE_SHAPING_FLAG = 0x01,
        #[doc="Set if we are to ignore ligatures."]
        const IGNORE_LIGATURES_SHAPING_FLAG = 0x02,
        #[doc="Set if we are to disable kerning."]
        const DISABLE_KERNING_SHAPING_FLAG = 0x04
    }
}

/// Various options that control text shaping.
#[derive(Clone, Eq, PartialEq, Hash, Copy)]
pub struct ShapingOptions {
    /// Spacing to add between each letter. Corresponds to the CSS 2.1 `letter-spacing` property.
    /// NB: You will probably want to set the `IGNORE_LIGATURES_SHAPING_FLAG` if this is non-null.
    pub letter_spacing: Option<Au>,
    /// Spacing to add between each word. Corresponds to the CSS 2.1 `word-spacing` property.
    pub word_spacing: Au,
    /// Various flags.
    pub flags: ShapingFlags,
}

/// An entry in the shape cache.
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct ShapeCacheEntry {
    text: String,
    options: ShapingOptions,
}

impl Font {
    pub fn shape_text(&mut self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> {
        self.make_shaper(options);

        //FIXME: find the equivalent of Equiv and the old ShapeCacheEntryRef
        let shaper = &self.shaper;
        let lookup_key = ShapeCacheEntry {
            text: text.to_owned(),
            options: options.clone(),
        };
        match self.shape_cache.find(&lookup_key) {
            None => {}
            Some(glyphs) => return glyphs.clone(),
        }

        let mut glyphs = GlyphStore::new(text.chars().count(),
                                         options.flags.contains(IS_WHITESPACE_SHAPING_FLAG));
        shaper.as_ref().unwrap().shape_text(text, options, &mut glyphs);

        let glyphs = Arc::new(glyphs);
        self.shape_cache.insert(ShapeCacheEntry {
            text: text.to_owned(),
            options: *options,
        }, glyphs.clone());
        glyphs
    }

    fn make_shaper<'a>(&'a mut self, options: &ShapingOptions) -> &'a Shaper {
        // fast path: already created a shaper
        match self.shaper {
            Some(ref mut shaper) => {
                shaper.set_options(options);
                return shaper
            },
            None => {}
        }

        let shaper = Shaper::new(self, options);
        self.shaper = Some(shaper);
        self.shaper.as_ref().unwrap()
    }

    pub fn get_table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
        let result = self.handle.get_table_for_tag(tag);
        let status = if result.is_some() { "Found" } else { "Didn't find" };

        debug!("{} font table[{}] with family={}, face={}",
               status, tag.tag_to_str(),
               self.handle.family_name(), self.handle.face_name());

        return result;
    }

    #[inline]
    pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
        let codepoint = match self.variant {
            font_variant::T::small_caps => codepoint.to_uppercase().next().unwrap(), //FIXME: #5938
            font_variant::T::normal => codepoint,
        };
        self.handle.glyph_index(codepoint)
    }

    pub fn glyph_h_kerning(&mut self, first_glyph: GlyphId, second_glyph: GlyphId)
                           -> FractionalPixel 


    pub fn glyph_h_advance(&mut self, glyph: GlyphId) -> FractionalPixel {
        let handle = &self.handle;
        self.glyph_advance_cache.find_or_create(&glyph, |glyph| {
            match handle.glyph_h_advance(*glyph) {
                Some(adv) => adv,
                None => 10f64 as FractionalPixel // FIXME: Need fallback strategy
            }
        })
    }
}

pub struct FontGroup {
    pub fonts: SmallVec8<Rc<RefCell<Font>>>,
}

impl FontGroup {
    pub fn new(fonts: SmallVec8<Rc<RefCell<Font>>>) -> FontGroup {
        FontGroup {
            fonts: fonts,
        }
    }
}

pub struct RunMetrics {
    // may be negative due to negative width (i.e., kerning of '.' in 'P.T.')
    pub advance_width: Au,
    pub ascent: Au, // nonzero
    pub descent: Au, // nonzero
    // this bounding box is relative to the left origin baseline.
    // so, bounding_box.position.y = -ascent
    pub bounding_box: Rect<Au>
}

impl RunMetrics {
    pub fn new(advance: Au, ascent: Au, descent: Au) -> RunMetrics {
        let bounds = Rect(Point2D(Au(0), -ascent),
                          Size2D(advance, ascent + descent));

        // TODO(Issue #125): support loose and tight bounding boxes; using the
        // ascent+descent and advance is sometimes too generous and
        // looking at actual glyph extents can yield a tighter box.

        RunMetrics {
            advance_width: advance,
            bounding_box: bounds,
            ascent: ascent,
            descent: descent,
        }
    }
}

{
        self.handle.glyph_h_kerning(first_glyph, second_glyph)
    }