using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;

namespace AutoDiff
{
    /// <summary>
    /// A collection of static methods to build new terms
    /// </summary>
    public static class TermBuilder
    {
        /// <summary>
        /// Builds a new constant term.
        /// </summary>
        /// <param name="value">The constant value</param>
        /// <returns>The constant term.</returns>
        public static Term Constant(double value)
        {
            Contract.Ensures(Contract.Result<Term>() != null);

            if (value == 0)
                return new Zero();
            else
                return new Constant(value);
        }

        /// <summary>
        /// Builds a sum of given terms.
        /// </summary>
        /// <param name="terms">The collection of terms in the sum.</param>
        /// <returns>A term representing the sum of the terms in <paramref name="terms"/>.</returns>
        public static Sum Sum(IEnumerable<Term> terms)
        {
            Contract.Requires(terms.Where(term => !(term is Zero)).Count() >= 2); // require at-least two non-zero terms.
            Contract.Requires(Contract.ForAll(terms, term => term != null));
            Contract.Ensures(Contract.Result<Sum>() != null);

            terms = terms.Where(term => !(term is Zero));
            return new Sum(terms);
        }

        /// <summary>
        /// Builds a sum of given terms.
        /// </summary>
        /// <param name="v1">The first term in the sum</param>
        /// <param name="v2">The second term in the sum</param>
        /// <param name="rest">The rest of the terms in the sum.</param>
        /// <returns>A term representing the sum of <paramref name="v1"/>, <paramref name="v2"/> and the terms in <paramref name="rest"/>.</returns>
        public static Sum Sum(Term v1, Term v2, params Term[] rest)
        {
            Contract.Requires(v1 != null);
            Contract.Requires(v2 != null);
            Contract.Requires(Contract.ForAll(rest, term => term != null));
            Contract.Ensures(Contract.Result<Sum>() != null);

            var allTerms = new Term[] { v1, v2 }.Concat(rest);
            return Sum(allTerms);
        }

        /// <summary>
        /// Builds a product of given terms.
        /// </summary>
        /// <param name="v1">The first term in the product</param>
        /// <param name="v2">The second term in the product</param>
        /// <param name="rest">The rest of the terms in the product</param>
        /// <returns>A term representing the product of <paramref name="v1"/>, <paramref name="v2"/> and the terms in <paramref name="rest"/>.</returns>
        public static Term Product(Term v1, Term v2, params Term[] rest)
        {
            Contract.Requires(v1 != null);
            Contract.Requires(v2 != null);
            Contract.Requires(Contract.ForAll(rest, term => term != null));
            Contract.Ensures(Contract.Result<Term>() != null);

            var result = new Product(v1, v2);
            foreach (var item in rest)
                result = new Product(result, item);

            return result;
        }

        /// <summary>
        /// Builds a power terms given a base and a constant exponent
        /// </summary>
        /// <param name="t">The power base term</param>
        /// <param name="power">The exponent</param>
        /// <returns>A term representing <c>t^power</c>.</returns>
        public static Term Power(Term t, double power)
        {
            Contract.Requires(t != null);
            Contract.Ensures(Contract.Result<Term>() != null);

            return new ConstPower(t, power);
        }

        /// <summary>
        /// Builds a power term given a base term and an exponent term.
        /// </summary>
        /// <param name="baseTerm">The base term</param>
        /// <param name="exponent">The exponent term</param>
        /// <returns></returns>
        public static Term Power(Term baseTerm, Term exponent)
        {
            Contract.Requires(baseTerm != null);
            Contract.Requires(exponent != null);
            Contract.Ensures(Contract.Result<Term>() != null);

            return new TermPower(baseTerm, exponent);
        }

        /// <summary>
        /// Builds a term representing the exponential function e^x.
        /// </summary>
        /// <param name="arg">The function's exponent</param>
        /// <returns>A term representing e^arg.</returns>
        public static Term Exp(Term arg)
        {
            Contract.Requires(arg != null);
            Contract.Ensures(Contract.Result<Term>() != null);

            return new Exp(arg);
        }

        /// <summary>
        /// Builds a term representing the natural logarithm.
        /// </summary>
        /// <param name="arg">The natural logarithm's argument.</param>
        /// <returns>A term representing the natural logarithm of <paramref name="arg"/></returns>
        public static Term Log(Term arg)
        {
            Contract.Requires(arg != null);
            Contract.Ensures(Contract.Result<Term>() != null);

            return new Log(arg);
        }

        /// <summary>
        /// Constructs a 2D quadratic form given the vector components x1, x2 and the matrix coefficients a11, a12, a21, a22.
        /// </summary>
        /// <param name="x1">First vector component</param>
        /// <param name="x2">Second vector component</param>
        /// <param name="a11">First row, first column matrix component</param>
        /// <param name="a12">First row, second column matrix component</param>
        /// <param name="a21">Second row, first column matrix component</param>
        /// <param name="a22">Second row, second column matrix component</param>
        /// <returns>A term describing the quadratic form</returns>
        public static Term QuadForm(Term x1, Term x2, Term a11, Term a12, Term a21, Term a22)
        {
            Contract.Requires(x1 != null);
            Contract.Requires(x2 != null);
            Contract.Requires(a11 != null);
            Contract.Requires(a12 != null);
            Contract.Requires(a21 != null);
            Contract.Requires(a22 != null);
            Contract.Ensures(Contract.Result<Term>() != null);

            return Sum(a11 * Power(x1, 2), (a12 + a21) * x1 * x2, a22 * Power(x2, 2));
        }
    }
}