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

import 'dart:io';

import 'package:archive/archive.dart';
import 'package:path/path.dart' as path;

import 'asset.dart';
import 'base/process.dart';

abstract class ZipBuilder {
  factory ZipBuilder() {
    if (exitsHappy(<String>['which', 'zip'])) {
      return new _ZipToolBuilder();
    } else {
      return new _ArchiveZipBuilder();
    }
  }

  ZipBuilder._();

  List<AssetBundleEntry> entries = <AssetBundleEntry>[];

  void addEntry(AssetBundleEntry entry) => entries.add(entry);

  void createZip(File outFile, Directory zipBuildDir);
}

class _ArchiveZipBuilder extends ZipBuilder {
  _ArchiveZipBuilder() : super._();

  @override
  void createZip(File outFile, Directory zipBuildDir) {
    Archive archive = new Archive();

    for (AssetBundleEntry entry in entries) {
      List<int> data = entry.contentsAsBytes();
      archive.addFile(new ArchiveFile.noCompress(entry.archivePath, data.length, data));
    }

    List<int> zipData = new ZipEncoder().encode(archive);
    outFile.writeAsBytesSync(zipData);
  }
}

class _ZipToolBuilder extends ZipBuilder {
  _ZipToolBuilder() : super._();

  @override
  void createZip(File outFile, Directory zipBuildDir) {
    // If there are no assets, then create an empty zip file.
    if (entries.isEmpty) {
      List<int> zipData = new ZipEncoder().encode(new Archive());
      outFile.writeAsBytesSync(zipData);
      return;
    }

    if (outFile.existsSync())
      outFile.deleteSync();

    if (zipBuildDir.existsSync())
      zipBuildDir.deleteSync(recursive: true);
    zipBuildDir.createSync(recursive: true);

    for (AssetBundleEntry entry in entries) {
      List<int> data = entry.contentsAsBytes();
      File file = new File(path.join(zipBuildDir.path, entry.archivePath));
      file.parent.createSync(recursive: true);
      file.writeAsBytesSync(data);
    }

    if (_getCompressedNames().isNotEmpty) {
      runCheckedSync(
        <String>['zip', '-q', outFile.absolute.path]..addAll(_getCompressedNames()),
        workingDirectory: zipBuildDir.path
      );
    }

    if (_getStoredNames().isNotEmpty) {
      runCheckedSync(
        <String>['zip', '-q', '-0', outFile.absolute.path]..addAll(_getStoredNames()),
        workingDirectory: zipBuildDir.path
      );
    }
  }

  static const List<String> _kNoCompressFileExtensions = const <String>['.png', '.jpg'];

  bool isAssetCompressed(AssetBundleEntry entry) {
    return !_kNoCompressFileExtensions.any(
        (String extension) => entry.archivePath.endsWith(extension)
    );
  }

  Iterable<String> _getCompressedNames() {
    return entries
      .where(isAssetCompressed)
      .map((AssetBundleEntry entry) => entry.archivePath);
  }

  Iterable<String> _getStoredNames() {
    return entries
      .where((AssetBundleEntry entry) => !isAssetCompressed(entry))
      .map((AssetBundleEntry entry) => entry.archivePath);
  }
}