// Copyright 2019 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.

mod constants;
mod fake_font_info_loader;
mod font_catalog;
mod font_db;
mod font_pkgs;
mod font_sets;
mod generator;
mod merge;
mod serde_ext;

pub(crate) use crate::font_catalog::FontCatalog;
pub(crate) use crate::font_pkgs::FontPackageListing;
pub(crate) use crate::font_sets::{FontSet, FontSets};

use {
    failure::Error,
    fake_font_info_loader::FakeFontInfoLoaderImpl,
    font_info::{FontAssetSource, FontInfo, FontInfoLoader, FontInfoLoaderImpl},
    manifest::{v2::FontsManifest, FontManifestWrapper},
    std::{
        convert::TryInto,
        env, fs,
        io::{self, Write},
        path::PathBuf,
    },
    structopt::StructOpt,
};

#[derive(Debug, StructOpt)]
#[structopt(name = "Font Manifest Generator")]
struct Args {
    #[structopt(
        long = "empty",
        short = "e",
        raw(
            conflicts_with_all = r#"&["all_fonts", "local_fonts", "font_pkgs", "font_catalog", "font_dir", "fake_code_points"]"#
        ),
        help = "If true, generates an empty manifest"
    )]
    empty: bool,

    #[structopt(
        long = "all-fonts",
        short = "a",
        required_unless = "empty",
        value_name = "FILE",
        help = "Path to local_fonts.json file containing list of all font file names for the target product, generated by GN."
    )]
    all_fonts: Option<PathBuf>,

    #[structopt(
        long = "local-fonts",
        short = "l",
        required_unless = "empty",
        value_name = "FILE",
        help = "Path to local_fonts.json file containing list of *local* font file names for the target product, generated by GN."
    )]
    local_fonts: Option<PathBuf>,

    #[structopt(
        long = "font-pkgs",
        short = "p",
        required_unless = "empty",
        value_name = "FILE",
        help = "Paths to .font_pkgs.json files, each containing a list of all font files and Fuchsia package names available in a CIPD fonts repo, generated by LUCI. There should be one file for each CIPD fonts repo used for the target product."
    )]
    font_pkgs: Vec<PathBuf>,

    #[structopt(
        long = "font-catalog",
        short = "i",
        required_unless = "empty",
        value_name = "FILE",
        help = "Paths to .font_catalog.json files, each containing an index of all font families, assets, and typefaces available in a CIPD fonts repo. This file is maintained by hand. There should be one file for each CIPD fonts repo used for the target product."
    )]
    font_catalog: Vec<PathBuf>,

    #[structopt(
        long = "font-dir",
        short = "d",
        value_name = "DIR",
        required_unless = "empty",
        help = "Path to the base directory containing all font files checked out from CIPD. Usually, this will end in \"prebuilt/third_party/fonts\"."
    )]
    font_dir: Option<PathBuf>,

    #[structopt(
        long = "fake-code-points",
        short = "f",
        help = "If true, will write fake code points instead of trying to read actual font files."
    )]
    fake_code_points: bool,

    #[structopt(long = "pretty-print", help = "If true, indents output JSON.")]
    pretty_print: bool,

    #[structopt(
        long = "output",
        short = "o",
        value_name = "FILE",
        help = "Optional path to output file ending in .font_manifest.json. If omitted, writes to STDOUT."
    )]
    output: Option<PathBuf>,
}

/// Required because Rust can't create trait objects (`dyn FontInfoLoader`) with generic methods.
enum FontInfoLoaderType {
    Real(FontInfoLoaderImpl),
    Fake(FakeFontInfoLoaderImpl),
}

impl FontInfoLoader for FontInfoLoaderType {
    fn load_font_info<S, E>(&self, source: S, index: u32) -> Result<FontInfo, Error>
    where
        S: Sized + TryInto<FontAssetSource, Error = E>,
        E: Sized + Sync + Send + Into<Error>,
    {
        match self {
            FontInfoLoaderType::Real(loader) => loader.load_font_info(source, index),
            FontInfoLoaderType::Fake(loader) => loader.load_font_info(source, index),
        }
    }
}

fn main() -> Result<(), Error> {
    env::set_var("RUST_BACKTRACE", "full");

    let args: Args = Args::from_args();

    let manifest = if args.empty {
        FontManifestWrapper::Version2(FontsManifest { families: vec![] })
    } else {
        let font_sets = FontSets::load_from_all_and_local_paths(
            args.all_fonts.unwrap(),
            args.local_fonts.unwrap(),
        )?;
        let font_pkgs = FontPackageListing::load_from_paths(args.font_pkgs)?;
        let font_catalog = FontCatalog::load_from_paths(args.font_catalog)?;

        let font_info_loader = if args.fake_code_points {
            FontInfoLoaderType::Fake(FakeFontInfoLoaderImpl::new())
        } else {
            FontInfoLoaderType::Real(FontInfoLoaderImpl::new()?)
        };

        generator::generate_manifest(
            font_catalog,
            font_pkgs,
            font_sets,
            font_info_loader,
            args.font_dir.unwrap(),
        )?
    };

    let manifest_str = if args.pretty_print {
        serde_json::to_string_pretty(&manifest)
    } else {
        serde_json::to_string(&manifest)
    }?;

    if let Some(output_path) = args.output {
        fs::write(output_path, manifest_str.as_bytes())?;
    } else {
        io::stdout().write_all(manifest_str.as_bytes())?;
    }

    Ok(())
}
