// Copyright (c) 2011, 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.

// This file has classes representing the grid tracks and grid template

/**
 * The data structure representing the grid-rows or grid-columns
 * properties.
 */
class GridTrackList {
  /** The set of tracks defined in CSS via grid-rows and grid-columns */
  final List<GridTrack> tracks;

  /**
   * Maps edge names to the corresponding track. Depending on whether the index
   * is used as a start or end, it might be interpreted exclusively or
   * inclusively.
   */
  final Map<String, int> lineNames;

  GridTrackList(this.tracks, this.lineNames) {}
}


/** Represents a row or a column. */
class GridTrack {
  /**
   * The start position of this track. Equal to the sum of previous track's
   * usedBreadth.
   */
  num start;

  /** The final computed breadth of this track. */
  num usedBreadth;

  // Fields used internally by the sizing algorithm
  num maxBreadth;
  num updatedBreadth;
  num tempBreadth;

  final TrackSizing sizing;

  GridTrack(this.sizing) {}

  /**
   * Support for the feature that repeats rows and columns, e.g.
   * [:grid-columns: 10px ("content" 250px 10px)[4]:]
   */
  GridTrack clone() => new GridTrack(sizing.clone());

  /** The min sizing function for the track. */
  SizingFunction get minSizing() => sizing.min;

  /** The min sizing function for the track. */
  SizingFunction get maxSizing() => sizing.max;

  num get end() => start + usedBreadth;

  bool get isFractional() => minSizing.isFraction || maxSizing.isFraction;
}


/** Represents the grid-row-align or grid-column-align. */
class GridItemAlignment {
  // TODO(jmesserly): should this be stored as an int for performance?
  final String value;

  // 'start' | 'end' | 'center' | 'stretch'
  GridItemAlignment.fromString(String value)
    : this.value = (value == null) ? 'stretch' : value {

      switch (this.value) {
      case 'start': case 'end': case 'center': case 'stretch':
        break;
      default:
        throw new UnsupportedOperationException(
            'invalid row/column alignment "$value"');
    }
  }

  _GridLocation align(_GridLocation span, int size) {
    switch (value) {
      case 'start':
        return new _GridLocation(span.start, size);
      case 'end':
        return new _GridLocation(span.end - size, size);
      case 'center':
        size = Math.min(size, span.length);
        num center = span.start + span.length / 2;
        num left = center - size / 2;
        return new _GridLocation(left.round().toInt(), size);
      case 'stretch':
        return span;
    }
  }
}


/**
 * Represents a grid-template. Used in conjunction with a grid-cell to
 * place cells in the grid, without needing to specify the exact row/column.
 */
class GridTemplate {
  final Map<int, _GridTemplateRect> _rects;
  final int _numRows;

  GridTemplate(List<String> rows)
    : _rects = new Map<int, _GridTemplateRect>(),
      _numRows = rows.length {
    _buildRects(rows);
  }

  /** Scans the template strings and computes bounds for each one. */
  void _buildRects(List<String> templateRows) {
    for (int r = 0; r < templateRows.length; r++) {
      String row = templateRows[r];
      for (int c = 0; c < row.length; c++) {
        int cell = row.charCodeAt(c);
        final rect = _rects[cell];
        if (rect != null) {
          rect.add(r + 1, c + 1);
        } else {
          _rects[cell] = new _GridTemplateRect(cell, r + 1, c + 1);
        }
      }
    }

    // Finally, check that each rectangle is valid (i.e. all spaces filled)
    for (final rect in _rects.getValues()) {
      rect.checkValid();
    }
  }

  /**
   * Looks up the given cell in the template, and returns the rect.
   */
  _GridTemplateRect lookupCell(String cell) {
    if (cell.length != 1) {
      throw new UnsupportedOperationException(
          'grid-cell "$cell" must be a one character string');
    }
    final rect = _rects[cell.charCodeAt(0)];
    if (rect == null) {
      throw new UnsupportedOperationException(
        'grid-cell "$cell" not found in parent\'s grid-template');
    }
    return rect;
  }
}

/** Used by GridTemplate to track a single cell's bounds. */
class _GridTemplateRect {
  int row, column, rowSpan, columnSpan, _count, _char;
  _GridTemplateRect(this._char, this.row, this.column)
    : rowSpan = 1, columnSpan = 1, _count = 1 {}

  void add(int r, int c) {
    assert (r >= row && c >= column);
    _count++;
    rowSpan = Math.max(rowSpan, r - row + 1);
    columnSpan = Math.max(columnSpan, c - column + 1);
  }

  void checkValid() {
    int expected = rowSpan * columnSpan;
    if (expected != _count) {
      // TODO(jmesserly): not sure if we should throw here, due to CSS's
      // permissiveness. At the moment we're noisy about errors.
      String cell = new String.fromCharCodes([_char]);
      throw new UnsupportedOperationException('grid-template "$cell"' +
          ' is not square, expected $expected cells but got $_count');
    }
  }
}


/**
 * Used to return a row/column and span during parsing of grid-row and
 * grid-column during parsing.
 */
class _GridLocation {
  final int start, length;
  _GridLocation(this.start, this.length) {}

  int get end() => start + length;
}
