import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math' as math;
import 'dart:ui' as ui;

import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// import 'package:fluttertoast/fluttertoast.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';

T? tryCast<T>(dynamic object) => object is T ? object : null;

final Logger _log = Logger('utils.dart');

Future<ui.Image> loadUiImageFromFile(String filePath) async {
  final file = await localFile(filePath);
  final data = await file.readAsBytes();
  final Completer<ui.Image> completer = Completer();
  ui.decodeImageFromList(data, (ui.Image img) {
    return completer.complete(img);
  });
  return completer.future;
}

Future<ui.Image> loadUiImageFromXFile(XFile xfile) async {
  final file = File(xfile.path);
  final data = await file.readAsBytes();
  final Completer<ui.Image> completer = Completer();
  ui.decodeImageFromList(data, (ui.Image img) {
    return completer.complete(img);
  });
  return completer.future;
}

Future<ui.Image> loadUiImageFromAsset(String assetPath) async {
  final ByteData data = await rootBundle.load(assetPath);
  final Completer<ui.Image> completer = Completer();
  ui.decodeImageFromList(Uint8List.view(data.buffer), (ui.Image img) {
    return completer.complete(img);
  });
  return completer.future;
}

Future<ui.Image> loadUiImageFromNetwork(String url) async {
  final ByteData data = await NetworkAssetBundle(Uri.parse(url)).load('');
  final Completer<ui.Image> completer = Completer();
  ui.decodeImageFromList(Uint8List.view(data.buffer), (ui.Image img) {
    return completer.complete(img);
  });
  return completer.future;
}

Future<Map<String, dynamic>> loadJSONFromAsset(String assetPath) async {
  final jsonStr = await rootBundle.loadString(assetPath);
  final Map<String, dynamic> json = jsonDecode(jsonStr);
  return json;
}

Future<Uint8List> loadFileDataFromAsset(String assetPath) async {
  final ByteData data = await rootBundle.load(assetPath);
  return Uint8List.view(data.buffer);
}

Future<List<String>> listAssets(String path) async {
  final manifestContent = await rootBundle.loadString('AssetManifest.json');
  final Map<String, dynamic> manifestMap = jsonDecode(manifestContent);
  // _log.info('manifestMap: $manifestMap');
  final paths = manifestMap.keys.where((String key) => key.contains(path)).toList();
  return paths;
}

Future<String> localDir() async {
  final Directory dir = await getApplicationDocumentsDirectory();
  return dir.path;
}

Future<File> localFile(String filePath) async {
  final String path = await localDir();
  return File('$path/$filePath');
}

// Future<Map<String, dynamic>> loadJson(String filePath) async {
Future<Object> loadJson(String filePath) async {
  File file = await localFile(filePath);
  final String str = await file.readAsString();
  final json = jsonDecode(str);
  _log.info('json: ${json.runtimeType}');
  return json;
}

Future<bool> ensureDirectory(File file) async {
  Directory directory = file.parent;
  if (!await directory.exists()) {
    await directory.create(recursive: true);
    return true;
  }
  return true;
}

Future<bool> saveBytes(String filePath, Uint8List data) async {
  File file = await localFile(filePath);

  _log.info('saveImage: file=$file');
  await ensureDirectory(file);
  await file.writeAsBytes(data);
  return true;
}

Future<bool> saveImage(String filePath, ui.Image image) async {
  final bytes = await image.toByteData(format: ui.ImageByteFormat.png);
  if (bytes == null) return false;
  return saveBytes(filePath, bytes.buffer.asUint8List());
}

Future<bool> saveJson(String filePath, dynamic data) async {
  File file = await localFile(filePath);
  await ensureDirectory(file);
  String str = jsonEncode(data);
  await file.writeAsString(str);
  return true;
}

Future<bool> saveString(String filePath, String data) async {
  File file = await localFile(filePath);
  await ensureDirectory(file);
  await file.writeAsString(data);
  return true;
}

Future<ui.Image> mosaicImage(ui.Image image) async {
  ui.PictureRecorder recorder = ui.PictureRecorder();
  ui.Canvas canvas = ui.Canvas(recorder);
  ui.Paint paint = ui.Paint();

  canvas.drawImageRect(image, Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()), const Rect.fromLTWH(0, 0, 8, 8), paint);

  ui.Image mosaicImage = await recorder.endRecording().toImage(8, 8);
  return mosaicImage;
}

Future<ui.Image> mosaicImage2(ui.Image image) async {
  const int piece = 8;
  final data = await image.toByteData();
  final pixels = data!.buffer.asUint32List();
  ui.PictureRecorder recorder = ui.PictureRecorder();
  ui.Canvas canvas = ui.Canvas(recorder);
  ui.Paint paint = ui.Paint();

  double width = image.width / piece;
  double height = image.height / piece;
  for (int i = 0; i < piece; i++) {
    for (int j = 0; j < piece; j++) {
      Rect rect = Rect.fromLTWH(i * width, j * height, width, height);
      int pixel32 = pixels[rect.center.dy.toInt() * image.width + rect.center.dx.toInt()];
      int hex = abgrToArgb(pixel32);
      Color color = Color(hex);
      canvas.drawRect(rect, paint..color = color);
    }
  }
  ui.Image mosaicImage = await recorder.endRecording().toImage(image.width, image.height);
  return mosaicImage;
}

int abgrToArgb(int argbColor) {
  int r = (argbColor >> 16) & 0xFF;
  int b = argbColor & 0xFF;
  return (argbColor & 0xFF00FF00) | (b << 16) | r;
}

class TimingLogSplit {
  String tag;
  Duration duration;
  TimingLogSplit(this.tag, this.duration);
  @override
  String toString() => '$tag: ${duration.inMilliseconds}';
}

class TimingLog {
  final String label;
  final DateTime _start = DateTime.now();
  DateTime? _lastSplit;

  final List<TimingLogSplit> splits = [];

  TimingLog(this.label);

  TimingLog addSplit(String tag) {
    _lastSplit ??= _start;
    final current = DateTime.now();
    splits.add(TimingLogSplit('$label.$tag', Duration(microseconds: (current.microsecondsSinceEpoch - _lastSplit!.microsecondsSinceEpoch))));
    _lastSplit = current;
    return this;
  }

  TimingLog stop() {
    final current = DateTime.now();
    splits.add(TimingLogSplit('$label.total', Duration(microseconds: (current.microsecondsSinceEpoch - _start.microsecondsSinceEpoch))));
    return this;
  }

  @override
  String toString() {
    return splits.join('\n');
  }
}

String md5Hash(String str) {
  return md5.convert(utf8.encode(str)).toString();
}

String niceBytes(bytes, {decimals = 2}) {
  if (!bytes) return '0 Bytes';
  const k = 1024;
  final dm = decimals < 0 ? 0 : decimals;
  final sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
  final i = (math.log(bytes) / math.log(k)).floor();
  return '${((bytes / math.pow(k, i)).toFixed(dm))} ${sizes[i]}';
}

String timeSpentFormat(int seconds) {
  int hour = seconds ~/ 3600;
  int minute = seconds % 3600 ~/ 60;
  int second = seconds % 60;

  if (hour > 0) {
    return "${formatNum(hour)}:${formatNum(minute)}:${formatNum(second)}";
  } else {
    return "${formatNum(minute)}:${formatNum(second)}";
  }
}

String formatNum(int num) {
  return num < 10 ? "0$num" : "$num";
}

void checkMemory() {
  ImageCache imageCache = PaintingBinding.instance.imageCache;
  debugPrint("liveImageCount = ${imageCache.liveImageCount}");
  if (imageCache.liveImageCount >= 100) {
    debugPrint("liveImageCount = ${imageCache.liveImageCount}, clear!");
    imageCache.clear();
    imageCache.clearLiveImages();
  }
}

// 辅助扩展：解决 Dart 缺少 firstWhereOrNull 的问题
extension IterableExtension<T> on Iterable<T> {
  T? firstWhereOrNull(bool Function(T element) test) {
    for (final element in this) {
      if (test(element)) {
        return element;
      }
    }
    return null;
  }
}
