import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:archive/archive.dart';
import 'package:archive/archive_io.dart';
import 'package:guru_utils/http/http_ex.dart';

import 'package:guru_utils/log/log.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';

/// Created by Haoyi on 2020/4/22
///

class FileUtils {
  static FileUtils? _instance;

  static FileUtils _getInstance() {
    _instance ??= FileUtils._internal();
    return _instance!;
  }

  factory FileUtils() => _getInstance();

  static FileUtils get instance => _getInstance();

  final _utf8Decoder = const Utf8Decoder();

  FileUtils._internal();

  static Directory? appFileDirectory;

  Future<Directory> getFileDirectory() async {
    appFileDirectory ??= await (Platform.isIOS
        ? getApplicationDocumentsDirectory()
        : getApplicationSupportDirectory());
    return appFileDirectory!;
  }

  Future<Directory> getCacheDir({List<String>? subDirs, bool recursive = true}) async {
    return getTemporaryDirectory().then((dir) =>
        Directory("${dir.path}/${subDirs != null ? path.joinAll(subDirs) : ''}")
            .create(recursive: recursive));
  }

  Future<Directory> getAppDir({List<String>? subDirs, bool recursive = true}) async {
    return getFileDirectory().then((dir) =>
        Directory("${dir.path}/${subDirs != null ? path.joinAll(subDirs) : ''}")
            .create(recursive: recursive));
  }

  Future<File> createCacheFile(String name) async {
    return getTemporaryDirectory().then((dir) => File(path.join(dir.path, name)));
  }

  File joinPathSegments(List<String> paths) {
    return File(path.joinAll(paths));
  }

  File createFile(Directory directory, name) {
    return File(path.join(directory.path, name));
  }

  Future<File> downloadFile(String url, File file) async {
    final path = file.path;
    final newFile = File(path + ".tmp");
    final data = await HttpEx.get(Uri.parse(url));
    if (data.statusCode != 200) {
      return file;
    }
    await newFile.writeAsBytes(data.bodyBytes);
    if (file.existsSync()) {
      file.deleteSync();
    }
    return newFile.renameSync(path);
  }

  Future<T?> loadFileAndConvert<T>(File file, T Function(String data) convert) async {
    try {
      final dataStr = file.readAsStringSync();
      return convert(dataStr);
    } catch (error, stacktrace) {
      Log.w("downloadFileAndCovert error! $error $stacktrace");
      return null;
    }
  }

  Future<T?> downloadFileAndConvert<T>(
      String url, File file, T Function(String data) convert) async {
    final path = file.path;
    final newFile = File(path + ".tmp");
    final data = await HttpEx.get(Uri.parse(url));
    if (data.statusCode != 200) {
      return null;
    }
    try {
      final result = convert(_utf8Decoder.convert(data.bodyBytes));
      await newFile.writeAsBytes(data.bodyBytes);
      if (file.existsSync()) {
        file.deleteSync();
      }
      newFile.renameSync(path);
      return result;
    } catch (error, stacktrace) {
      Log.w("downloadFileAndCovert error! $error $stacktrace");
      return null;
    }
  }

  Future<bool> checkFileExists(File file) async {
    return file.exists();
  }

  Future<bool> saveFile(File file, String data) async {
    file.writeAsStringSync(data);
    return true;
  }

  Future<File> copyFile(File srcFile, File dstFile, {bool deleteSrc = false}) async {
    if (!dstFile.existsSync()) {
      dstFile.createSync();
    }
    srcFile.copySync(dstFile.path);
    if (deleteSrc) {
      srcFile.deleteSync();
    }
    return dstFile;
  }

  void deleteFile(File file, {bool recursive = false}) {
    file.deleteSync(recursive: recursive);
  }

  static Future<bool> unzipFile(File zipFile, String unzipPath) async {
    try {
      List<int> bytes = zipFile.readAsBytesSync();
      Archive archive = ZipDecoder().decodeBytes(bytes);
      for (ArchiveFile file in archive) {
        if (file.isFile) {
          List<int> data = file.content;
          File(unzipPath + "/" + file.name)
            ..createSync(recursive: true)
            ..writeAsBytesSync(data);
        } else {
          Directory(unzipPath + "/" + file.name).create(recursive: true);
        }
      }
      print("unzip success");
      return true;
    } catch (error) {
      print("unzip error=${error}");
      return false;
    }
  }

  static Future<bool> unzipTo(Uint8List data, String unzipPath, {String? password}) async {
    try {
      Archive archive = ZipDecoder().decodeBytes(data, password: password);
      for (ArchiveFile file in archive) {
        if (file.isFile) {
          List<int> data = file.content;
          File(unzipPath + "/" + file.name)
            ..createSync(recursive: true)
            ..writeAsBytesSync(data);
        } else {
          Directory(unzipPath + "/" + file.name).create(recursive: true);
        }
      }
      Log.d("unzip success");
      return true;
    } catch (error) {
      Log.d("unzip error:$error");
    }
    return false;
  }

  void zipFile(String zipName, String zipPath) {
    var encoder = ZipFileEncoder();
    encoder.zipDirectory(Directory(zipPath));
    encoder.create(zipName);
    encoder.addDirectory(Directory(zipPath));
    encoder.addFile(File(zipName));
    encoder.close();
  }

  String getFileName(File file, {bool withoutExtension = false}) {
    if (!file.existsSync()) {
      return "";
    }
    if (withoutExtension) {
      return path.basenameWithoutExtension(file.path);
    } else {
      return path.basename(file.path);
    }
  }

  Future saveFileData(File file, Uint8List data) async {
    file.writeAsBytesSync(data);
  }
}