require 'flr/constant'

module Flr

  # 代码生成相关的工具类方法
  class CodeUtil

    @@should_support_nullsafety = nil

    def self.should_support_nullsafety
      if @@should_support_nullsafety != nil
        return @@should_support_nullsafety
      end

      # $ flutter --version
      # Flutter 1.12.13+hotfix.5 • channel stable • https://github.com/flutter/flutter.git
      # Framework • revision 27321ebbad (5 weeks ago) • 2019-12-10 18:15:01 -0800
      # Engine • revision 2994f7e1e6
      # Tools • Dart 2.7.0
      flutter_version_result = `flutter --version`
      if (flutter_version_result.nil? == true || flutter_version_result.empty? == true)
        @@should_support_nullsafety = false
        return @@should_support_nullsafety
      end

      dart_version_str = flutter_version_result.split("Dart")[1]
      dart_version_str.strip!

      if Version.new(dart_version_str) >= Version.new("2.12.0")
        @@should_support_nullsafety = true
        return @@should_support_nullsafety
      end

      @@should_support_nullsafety = false
      return @@should_support_nullsafety
    end

    # generate_R_class(package_name) -> string
    #
    # 根据模板生成 R class 的代码
    #
    def self.generate_R_class(package_name)
      code = <<-CODE
// IT IS GENERATED BY FLR - DO NOT MODIFY BY HAND
// YOU CAN GET MORE DETAILS ABOUT FLR FROM:
// - https://github.com/Fly-Mix/flr-cli
// - https://github.com/Fly-Mix/flr-vscode-extension
// - https://github.com/Fly-Mix/flr-as-plugin
//

// ignore: unused_import
import 'package:flutter/widgets.dart';
// ignore: unused_import
import 'package:flutter/services.dart' show rootBundle;
// ignore: unused_import
import 'package:path/path.dart' as path;
// ignore: unused_import
import 'package:flutter_svg/flutter_svg.dart';
// ignore: unused_import
import 'package:r_dart_library/asset_svg.dart';

/// This `R` class is generated and contains references to static asset resources.
class R {
  /// package name: #{package_name}
  static const package = "#{package_name}";

  /// This `R.image` struct is generated, and contains static references to static non-svg type image asset resources.
  static const image = _R_Image();

  /// This `R.svg` struct is generated, and contains static references to static svg type image asset resources.
  static const svg = _R_Svg();

  /// This `R.text` struct is generated, and contains static references to static text asset resources.
  static const text = _R_Text();

  /// This `R.fontFamily` struct is generated, and contains static references to static font asset resources.
  static const fontFamily = _R_FontFamily();
}
      CODE

      return code
    end


    # generate_AssetResource_class(package_name) -> string
    #
    # 根据模板生成 AssetResource class 的代码
    #
    def self.generate_AssetResource_class(package_name)
      code = <<-CODE
/// Asset resource’s metadata class.
/// For example, here is the metadata of `packages/flutter_demo/assets/images/example.png` asset:
/// - packageName：flutter_demo
/// - assetName：assets/images/example.png
/// - fileDirname：assets/images
/// - fileBasename：example.png
/// - fileBasenameNoExtension：example
/// - fileExtname：.png
      CODE

      if should_support_nullsafety
        code += <<-CODE
class AssetResource {
  /// Creates an object to hold the asset resource’s metadata.
  const AssetResource(this.assetName, {this.packageName = ""});

  /// The name of the main asset from the set of asset resources to choose from.
  final String assetName;

  /// The name of the package from which the asset resource is included.
  final String? packageName;

  /// The name used to generate the key to obtain the asset resource. For local assets
  /// this is [assetName], and for assets from packages the [assetName] is
  /// prefixed 'packages/<package_name>/'.
  String get keyName => packageName == null ? assetName : "packages/$packageName/$assetName";

  /// The file basename of the asset resource.
  String get fileBasename {
    final basename = path.basename(assetName);
    return basename;
  }

  /// The no extension file basename of the asset resource.
  String get fileBasenameNoExtension {
    final basenameWithoutExtension = path.basenameWithoutExtension(assetName);
    return basenameWithoutExtension;
  }

  /// The file extension name of the asset resource.
  String get fileExtname {
    final extension = path.extension(assetName);
    return extension;
  }

  /// The directory path name of the asset resource.
  String get fileDirname {
    var dirname = assetName;
    if (packageName != null) {
      final packageStr = "packages/$packageName/";
      dirname = dirname.replaceAll(packageStr, "");
    }
    final filenameStr = "$fileBasename/";
    dirname = dirname.replaceAll(filenameStr, "");
    return dirname;
  }
}
        CODE
      else
        code += <<-CODE
class AssetResource {
  /// Creates an object to hold the asset resource’s metadata.
  const AssetResource(this.assetName, {this.packageName}) : assert(assetName != null);

  /// The name of the main asset from the set of asset resources to choose from.
  final String assetName;

  /// The name of the package from which the asset resource is included.
  final String packageName;

  /// The name used to generate the key to obtain the asset resource. For local assets
  /// this is [assetName], and for assets from packages the [assetName] is
  /// prefixed 'packages/<package_name>/'.
  String get keyName => packageName == null ? assetName : "packages/$packageName/$assetName";

  /// The file basename of the asset resource.
  String get fileBasename {
    final basename = path.basename(assetName);
    return basename;
  }

  /// The no extension file basename of the asset resource.
  String get fileBasenameNoExtension {
    final basenameWithoutExtension = path.basenameWithoutExtension(assetName);
    return basenameWithoutExtension;
  }

  /// The file extension name of the asset resource.
  String get fileExtname {
    final extension = path.extension(assetName);
    return extension;
  }

  /// The directory path name of the asset resource.
  String get fileDirname {
    var dirname = assetName;
    if (packageName != null) {
      final packageStr = "packages/$packageName/";
      dirname = dirname.replaceAll(packageStr, "");
    }
    final filenameStr = "$fileBasename/";
    dirname = dirname.replaceAll(filenameStr, "");
    return dirname;
  }
}
        CODE
      end

      return code
    end

    # generate_asset_id (asset, used_asset_id_array, prior_asset_type) -> string
    #
    # - prior_asset_type: 优先的资源类型；默认值为 ".*"，意味当前不存在任何优先的资源类型
    #                     对于优先的资源类型，其 asset_id 不会携带类型信息，详细见例子
    #
    # 为当前 asset 生成 asset_id（资产ID）；asset_id 一般为 asset 的 file_basename_no_extension；
    # 但是为了保证 asset_id 的健壮性，需要对 file_basename_no_extension 做以下加工处理：
    # - 处理非法字符：把除了字母（a-z, A-Z）、数字（0-9）、'_' 字符、'$' 字符之外的字符转换为 '_' 字符
    # - 首字母转化为小写
    # - 处理首字符异常情况：检测首字符是不是数字、'_'、'$'，若是则添加前缀字符"a"
    # - 处理 asset_id 重名的情况
    #
    # === Examples
    #
    # ===== Example-1
    # asset = "packages/flutter_r_demo/assets/images/test.png"
    # asset = "packages/flutter_r_demo/assets/images/test.jpg"
    # used_asset_id_array = []
    # prior_asset_type = ".png"
    # asset_id = "test"
    #
    # ===== Example-2
    # asset = "packages/flutter_r_demo/assets/images/test.jpg"
    # used_asset_id_array = [test]
    # prior_asset_type = ".png"
    # asset_id = "test_jpg"
    #
    # ===== Example-3
    # asset = "packages/flutter_r_demo/assets/home-images/test.jpg"
    # used_asset_id_array = [test, test_jpg]
    # prior_asset_type = ".png"
    # asset_id = "test_jpg_1"
    #
    # ===== Example-4
    # asset = "packages/flutter_r_demo/assets/texts/test.json"
    # used_asset_id_array = []
    # prior_asset_type = ".*"
    # asset_id = "test_json"
    #
    def self.generate_asset_id(asset, used_asset_id_array, prior_asset_type = ".*")
      file_extname = File.extname(asset).downcase

      dirname = File.dirname(asset)
      parent_dir_name = File.basename(dirname)
      file_basename = File.basename(asset)

      file_basename_no_extension = File.basename(asset, ".*")
      asset_id = file_basename_no_extension.dup
      if prior_asset_type.eql?(".*") or file_extname.eql?(prior_asset_type) == false
        ext_info = file_extname
        ext_info[0] = "_"
        asset_id = asset_id + ext_info
      end

      # 处理非法字符
      asset_id = asset_id.gsub(/[^a-zA-Z0-9_$]/, "_")

      # 首字母转化为小写
      capital = asset_id[0].downcase
      asset_id[0] = capital

      # 处理首字符异常情况
      if capital =~ /[0-9_$]/
        asset_id = "a" + asset_id
      end

      # 处理 asset_id 重名的情况
      if used_asset_id_array.include?(asset_id)
        # 当前asset_id重名次数，初始值为1
        repeat_count = 1

        # 查找当前asset_id衍生出来的asset_id_brother（id兄弟）
        # asset_id_brother = #{asset_id}$#{repeat_count}
        # 其中，repeat_count >= 1
        #
        # Example：
        # asset_id = test
        # asset_id_brother = test$1
        #
        id_brother_regx = /^#{asset_id}\$[1-9][0-9]*$/
        cur_asset_id_brothers = used_asset_id_array.select{ |id| id =~ id_brother_regx }

        repeat_count += cur_asset_id_brothers.size
        asset_id = "#{asset_id}$#{repeat_count}"
      end

      return asset_id
    end

    # generate_asset_comment (asset, package_name) -> string
    #
    # 为当前asset生成注释
    #
    # === Examples
    # package_name = "flutter_r_demo"
    #
    # === Example-1
    # asset = "packages/flutter_r_demo/assets/images/test.png"
    # asset_comment = "asset: lib/assets/images/test.png"
    #
    # === Example-2
    # asset = "assets/images/test.png"
    # asset_comment = "asset: assets/images/test.png"
    #
    def self.generate_asset_comment (asset, package_name)
      packages_prefix = "packages/#{package_name}/"

      if asset =~ /\A#{packages_prefix}/
        # asset: packages/flutter_r_demo/assets/images/test.png
        # to get assetName: assets/images/test.png
        asset_name = asset.dup
        asset_name[packages_prefix] = ""

        asset_comment = "asset: lib/#{asset_name}"
        return asset_comment
      else
        # asset: assets/images/test.png
        # to get assetName: assets/images/test.png
        asset_name = asset.dup

        asset_comment = "asset: #{asset_name}"
        return asset_comment
      end

    end

    # generate_AssetResource_property(asset, asset_id_dict, package_name, is_package_project_type, prior_asset_type) -> string
    #
    # 为当前 asset 生成 AssetResource property 的代码
    #
    def self.generate_AssetResource_property(asset, asset_id_dict, package_name, is_package_project_type, prior_asset_type = ".*")
      asset_id = asset_id_dict[asset]
      asset_comment = generate_asset_comment(asset, package_name)

      asset_name = ""
      needPackage = false

      packages_prefix = "packages/#{package_name}/"
      if asset =~ /\A#{packages_prefix}/
        # asset: packages/flutter_r_demo/assets/images/test.png
        # to get asset_name: assets/images/test.png
        asset_name = asset.dup
        asset_name[packages_prefix] = ""

        needPackage = true
      else
        # asset: assets/images/test.png
        # to get asset_name: assets/images/test.png
        asset_name = asset.dup

        if is_package_project_type
          needPackage = true
        else
          needPackage = false
        end

      end

      # 对字符串中的 '$' 进行转义处理：'$' -> '\$'
      # asset_name: assets/images/test$.png
      # to get escaped_asset_name: assets/images/test\$.png
      escaped_asset_name = asset_name.gsub(/[$]/, "\\$")

      if needPackage
        code = <<-CODE
  /// #{asset_comment}
  // ignore: non_constant_identifier_names
  final #{asset_id} = const AssetResource("#{escaped_asset_name}", packageName: R.package);
        CODE

        return code
      else
        code = <<-CODE
  /// #{asset_comment}
  // ignore: non_constant_identifier_names
  final #{asset_id} = const AssetResource("#{escaped_asset_name}", packageName: null);
        CODE

        return code
      end
    end

    # generate__R_Image_AssetResource_class(non_svg_image_asset_array, non_svg_image_asset_id_dict, package_name, is_package_project_type) -> string
    #
    # 根据模板，为 non_svg_image_asset_array（非svg类的图片资产数组）生成 _R_Image_AssetResource class 的代码
    #
    def self.generate__R_Image_AssetResource_class(non_svg_image_asset_array, non_svg_image_asset_id_dict, package_name, is_package_project_type)

      all_g_AssetResource_property_code = ""

      non_svg_image_asset_array.each do |image_asset|
        all_g_AssetResource_property_code += "\n"
        g_AssetResource_property_code = generate_AssetResource_property(image_asset, non_svg_image_asset_id_dict, package_name, is_package_project_type, Flr::PRIOR_NON_SVG_IMAGE_FILE_TYPE)
        all_g_AssetResource_property_code += g_AssetResource_property_code
      end

      code = <<-CODE
// ignore: camel_case_types
class _R_Image_AssetResource {
  const _R_Image_AssetResource();
#{all_g_AssetResource_property_code}
}
      CODE

      return code
    end

    # generate__R_Svg_AssetResource_class(svg_image_asset_array, svg_image_asset_id_dict, package_name, is_package_project_type) -> string
    #
    # 根据模板，为 svg_image_asset_array（svg类的图片资产数组）生成 _R_Svg_AssetResource class 的代码
    #
    def self.generate__R_Svg_AssetResource_class(svg_image_asset_array, svg_image_asset_id_dict, package_name, is_package_project_type)

      all_g_AssetResource_property_code = ""

      svg_image_asset_array.each do |image_asset|
        all_g_AssetResource_property_code += "\n"
        g_AssetResource_property_code = generate_AssetResource_property(image_asset, svg_image_asset_id_dict, package_name, is_package_project_type, Flr::PRIOR_SVG_IMAGE_FILE_TYPE)
        all_g_AssetResource_property_code += g_AssetResource_property_code
      end

      code = <<-CODE
// ignore: camel_case_types
class _R_Svg_AssetResource {
  const _R_Svg_AssetResource();
#{all_g_AssetResource_property_code}
}
      CODE

      return code
    end

    # generate__R_Text_AssetResource_class(text_asset_array, text_asset_id_dict, package_name, is_package_project_typ) -> string
    #
    # 根据模板，为 text_asset_array（文本资产数组）生成 _R_Text_AssetResource class 的代码
    #
    def self.generate__R_Text_AssetResource_class(text_asset_array, text_asset_id_dict, package_name, is_package_project_type)

      all_g_AssetResource_property_code = ""

      text_asset_array.each do |text_asset|
        all_g_AssetResource_property_code += "\n"
        g_AssetResource_property_code = generate_AssetResource_property(text_asset, text_asset_id_dict, package_name, is_package_project_type, Flr::PRIOR_TEXT_FILE_TYPE)
        all_g_AssetResource_property_code += g_AssetResource_property_code
      end

      code = <<-CODE
// ignore: camel_case_types
class _R_Text_AssetResource {
  const _R_Text_AssetResource();
#{all_g_AssetResource_property_code}
}
      CODE

      return code
    end

    # generate__R_Image_class(non_svg_image_asset_array, non_svg_image_asset_id_dict, package_name) -> string
    #
    # 根据模板，为 non_svg_image_asset_array（非svg类的图片资产数组）生成 _R_Image class 的代码
    #
    def self.generate__R_Image_class(non_svg_image_asset_array, non_svg_image_asset_id_dict, package_name)

      all_g_Asset_method_code = ""

      non_svg_image_asset_array.each do |image_asset|
        all_g_Asset_method_code += "\n"

        asset_id = non_svg_image_asset_id_dict[image_asset]
        asset_comment = generate_asset_comment(image_asset, package_name)

        g_Asset_method_code = <<-CODE
  /// #{asset_comment}
  // ignore: non_constant_identifier_names
  AssetImage #{asset_id}() {
    return AssetImage(asset.#{asset_id}.keyName);
  }
        CODE

        all_g_Asset_method_code += g_Asset_method_code
      end

      code = <<-CODE
/// This `_R_Image` class is generated and contains references to static non-svg type image asset resources.
// ignore: camel_case_types
class _R_Image {
  const _R_Image();

  final asset = const _R_Image_AssetResource();
#{all_g_Asset_method_code}
}
      CODE

      return code
    end

    # generate__R_Svg_class(svg_image_asset_array, svg_image_asset_id_dict, package_name) -> string
    #
    # 根据模板，为 svg_image_asset_array（svg类的图片资产数组）生成 _R_Svg class 的代码
    #
    def self.generate__R_Svg_class(svg_image_asset_array, svg_image_asset_id_dict, package_name)

      all_g_Asset_method_code = ""

      svg_image_asset_array.each do |image_asset|
        all_g_Asset_method_code += "\n"

        asset_id = svg_image_asset_id_dict[image_asset]
        asset_comment = generate_asset_comment(image_asset, package_name)

        if should_support_nullsafety
          g_Asset_method_code = <<-CODE
  /// #{asset_comment}
  // ignore: non_constant_identifier_names
  AssetSvg #{asset_id}({required double width, required double height}) {
    final imageProvider = AssetSvg(asset.#{asset_id}.keyName, width: width, height: height);
    return imageProvider;
  }
          CODE
        else
          g_Asset_method_code = <<-CODE
  /// #{asset_comment}
  // ignore: non_constant_identifier_names
  AssetSvg #{asset_id}({@required double width, @required double height}) {
    final imageProvider = AssetSvg(asset.#{asset_id}.keyName, width: width, height: height);
    return imageProvider;
  }
          CODE
        end

        all_g_Asset_method_code += g_Asset_method_code
      end

      code = <<-CODE
/// This `_R_Svg` class is generated and contains references to static svg type image asset resources.
// ignore: camel_case_types
class _R_Svg {
  const _R_Svg();

  final asset = const _R_Svg_AssetResource();
#{all_g_Asset_method_code}
}
      CODE

      return code
    end

    # generate__R_Text_class(text_asset_array, text_asset_id_dict, package_name) -> string
    #
    # 根据模板，为 text_asset_array（文本资产数组）生成 _R_Text class 的代码
    #
    def self.generate__R_Text_class(text_asset_array, text_asset_id_dict, package_name)

      all_g_Asset_method_code = ""

      text_asset_array.each do |text_asset|
        all_g_Asset_method_code += "\n"

        asset_id = text_asset_id_dict[text_asset]
        asset_comment = generate_asset_comment(text_asset, package_name)

        g_Asset_method_code = <<-CODE
  /// #{asset_comment}
  // ignore: non_constant_identifier_names
  Future<String> #{asset_id}() {
    final str = rootBundle.loadString(asset.#{asset_id}.keyName);
    return str;
  }
        CODE

        all_g_Asset_method_code += g_Asset_method_code
      end

      code = <<-CODE
/// This `_R_Text` class is generated and contains references to static text asset resources.
// ignore: camel_case_types
class _R_Text {
  const _R_Text();

  final asset = const _R_Text_AssetResource();
#{all_g_Asset_method_code}
}
      CODE

      return code
    end

    # generate_font_family_id(font_family_name) -> string
    #
    # 为当前 font_family_name 生成 font_family_id；font_family_id 一般为 asset 的 font_family_name；
    # 但是为了保证 font_family_id 的健壮性，需要对 font_family_name 做以下加工处理：
    # - 处理非法字符：把除了字母（a-z, A-Z）、数字（0-9）、'_' 字符、'$' 字符之外的字符转换为 '_' 字符
    # - 首字母转化为小写
    # - 处理首字符异常情况：检测首字符是不是数字、'_'、'$'，若是则添加前缀字符"a"
    #
    # === Examples
    # a_font_family_name = "Amiri"
    # b_font_family_name = "Baloo-Thambi-2"
    # a_font_family_id = "amiri"
    # b_font_family_id = "baloo_Thambi_2"
    #
    #
    def self.generate_font_family_id(font_family_name)

      font_family_id = font_family_name.dup

      # 处理非法字符
      font_family_id = font_family_id.gsub(/[^a-zA-Z0-9_$]/, "_")

      # 首字母转化为小写
      capital = font_family_id[0].downcase
      font_family_id[0] = capital

      # 处理首字符异常情况
      if capital =~ /[0-9_$]/
        font_family_id = "a" + font_family_id
      end

      return font_family_id
    end

    # generate__R_FontFamily_class(font_family_config_array, package_name) -> string
    #
    # 根据模板，为 font_family_config_array（字体家族配置数组）生成 _R_FontFamily class 的代码
    #
    def self.generate__R_FontFamily_class(font_family_config_array, package_name)

      all_g_AssetResource_property_code = ""

      font_family_config_array.each do |font_family_config|
        all_g_AssetResource_property_code += "\n"

        font_family_name = font_family_config["family"]

        font_family_id = generate_font_family_id(font_family_name)
        font_family_comment = "font family: #{font_family_name}"

        g_AssetResource_property_code = <<-CODE
  /// #{font_family_comment}
  // ignore: non_constant_identifier_names
  final #{font_family_id} = "#{font_family_name}";
        CODE

        all_g_AssetResource_property_code += g_AssetResource_property_code
      end

      code = <<-CODE
/// This `_R_FontFamily` class is generated and contains references to static font asset resources.
// ignore: camel_case_types
class _R_FontFamily {
  const _R_FontFamily();
#{all_g_AssetResource_property_code}
}
      CODE

      return code
    end
  end

end
