// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#![warn(missing_docs)]

mod asset;
mod builder;
mod debug;
mod family;
mod inspect;
mod typeface;

use {
    self::{
        asset::AssetCollection,
        debug::{TypefaceRequestFormatter, TypefaceResponseFormatter},
        family::{FamilyOrAlias, FontFamily, TypefaceQueryOverrides},
        typeface::{Collection as TypefaceCollection, TypefaceInfoAndCharSet},
    },
    anyhow::{format_err, Context as _, Error},
    fidl::{self, endpoints::ServerEnd},
    fidl_fuchsia_fonts::{self as fonts, CacheMissPolicy},
    fidl_fuchsia_fonts_experimental as fonts_exp,
    fidl_fuchsia_fonts_ext::{
        FontFamilyInfoExt, RequestExt, TypefaceRequestExt, TypefaceResponseExt,
    },
    fidl_fuchsia_intl::LocaleId,
    fuchsia_async as fasync,
    fuchsia_component::server::{ServiceFs, ServiceObj},
    fuchsia_syslog::*,
    fuchsia_trace as trace,
    futures::prelude::*,
    itertools::Itertools,
    std::{collections::BTreeMap, iter, sync::Arc},
    unicase::UniCase,
};

pub use {
    asset::{AssetId, AssetLoader, AssetLoaderImpl},
    builder::FontServiceBuilder,
};

/// Get a field out of a `TypefaceRequest`'s `query` field as a reference, or returns early with a
/// `anyhow::Error` if the query is missing.
macro_rules! query_field {
    ($request:ident, $field:ident) => {
        $request.query.as_ref().ok_or(format_err!("Missing query"))?.$field.as_ref()
    };
}

pub enum ProviderRequestStream {
    Stable(fonts::ProviderRequestStream),
    Experimental(fonts_exp::ProviderRequestStream),
}

/// The result of a successful lookup of a font family by name. Combines a `FontFamily` and,
/// if the `FontFamilyAlias` that the client requested turned out to include
/// `TypefaceQueryOverrides` then also contains those overrides.
struct FontFamilyMatch<'a> {
    family: &'a FontFamily,
    overrides: Option<Arc<TypefaceQueryOverrides>>,
}

/// Maintains state and handles request streams for the font server.
#[derive(Debug)]
pub struct FontService<L>
where
    L: AssetLoader,
{
    assets: AssetCollection<L>,
    /// Maps the font family name from the manifest (`families[x].name`) to a FamilyOrAlias.
    families: BTreeMap<UniCase<String>, FamilyOrAlias>,
    fallback_collection: TypefaceCollection,
    /// Holds Inspect data about manifests, families, and the fallback collection.
    // TODO(fxbug.dev/84729)
    #[allow(unused)]
    inspect_data: inspect::ServiceInspectData,

    /// Indicates whether the service is running on an internal (non-production) Fuchsia build.
    is_internal_build: bool,
}

impl<L> FontService<L>
where
    L: AssetLoader,
{
    /// Resolves a font family or alias either to itself (if it's a family), or to the canonical
    /// family. If it's an alias and contains `TypefaceQueryOverrides`, then the resulting
    /// `FontFamilyMatch` will contain those overrides.
    fn resolve_alias<'a>(
        &'a self,
        family_or_alias: &'a FamilyOrAlias,
    ) -> Option<FontFamilyMatch<'a>> {
        match family_or_alias {
            FamilyOrAlias::Family(family) => Some(FontFamilyMatch { family, overrides: None }),
            FamilyOrAlias::Alias(name, overrides) => match self.families.get(name) {
                Some(FamilyOrAlias::Family(family)) => {
                    Some(FontFamilyMatch { family, overrides: overrides.clone() })
                }
                _ => None,
            },
        }
    }

    /// Get font family by name.
    fn match_family(&self, family_name: &UniCase<String>) -> Option<FontFamilyMatch<'_>> {
        self.resolve_alias(self.families.get(family_name)?)
    }

    /// Get all font families whose name contains the requested string
    fn match_families_substr(&self, family_name: String) -> impl Iterator<Item = &FontFamily> {
        self.families
            .iter()
            .filter_map(move |(key, value)| {
                // Note: This might not work for some non-Latin strings
                if key.as_ref().to_lowercase().contains(&family_name.to_lowercase()) {
                    return self.resolve_alias(value).map(|matched| matched.family);
                }
                None
            })
            .unique_by(|family| family.name.to_owned())
    }

    fn apply_query_overrides(
        mut request: fonts::TypefaceRequest,
        overrides: Arc<TypefaceQueryOverrides>,
    ) -> fonts::TypefaceRequest {
        match &mut request.query {
            Some(query) => {
                if overrides.has_style_overrides() {
                    // If query has no style at all, use the values from TypefaceQueryOverrides
                    match &mut query.style {
                        None => query.style = Some(overrides.style.clone().into()),
                        Some(style) => {
                            style.slant = style.slant.or(overrides.style.slant);
                            style.width = style.width.or(overrides.style.width);
                            style.weight = style.weight.or(overrides.style.weight);
                        }
                    }
                }

                if overrides.has_language_overrides() {
                    match &mut query.languages {
                        None => {
                            query.languages = Some(
                                overrides
                                    .languages
                                    .iter()
                                    .map(|lang| LocaleId { id: lang.to_owned() })
                                    .collect_vec(),
                            )
                        }
                        Some(_) => (),
                    }
                }
            }
            None => (),
        }
        request
    }

    async fn match_request(
        &self,
        request: fonts::TypefaceRequest,
    ) -> Result<fonts::TypefaceResponse, Error> {
        fx_log_debug!("match_request: {:?}", &TypefaceRequestFormatter(&request));

        let query_family = query_field!(request, family);
        let query_family_string =
            (&query_family).map(|family| family.name.clone()).unwrap_or_default();
        trace::duration!(
            "fonts",
            "service:match_request",
            "family" => &query_family_string[..]);
        // TODO(fxbug.dev/44328): If support for lazy trace args is added, include more query params, e.g.
        // code points.

        let matched_family: Option<FontFamilyMatch<'_>> =
            query_family.and_then(|family| self.match_family(&UniCase::new(family.name.clone())));

        let mut request = match &matched_family {
            Some(FontFamilyMatch { family: _, overrides: Some(overrides) }) => {
                Self::apply_query_overrides(request, overrides.clone())
            }
            _ => request,
        };

        let mut typeface = match &matched_family {
            Some(FontFamilyMatch { family, overrides: _ }) => {
                family.faces.match_request(&request)?
            }
            None => None,
        };

        // If an exact match wasn't found, but fallback families are allowed...
        if typeface.is_none() && !request.exact_family() {
            // If fallback_family is not specified by the client explicitly then copy it from
            // the matched font family.
            if query_field!(request, fallback_family).is_none() {
                if let Some(FontFamilyMatch { family, overrides: _ }) = matched_family {
                    request
                        .query
                        .as_mut()
                        .ok_or_else(|| format_err!("This should never happen"))?
                        .fallback_family = family.generic_family;
                }
            }
            typeface = self.fallback_collection.match_request(&request)?;
        }

        let typeface_response = match typeface {
            Some(typeface) => self
                .assets
                .get_asset(typeface.asset_id, request.cache_miss_policy())
                .await
                .and_then(|buffer| {
                    Ok(fonts::TypefaceResponse {
                        buffer: Some(buffer),
                        buffer_id: Some(typeface.asset_id.into()),
                        font_index: Some(typeface.font_index),
                        ..fonts::TypefaceResponse::EMPTY
                    })
                })
                .ok(),
            None => None,
        };

        if typeface_response.is_none() && self.is_internal_build {
            fx_log_warn!("Unfulfilled request {:?}", &TypefaceRequestFormatter(&request));
        }

        let typeface_response = typeface_response.unwrap_or(fonts::TypefaceResponse::EMPTY);

        fx_log_debug!("Response: {:?}", &TypefaceResponseFormatter(&typeface_response));

        // Note that not finding a typeface is not an error, as long as the query was legal.
        Ok(typeface_response)
    }

    fn get_family_info(&self, family_name: fonts::FamilyName) -> fonts::FontFamilyInfo {
        let family_name = UniCase::new(family_name.name);
        let family = self.match_family(&family_name);
        family.map_or(fonts::FontFamilyInfo::EMPTY, |FontFamilyMatch { family, overrides: _ }| {
            fonts::FontFamilyInfo {
                name: Some(fonts::FamilyName { name: family.name.clone() }),
                styles: Some(family.faces.get_styles().collect()),
                ..fonts::FontFamilyInfo::EMPTY
            }
        })
    }

    async fn get_typeface_by_id(
        &self,
        id: AssetId,
        policy: CacheMissPolicy,
    ) -> Result<fonts::TypefaceResponse, fonts_exp::Error> {
        match self.assets.get_asset(id, policy).await {
            Ok(buffer) => {
                let response = fonts::TypefaceResponse {
                    buffer: Some(buffer),
                    buffer_id: Some(id.into()),
                    font_index: None,
                    ..fonts::TypefaceResponse::EMPTY
                };
                Ok(response)
            }
            Err(e) => {
                let msg = e.to_string();
                if msg.starts_with("No asset found") {
                    return Err(fonts_exp::Error::NotFound);
                }
                Err(fonts_exp::Error::Internal)
            }
        }
    }

    fn get_typefaces_by_family(
        &self,
        family_name: fonts::FamilyName,
    ) -> Result<fonts_exp::TypefaceInfoResponse, fonts_exp::Error> {
        let family = self
            .match_family(&UniCase::new(family_name.name.clone()))
            .map(|matched| matched.family)
            .ok_or(fonts_exp::Error::NotFound)?;
        let faces = family.extract_faces().map_into().collect();
        let response = fonts_exp::TypefaceInfoResponse {
            results: Some(faces),
            ..fonts_exp::TypefaceInfoResponse::EMPTY
        };
        Ok(response)
    }

    /// Helper that runs the "match by family name" step of [`list_typefaces`].
    /// Returns a vector of all available font families whose name or alias equals (or contains, if
    /// the `MatchFamilyNameSubstring` flag is set) the name requested in `query`.
    /// If `query` or `query.family` is `None`, all families are matched.
    fn list_typefaces_match_families<'a>(
        &'a self,
        flags: fonts_exp::ListTypefacesFlags,
        request: &fonts_exp::ListTypefacesRequest,
    ) -> Box<dyn Iterator<Item = &FontFamily> + 'a> {
        match request.family.as_ref() {
            Some(fonts::FamilyName { name }) => {
                if flags.contains(fonts_exp::ListTypefacesFlags::MatchFamilyNameSubstring) {
                    Box::new(self.match_families_substr(name.clone()))
                } else {
                    match self.match_family(&UniCase::new(name.clone())) {
                        Some(matched) => Box::new(iter::once(matched.family)),
                        None => Box::new(iter::empty()),
                    }
                }
            }
            None => Box::new(self.families.iter().filter_map(move |(_, value)| match value {
                FamilyOrAlias::Family(family) => Some(family),
                FamilyOrAlias::Alias(_, _) => None,
            })),
        }
    }

    fn list_typefaces_inner(
        &self,
        request: fonts_exp::ListTypefacesRequest,
    ) -> Result<Vec<fonts_exp::TypefaceInfo>, fonts_exp::Error> {
        let flags = request.flags.unwrap_or(fonts_exp::ListTypefacesFlags::empty());

        let matched_families = self.list_typefaces_match_families(flags, &request);

        // Flatten matches into Iter<TypefaceInfoAndCharSet>
        let matched_faces = matched_families.flat_map(|family| family.extract_faces());

        let slant_predicate = |face: &TypefaceInfoAndCharSet| -> bool {
            match request.slant {
                Some(fonts_exp::SlantRange { lower, upper }) => {
                    // Unwrap is safe because manifest loading assigns default values if needed
                    (lower..=upper).contains(&face.style.slant.unwrap())
                }
                None => true,
            }
        };

        let weight_predicate = |face: &TypefaceInfoAndCharSet| -> bool {
            match request.weight {
                Some(fonts_exp::WeightRange { lower, upper }) => {
                    // Unwrap is safe because manifest loading assigns default values if needed
                    (lower..=upper).contains(&face.style.weight.unwrap())
                }
                None => true,
            }
        };

        let width_predicate = |face: &TypefaceInfoAndCharSet| -> bool {
            match request.width {
                Some(fonts_exp::WidthRange { lower, upper }) => {
                    // Unwrap is safe because manifest loading assigns default values if needed
                    (lower..=upper).contains(&face.style.width.unwrap())
                }
                None => true,
            }
        };

        let lang_predicate = |face: &TypefaceInfoAndCharSet| -> bool {
            match request.languages.as_ref() {
                // This is O(face_langs.len() * fonts.MAX_FACE_QUERY_LANGUAGES). As of 06/2019,
                // MAX_FACE_QUERY_LANGAUGES == 8. face_langs.len() *should* be small as well.
                Some(langs) => langs.iter().all(|lang| face.languages.contains(&lang)),
                None => true,
            }
        };

        let code_point_predicate = |face: &TypefaceInfoAndCharSet| -> bool {
            match request.code_points.as_ref() {
                Some(points) => points.iter().all(|point| face.char_set.contains(*point)),
                None => true,
            }
        };

        let generic_family_predicate = |face: &TypefaceInfoAndCharSet| -> bool {
            match request.generic_family.as_ref() {
                Some(generic_family) => {
                    face.generic_family.map_or(false, |gf| generic_family == &gf)
                }
                None => true,
            }
        };

        let total_predicate = |face: &TypefaceInfoAndCharSet| -> bool {
            slant_predicate(face)
                && weight_predicate(face)
                && width_predicate(face)
                && lang_predicate(face)
                && code_point_predicate(face)
                && generic_family_predicate(face)
        };

        // Filter
        let matched_faces = matched_faces.filter(total_predicate).map_into().collect();

        Ok(matched_faces)
    }

    fn list_typefaces(
        &self,
        request: fonts_exp::ListTypefacesRequest,
        iterator: ServerEnd<fonts_exp::ListTypefacesIteratorMarker>,
    ) -> Result<(), fonts_exp::Error> {
        let mut results = self.list_typefaces_inner(request)?;

        fasync::Task::spawn(
            async move {
                let mut stream = iterator.into_stream()?;
                while let Some(request) = stream.try_next().await? {
                    match request {
                        fonts_exp::ListTypefacesIteratorRequest::GetNext { responder } => {
                            let split_at =
                                (fonts_exp::MAX_TYPEFACE_RESULTS as usize).min(results.len());
                            // Return results in order
                            let chunk = results.drain(..split_at).collect_vec();
                            let response = fonts_exp::TypefaceInfoResponse {
                                results: Some(chunk),
                                ..fonts_exp::TypefaceInfoResponse::EMPTY
                            };
                            responder.send(response)?;
                        }
                    }
                }
                Ok(())
            }
            .unwrap_or_else(|e: Error| {
                fx_log_err!("Error while running ListTypefacesIterator: {:?}", e)
            }),
        )
        .detach();

        Ok(())
    }

    async fn handle_font_provider_request(
        &self,
        request: fonts::ProviderRequest,
    ) -> Result<(), Error> {
        use fonts::ProviderRequest::*;

        match request {
            // TODO(fxbug.dev/8903): Remove when all clients have migrated to GetTypeface
            GetFont { request, responder } => {
                let request = request.into_typeface_request();
                let mut response = self.match_request(request).await?.into_font_response();
                Ok(responder.send(response.as_mut())?)
            }
            // TODO(fxbug.dev/8903): Remove when all clients have migrated to GetFontFamilyInfo
            GetFamilyInfo { family, responder } => {
                let mut font_info =
                    self.get_family_info(fonts::FamilyName { name: family }).into_family_info();
                Ok(responder.send(font_info.as_mut())?)
            }
            GetTypeface { request, responder } => {
                let response = self.match_request(request).await?;
                Ok(responder.send(response)?)
            }
            GetFontFamilyInfo { family, responder } => {
                let family_info = self.get_family_info(family);
                Ok(responder.send(family_info)?)
            }
            // TODO(fxbug.dev/34897): Implement font event dispatch
            RegisterFontSetEventListener { listener: _, responder: _ } => unimplemented!(),
        }
    }

    async fn handle_experimental_request(
        &self,
        request: fonts_exp::ProviderRequest,
    ) -> Result<(), Error> {
        use fonts_exp::ProviderRequest::*;

        match request {
            GetTypefaceById { id, responder } => {
                let mut response = self
                    .get_typeface_by_id(AssetId(id), CacheMissPolicy::BlockUntilDownloaded)
                    .await;
                Ok(responder.send(&mut response)?)
            }
            GetTypefacesByFamily { family, responder } => {
                let mut response = self.get_typefaces_by_family(family);
                Ok(responder.send(&mut response)?)
            }
            ListTypefaces { request, iterator, responder } => {
                let mut response = self.list_typefaces(request, iterator);
                Ok(responder.send(&mut response)?)
            }
        }
    }

    pub async fn run(self, fs: ServiceFs<ServiceObj<'static, ProviderRequestStream>>) {
        let self_ = Arc::new(self);
        fs.for_each_concurrent(None, move |stream| self_.clone().handle_stream(stream)).await;
    }

    async fn handle_stream(self: Arc<Self>, stream: ProviderRequestStream) {
        let self_ = self.clone();
        match stream {
            ProviderRequestStream::Stable(stream) => {
                self_.as_ref().handle_stream_stable(stream).await.unwrap_or_default()
            }
            ProviderRequestStream::Experimental(stream) => {
                self_.as_ref().handle_stream_experimental(stream).await.unwrap_or_default()
            }
        }
    }

    async fn handle_stream_stable(
        &self,
        mut stream: fonts::ProviderRequestStream,
    ) -> Result<(), Error> {
        while let Some(request) = stream.try_next().await.context("Error running provider")? {
            self.handle_font_provider_request(request)
                .await
                .context("Error while handling font provider request")
                .map_err(|err| {
                    fx_log_err!("{:?}", err);
                    err
                })?;
        }
        Ok(())
    }

    async fn handle_stream_experimental(
        &self,
        mut stream: fonts_exp::ProviderRequestStream,
    ) -> Result<(), Error> {
        while let Some(request) = stream.try_next().await.context("Error running provider")? {
            self.handle_experimental_request(request)
                .await
                .context("Error while handling experimental font provider request")
                .map_err(|err| {
                    fx_log_err!("{:?}", err);
                    err
                })?;
        }
        Ok(())
    }
}
