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

part of cassowary;

class Expression extends _EquationMember {
  final List<Term> terms;

  final double constant;

  bool get isConstant => terms.length == 0;

  double get value => terms.fold(constant, (value, term) => value + term.value);

  Expression(this.terms, this.constant);
  Expression.fromExpression(Expression expr)
      : this.terms = new List<Term>.from(expr.terms),
        this.constant = expr.constant;

  Expression asExpression() => this;

  Constraint _createConstraint(
      _EquationMember /* rhs */ value, Relation relation) {
    if (value is ConstantMember) {
      return new Constraint(
          new Expression(new List.from(terms), constant - value.value),
          relation);
    }

    if (value is Param) {
      var newTerms = new List<Term>.from(terms)
        ..add(new Term(value.variable, -1.0));
      return new Constraint(new Expression(newTerms, constant), relation);
    }

    if (value is Term) {
      var newTerms = new List<Term>.from(terms)
        ..add(new Term(value.variable, -value.coefficient));
      return new Constraint(new Expression(newTerms, constant), relation);
    }

    if (value is Expression) {
      var newTerms = value.terms.fold(new List<Term>.from(terms),
          (list, t) => list..add(new Term(t.variable, -t.coefficient)));
      return new Constraint(
          new Expression(newTerms, constant - value.constant), relation);
    }

    assert(false);
    return null;
  }

  Constraint operator >=(_EquationMember value) =>
      _createConstraint(value, Relation.greaterThanOrEqualTo);

  Constraint operator <=(_EquationMember value) =>
      _createConstraint(value, Relation.lessThanOrEqualTo);

  operator ==(_EquationMember value) =>
    _createConstraint(value, Relation.equalTo); // analyzer says "Type check failed" // analyzer says "The return type 'Constraint' is not a 'bool', as defined by the method '=='"

  Expression operator +(_EquationMember m) {
    if (m is ConstantMember) {
      return new Expression(new List.from(terms), constant + m.value);
    }

    if (m is Param) {
      return new Expression(
          new List.from(terms)..add(new Term(m.variable, 1.0)), constant);
    }

    if (m is Term) {
      return new Expression(new List.from(terms)..add(m), constant);
    }

    if (m is Expression) {
      return new Expression(
          new List.from(terms)..addAll(m.terms), constant + m.constant);
    }

    assert(false);
    return null;
  }

  Expression operator -(_EquationMember m) {
    if (m is ConstantMember) {
      return new Expression(new List.from(terms), constant - m.value);
    }

    if (m is Param) {
      return new Expression(
          new List.from(terms)..add(new Term(m.variable, -1.0)), constant);
    }

    if (m is Term) {
      return new Expression(new List.from(terms)
        ..add(new Term(m.variable, -m.coefficient)), constant);
    }

    if (m is Expression) {
      var copiedTerms = new List<Term>.from(terms);
      m.terms.forEach(
          (t) => copiedTerms.add(new Term(t.variable, -t.coefficient)));
      return new Expression(copiedTerms, constant - m.constant);
    }

    assert(false);
    return null;
  }

  _EquationMember _applyMultiplicand(double m) {
    var newTerms = terms.fold(new List<Term>(), (list, term) => list
      ..add(new Term(term.variable, term.coefficient * m)));
    return new Expression(newTerms, constant * m);
  }

  _Pair<Expression, double> _findMulitplierAndMultiplicand(_EquationMember m) {
    // At least on of the the two members must be constant for the resulting
    // expression to be linear

    if (!this.isConstant && !m.isConstant) {
      return null;
    }

    if (this.isConstant) {
      return new _Pair(m.asExpression(), this.value);
    }

    if (m.isConstant) {
      return new _Pair(this.asExpression(), m.value);
    }

    assert(false);
    return null;
  }

  _EquationMember operator *(_EquationMember m) {
    _Pair<Expression, double> args = _findMulitplierAndMultiplicand(m);

    if (args == null) {
      throw new ParserException(
          'Could not find constant multiplicand or multiplier', [this, m]);
      return null;
    }

    return args.first._applyMultiplicand(args.second);
  }

  _EquationMember operator /(_EquationMember m) {
    if (!m.isConstant) {
      throw new ParserException(
          'The divisor was not a constant expression', [this, m]);
      return null;
    }

    return this._applyMultiplicand(1.0 / m.value);
  }

  String toString() {
    StringBuffer buffer = new StringBuffer();

    terms.forEach((t) => buffer.write('$t'));

    if (constant != 0.0) {
      buffer.write(constant.sign > 0.0 ? '+' : '-');
      buffer.write(constant.abs());
    }

    return buffer.toString();
  }
}