// Copyright 2015 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:sky';

enum FontWeight { w100, w200, w300, w400, w500, w600, w700, w800, w900 }
const normal = FontWeight.w400;
const bold = FontWeight.w700;

enum FontStyle { normal, italic, oblique }

enum TextAlign { left, right, center }

enum TextBaseline { alphabetic, ideographic }

enum TextDecoration { none, underline, overline, lineThrough }
const underline = const <TextDecoration>[TextDecoration.underline];
const overline = const <TextDecoration>[TextDecoration.overline];
const lineThrough = const <TextDecoration>[TextDecoration.lineThrough];

enum TextDecorationStyle { solid, double, dotted, dashed, wavy }

class TextStyle {
  const TextStyle({
    this.color,
    this.fontFamily,
    this.fontSize,
    this.fontWeight,
    this.fontStyle,
    this.textAlign,
    this.textBaseline,
    this.height,
    this.decoration,
    this.decorationColor,
    this.decorationStyle
  });

  final Color color;
  final String fontFamily;
  final double fontSize; // in pixels
  final FontWeight fontWeight;
  final FontStyle fontStyle;
  final TextAlign textAlign;
  final TextBaseline textBaseline;
  final double height; // multiple of fontSize
  final List<TextDecoration> decoration; // TODO(ianh): Switch this to a Set<> once Dart supports constant Sets
  final Color decorationColor;
  final TextDecorationStyle decorationStyle;

  TextStyle copyWith({
    Color color,
    String fontFamily,
    double fontSize,
    FontWeight fontWeight,
    FontStyle fontStyle,
    TextAlign textAlign,
    TextBaseline textBaseline,
    double height,
    List<TextDecoration> decoration,
    Color decorationColor,
    TextDecorationStyle decorationStyle
  }) {
    return new TextStyle(
      color: color != null ? color : this.color,
      fontFamily: fontFamily != null ? fontFamily : this.fontFamily,
      fontSize: fontSize != null ? fontSize : this.fontSize,
      fontWeight: fontWeight != null ? fontWeight : this.fontWeight,
      fontStyle: fontStyle != null ? fontStyle : this.fontStyle,
      textAlign: textAlign != null ? textAlign : this.textAlign,
      textBaseline: textBaseline != null ? textBaseline : this.textBaseline,
      height: height != null ? height : this.height,
      decoration: decoration != null ? decoration : this.decoration,
      decorationColor: decorationColor != null ? decorationColor : this.decorationColor,
      decorationStyle: decorationStyle != null ? decorationStyle : this.decorationStyle
    );
  }

  TextStyle merge(TextStyle other) {
    return copyWith(
      color: other.color,
      fontFamily: other.fontFamily,
      fontSize: other.fontSize,
      fontWeight: other.fontWeight,
      fontStyle: other.fontStyle,
      textAlign: other.textAlign,
      textBaseline: other.textBaseline,
      height: other.height,
      decoration: other.decoration,
      decorationColor: other.decorationColor,
      decorationStyle: other.decorationStyle
    );
  }

  static String _colorToCSSString(Color color) {
    return 'rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha / 255.0})';
  }

  static String _fontFamilyToCSSString(String fontFamily) {
    // TODO(hansmuller): escape the fontFamily string.
    return fontFamily;
  }

  static String _decorationToCSSString(List<TextDecoration> decoration) {
    assert(decoration != null);
    const toCSS = const <TextDecoration, String>{
      TextDecoration.none: 'none',
      TextDecoration.underline: 'underline',
      TextDecoration.overline: 'overline',
      TextDecoration.lineThrough: 'lineThrough'
    };
    return decoration.map((d) => toCSS[d]).join(' ');
  }

  static String _decorationStyleToCSSString(TextDecorationStyle decorationStyle) {
    assert(decorationStyle != null);
    const toCSS = const <TextDecorationStyle, String>{
      TextDecorationStyle.solid: 'solid',
      TextDecorationStyle.double: 'double',
      TextDecorationStyle.dotted: 'dotted',
      TextDecorationStyle.dashed: 'dashed',
      TextDecorationStyle.wavy: 'wavy'
    };
    return toCSS[decorationStyle];
  }

  void applyToCSSStyle(CSSStyleDeclaration cssStyle) {
    if (color != null) {
      cssStyle['color'] = _colorToCSSString(color);
    }
    if (fontFamily != null) {
      cssStyle['font-family'] = _fontFamilyToCSSString(fontFamily);
    }
    if (fontSize != null) {
      cssStyle['font-size'] = '${fontSize}px';
    }
    if (fontWeight != null) {
      cssStyle['font-weight'] = const {
        FontWeight.w100: '100',
        FontWeight.w200: '200',
        FontWeight.w300: '300',
        FontWeight.w400: '400',
        FontWeight.w500: '500',
        FontWeight.w600: '600',
        FontWeight.w700: '700',
        FontWeight.w800: '800',
        FontWeight.w900: '900'
      }[fontWeight];
    }
    if (fontStyle != null) {
      cssStyle['font-style'] = const {
        FontStyle.normal: 'normal',
        FontStyle.italic: 'italic',
        FontStyle.oblique: 'oblique',
      }[fontStyle];
    }
    if (decoration != null) {
      cssStyle['text-decoration'] = _decorationToCSSString(decoration);
      if (decorationColor != null)
        cssStyle['text-decoration-color'] = _colorToCSSString(decorationColor);
      if (decorationStyle != null)
        cssStyle['text-decoration-style'] = _decorationStyleToCSSString(decorationStyle);
    }
  }

  void applyToContainerCSSStyle(CSSStyleDeclaration cssStyle) {
    if (textAlign != null) {
      cssStyle['text-align'] = const {
        TextAlign.left: 'left',
        TextAlign.right: 'right',
        TextAlign.center: 'center',
      }[textAlign];
    }
    if (height != null) {
      cssStyle['line-height'] = '${height}';
    }
  }

  bool operator ==(other) {
    if (identical(this, other))
      return true;
    return other is TextStyle &&
      color == other.color &&
      fontFamily == other.fontFamily &&
      fontSize == other.fontSize &&
      fontWeight == other.fontWeight &&
      fontStyle == other.fontStyle &&
      textAlign == other.textAlign &&
      textBaseline == other.textBaseline &&
      decoration == other.decoration &&
      decorationColor == other.decorationColor &&
      decorationStyle == other.decorationStyle;
  }

  int get hashCode {
    // Use Quiver: https://github.com/domokit/mojo/issues/236
    int value = 373;
    value = 37 * value + color.hashCode;
    value = 37 * value + fontFamily.hashCode;
    value = 37 * value + fontSize.hashCode;
    value = 37 * value + fontWeight.hashCode;
    value = 37 * value + fontStyle.hashCode;
    value = 37 * value + textAlign.hashCode;
    value = 37 * value + textBaseline.hashCode;
    value = 37 * value + decoration.hashCode;
    value = 37 * value + decorationColor.hashCode;
    value = 37 * value + decorationStyle.hashCode;
    return value;
  }

  String toString([String prefix = '']) {
    List<String> result = [];
    if (color != null)
      result.add('${prefix}color: $color');
    // TODO(hansmuller): escape the fontFamily string.
    if (fontFamily != null)
      result.add('${prefix}fontFamily: "${fontFamily}"');
    if (fontSize != null)
      result.add('${prefix}fontSize: $fontSize');
    if (fontWeight != null)
      result.add('${prefix}fontWeight: $fontWeight');
    if (fontStyle != null)
      result.add('${prefix}fontStyle: $fontStyle');
    if (textAlign != null)
      result.add('${prefix}textAlign: $textAlign');
    if (textBaseline != null)
      result.add('${prefix}textBaseline: $textBaseline');
    if (decoration != null)
      result.add('${prefix}decoration: $decoration');
    if (decorationColor != null)
      result.add('${prefix}decorationColor: $decorationColor');
    if (decorationStyle != null)
      result.add('${prefix}decorationStyle: $decorationStyle');
    if (result.isEmpty)
      return '${prefix}<no style specified>';
    return result.join('\n');
  }
}
