import sys

import numpy as np

from functools import wraps
import itertools

import interp
import logging
log = logging.getLogger('interp')

def get_phis(X, R):
  """
    The get_phis function is used to get barycentric coordonites for a point on
    a triangle or tetrahedron:


    in 2D:

    X -- the destination point (2D)
         X = [0,0]
    r -- the three points that make up the containing triangular simplex (2D)
         r = [[-1, -1], [0, 2], [1, -1]]

    this will return [0.333, 0.333, 0.333]


    in 3D:

    X -- the destination point (3D)
         X = [0,0,0]
    R -- the four points that make up the containing simplex, tetrahedron (3D)
         R = [
               [0.0, 0.0, 1.0],
               [0.94280904333606508, 0.0, -0.3333333283722672],
               [-0.47140452166803232, 0.81649658244673617, -0.3333333283722672],
               [-0.47140452166803298, -0.81649658244673584, -0.3333333283722672],
             ]

    this will return [0.25, 0.25, 0.25, 0.25]
  """

  # baker: eq 7
  # TODO: perhaps also test len(R[0])  .. ?
  if len(X) == 2:
    log.debug("running 2D")
    A = np.array([
                  [      1,       1,       1],
                  [R[0][0], R[1][0], R[2][0]],
                  [R[0][1], R[1][1], R[2][1]],
                  ])
    b = np.array([    1,
                    X[0],
                    X[1]
                ])
  elif len(X) == 3:
    log.debug("running 3D")
    A = np.array([
                  [      1,       1,       1,       1 ],
                  [R[0][0], R[1][0], R[2][0], R[3][0]],
                  [R[0][1], R[1][1], R[2][1], R[3][1]],
                  [R[0][2], R[1][2], R[2][2], R[3][2]],
                  ])
    b = np.array([    1,
                    X[0],
                    X[1],
                    X[2]
                ])
  else:
    raise Exception("inapropriate demension on X")

  try:
    phi = np.linalg.solve(A,b)
  except np.linalg.LinAlgError as e:
    msg = "calculation of phis yielded a linearly dependant system (%s)" % e
    log.error(msg)
    # raise Exception(msg)
    phi = np.dot(np.linalg.pinv(A), b)

  log.debug("phi: %s", phi)

  return phi

def qlinear(X, R):
  """
    this calculates the linear portion of q from R to X

    also, this is baker eq 3

    X = destination point
    R = a inter.grid object; must have R.points and R.q
  """

  phis = get_phis(X, R.verts)
  qlin = np.sum([q_i * phi_i for q_i, phi_i in zip(R.q, phis)])

  log.debug("phis: %s", phis)
  log.debug("qlin: %s", qlin)

  return phis, qlin

def get_error(phi, R, S, order = 2):
  #TODO: change the equation names in the comments
  B = [] # baker eq 9
  w = [] # baker eq 11

  cur_pattern  = pattern(len(phi), order)
  log.info("pattern: %s" % cur_pattern)

  for (s,q) in zip(S.verts, S.q):
    cur_phi, cur_qlin = qlinear(s, R)
    l = []
    for i in cur_pattern:
      cur_sum = cur_phi[i[0]]
      for j in i[1:]:
        cur_sum *= cur_phi[j]
      l.append(cur_sum)

    B.append(l)
    w.append(q - cur_qlin)

  log.info("B: %s" % B)
  log.info("w: %s" % w)


  B = np.array(B)
  w = np.array(w)

  A = np.dot(B.T, B)
  b = np.dot(B.T, w)

  try:
    abc = np.linalg.solve(A,b)
  except np.linalg.LinAlgError as e:
    log.error("linear calculation went bad, resorting to np.linalg.pinv: %s" % e)
    abc = np.dot(np.linalg.pinv(A), b)

  error_term = 0.0
  for (a, i) in zip(abc, cur_pattern):
    cur_sum = a
    for j in i:
      cur_sum *= phi[j]
    error_term += cur_sum

  log.debug("error_term: %s" % error_term)
  return error_term, abc

def run_baker(X, R, S, order=2):
  """
    This is the main function to call to get an interpolation to X from the
    input meshes

    X -- the destination point (2D)
         X = [0,0]

    R = Simplex
    S = extra points
  """
  log.debug("order = %d" % order)
  log.debug("extra points = %d" % len(S.verts))

  answer = {
             'qlin': None,
             'error': None,
             'final': None,
            }
  # calculate values only for the simplex triangle
  phi, qlin = qlinear(X, R)

  if order == 1:
    answer['qlin'] = qlin
    answer['final'] = qlin
    return answer
  elif order in xrange(2,11):
    error_term, abc = get_error(phi, R, S, order)
  else:
    raise Exception('unsupported order "%d" for baker method' % order)

  q_final = qlin + error_term

  answer['qlin' ] =  qlin
  answer['error'] =  error_term
  answer['final'] =  q_final
  answer['abc'  ] =  abc

  log.debug(answer)

  return answer


def memoize(f):
  """
    for more information on what I'm doing here,
    please read:

    http://en.wikipedia.org/wiki/Memoize
  """
  cache = {}
  @wraps(f)
  def memf(simplex_size, nu):
    x = (simplex_size, nu)
    if x not in cache:
      log.debug("adding to cache: %s", x)
      cache[x] = f(simplex_size, nu)
    return cache[x]
  return memf


@memoize
def pattern(simplex_size, nu):
  """
    This function returns the pattern requisite to compose the error
    approximation function, and the matrix B.
  """
  log.debug("pattern: simplex: %d, order: %d" % (simplex_size, nu))

  r = []
  for i in itertools.product(xrange(simplex_size), repeat = nu):
    if len(set(i)) !=1:
      r.append(tuple(sorted(i)))
  unique_r = list(set(r))
  return unique_r


if __name__ == '__main__':
  print len(pattern(3, 2)), pattern(3, 2)
  print len(pattern(4, 2)), pattern(4, 2)

  print len(pattern(3, 3)), pattern(3, 3)
  print len(pattern(4, 3)), pattern(4, 3)

  print len(pattern(3, 4)), pattern(3, 4)
  print len(pattern(4, 4)), pattern(4, 4)

  print len(pattern(3, 2)), pattern(3, 2)
  print len(pattern(4, 2)), pattern(4, 2)

  print len(pattern(3, 3)), pattern(3, 3)
  print len(pattern(4, 3)), pattern(4, 3)

  print len(pattern(3, 4)), pattern(3, 4)
  print len(pattern(4, 4)), pattern(4, 4)

  print len(pattern(3, 2)), pattern(3, 2)
  print len(pattern(4, 2)), pattern(4, 2)

  print len(pattern(3, 3)), pattern(3, 3)
  print len(pattern(4, 3)), pattern(4, 3)

  print len(pattern(3, 4)), pattern(3, 4)
  print len(pattern(4, 4)), pattern(4, 4)

  print len(pattern(3, 2)), pattern(3, 2)
  print len(pattern(4, 2)), pattern(4, 2)

  print len(pattern(3, 3)), pattern(3, 3)
  print len(pattern(4, 3)), pattern(4, 3)

  print len(pattern(3, 4)), pattern(3, 4)
  print len(pattern(4, 4)), pattern(4, 4)
