// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
// for details. 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:collection';
import 'dart:typed_data';

import 'indirect_table.dart';
import 'list_overlap.dart';

/// Splits an indirect table with one large chunk into separate smaller chunks.
///
/// No new chunk is larger than the largest entry.
///
/// Preserves the entries, but they now point into the new chunks.
/// All chunks are distinct, and no chunk is a sub-list of another chunk.
void chunkifyTable(IndirectTable table) {
  if (table.chunks.length != 1) {
    throw ArgumentError('Single chunk table required');
  }
  var data = table.chunks[0];
  var entries = table.entries.toList();
  entries.sort((a, b) => b.length - a.length);
  var uniqueChunks = <Uint8List>[];
  var duplicateDetector =
      HashMap<Uint8List, TableEntry>(equals: _equals, hashCode: _hash);
  for (var entry in entries) {
    var chunk = data.sublist(entry.start, entry.end);
    var existingEntry = duplicateDetector[chunk];
    if (existingEntry != null) {
      entry.copyFrom(existingEntry);
    } else {
      // Check if chunk is a sublist of any existing chunk.
      var chunkNum = 0;
      var indexOf = 0;
      for (; chunkNum < uniqueChunks.length; chunkNum++) {
        var existingChunk = uniqueChunks[chunkNum];
        if (existingChunk.length > chunk.length) {
          var position = _indexOf(chunk, existingChunk);
          if (position >= 0) {
            indexOf = position;
            break;
          }
        }
      }
      if (chunkNum == uniqueChunks.length) {
        uniqueChunks.add(chunk);
      }
      entry.update(chunkNum, indexOf, entry.length);
      duplicateDetector[chunk] = entry;
    }
  }
  table.chunks = uniqueChunks;
}

int _indexOf(Uint8List short, Uint8List long) {
  var length = short.length;
  var range = long.length - length;
  outer:
  for (var i = 0; i < range; i++) {
    for (var j = 0; j < short.length; j++) {
      if (short[j] != long[i + j]) continue outer;
    }
    return i;
  }
  return -1;
}

/// Combines an indirect table with multiple chunks into only one chunk.
void combineChunkedTable(IndirectTable table) {
  var overlapped = combineLists(table.chunks);
  for (var entry in table.entries) {
    var chunkEntry = overlapped.entries[entry.chunkNumber];
    entry.update(0, entry.start + chunkEntry.start, entry.length);
  }
  table.chunks = [overlapped.chunks[0]];
}

/// Hash on a list.
int _hash(Uint8List list) {
  var view = list.buffer.asUint32List();
  var hash = 0;
  for (var i = 0; i < view.length; i++) {
    hash = (hash * 37 ^ view[i]) & 0xFFFFFFFF;
  }
  return hash;
}

/// Equality of lists of equal length.
bool _equals(Uint8List a, Uint8List b) {
  assert(a.length == b.length);
  assert(a.length % 8 == 0);
  // Compare 32 bits at a time.
  var aView = a.buffer.asInt64List();
  var bView = b.buffer.asInt64List();
  for (var i = 0; i < aView.length; i++) {
    if (aView[i] != bView[i]) return false;
  }
  return true;
}
