#include <QVector2D.h>

#include <math.h>
#include "game.h"

#pragma once

/**
  * Ported to C++ (Qt for vector class) from original python code:
  *
# An example implementation of the algorithm described at
# http://www.niksula.cs.hut.fi/~hkankaan/Homepages/metaballs.html
#
# The code contains some references to the document above, in form
# ### Formula (x)
# to make clear where each of the formulas is implemented (and what
# it looks like in Python)
#
# Since Python doesn't have an in-built vector type, I used complex
# numbers for coordinates (x is the real part, y is the imaginary part)
#
# Made by Hannu Kankaanpää. Use for whatever you wish.
*/


class Ball
{
public:
    int sharedBall;
    QVector2D pos;
    QVector2D pos0;
    QVector2D edgePos;
    bool tracking;
    float size;


    VGubyte commands[1024];
    VGfloat vertices[1024];

    int verticeCount;

    /**Single metaball. */
    Ball(QVector2D _pos0, float size0) : pos(_pos0), size(size0), tracking(false)
    {
    }

};

#if 0




/*
def main():
    # This is where the execution starts.
    # First initialize the screen.
    pygame.init()
    screen = pygame.display.set_mode((640, 480))

    # Then create a couple of balls
    balls = [Ball(350 + 100j, size=3),
             Ball(20  + 200j, size=2),
             Ball(280 + 140j, size=4),
             Ball(400 + 440j, size=3)]

    # And a metaball system (see below for class definition)
    mbs = MetaballSystem(balls, goo=2.0, threshold=0.0004, screen=screen)

    while True:
        # clear screen with black
        screen.fill((0, 0, 0))

        # move ball number 0 according to mouse position
        if pygame.mouse.get_focused():
            balls[0].pos = complex(*pygame.mouse.get_pos())

        # Draw the balls.
        # Try different methods: euler, rungeKutta2 and rungeKutta4
        drawBalls(differentialMethod=rungeKutta2, metaballSystem=mbs,
                  step=20, screen=screen)
        pygame.display.flip()

        # exit when esc is pressed
        for event in pygame.event.get():
            if event.type == QUIT:
                return
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    return

*/


def drawBalls(differentialMethod, metaballSystem, step, screen):
    mbs = metaballSystem
    balls = mbs.balls

    # First, track the border for all balls and store
    # it to pos0 and edgePos. The latter will move along the border,
    # pos0 stays at the initial coordinates.
    for ball in balls:
        ball.pos0 = mbs.trackTheBorder(ball.pos + 1j)
        ball.edgePos = ball.pos0
        ball.tracking = True

    loopIndex = 0
    while loopIndex < 200:
        loopIndex += 1
        for ball in balls:
            if not ball.tracking:
                continue

            # store the old coordinates
            old_pos = ball.edgePos

            # walk along the tangent, using chosen differential method
            ball.edgePos = differentialMethod(ball.edgePos, step, mbs.calcTangent)

            # correction step towards the border
            ball.edgePos, tmp = mbs.stepOnceTowardsBorder(ball.edgePos)

            pygame.draw.line(screen, (255, 255, 255),
                             (old_pos.real, old_pos.imag),
                             (ball.edgePos.real, ball.edgePos.imag))

            # check if we've gone a full circle or hit some other
            # edge tracker
            for ob in balls:
                if (ob is not ball or loopIndex > 3) and \
                   abs(ob.pos0 - ball.edgePos) < step:
                    ball.tracking = False

        tracking = 0
        for ball in balls:
            if ball.tracking:
                tracking += 1
        if tracking == 0:
            break



#endif


/**A class that manages the metaballs and can calculate
 several useful values from the system.
*/
class MetaballSystem
{
public:
    float minSize;
    int ballCount;
    Ball *balls;
    float goo;
    float threshold;

    MetaballSystem(int ballCount0, Ball *balls0, float goo0, float threshold0) : balls(balls0), ballCount(ballCount0), goo(goo0), threshold(threshold0)
    {
        minSize=MAXFLOAT;
        for(int i=0;i<ballCount;i++)
        {
            if(balls[i].size<minSize)
            {
                minSize=balls[i].size;
            }
        }
    }

    /**Return the metaball field's force at point 'pos'.*/

    float calcForce(QVector2D pos)
    {
          float force = 0;
          for(int i=0;i<ballCount;i++)
          {
              //### Formula (1)
              // TODO div = abs(ball.pos - pos)**self.goo

              QVector2D v=(balls[i].pos-pos);
              float div = (goo==2.0 ? v.lengthSquared() : powf(v.length(), goo));
              if(div != 0) //# to prevent division by zero
                  force += balls[i].size / div;
              else
                  force += 10000; //#"big number"
          }
          return force;
    }

    /**Return a normalized (magnitude = 1) normal at point 'pos'.*/
      QVector2D calcNormal(QVector2D pos)
      {
          QVector2D np(0,0);

          for(int i=0;i<ballCount;i++)
          {
              //### Formula (3)
              //div = abs(ball.pos - pos)**(2 + self.goo)
              //np += -self.goo * ball.size * (ball.pos - pos) / div

              QVector2D v=(balls[i].pos-pos);
              //float div = pow(v.length(), 2+goo);
              float div = v.lengthSquared() * (goo==2.0 ? v.lengthSquared() : powf(v.length(), goo));

              np += -goo * balls[i].size * v / div;

          }
          return np.normalized();

      }

    /**Return a normalized (magnitude = 1) tangent at point 'pos'.*/
      QVector2D calcTangent(QVector2D pos)
      {
          QVector2D np = calcNormal(pos);
          //### Formula (7)
          return QVector2D(-np.y(), np.x());
      }

      /**Step once towards the border of the metaball field, return
         new coordinates and force at old coordinates.
      */
      float stepOnceTowardsBorder(QVector2D &pos)
      {
          float force = calcForce(pos);
          QVector2D np = calcNormal(pos);
          //### Formula (5)
          float stepsize = powf(minSize / threshold, 1 / goo) -
                     powf(minSize / force, 1 / goo) + 0.01;
          pos += np * stepsize;
          return force;
      }

      /**Track the border of the metaball field and return new
         coordinates.
      */
      QVector2D trackTheBorder(QVector2D pos)
      {
          float force = 9999999;
          //# loop until force is weaker than the desired threshold

          int foo=0;
          while (force > threshold)
          {
              foo++;
              if(foo>100) break;
              force = stepOnceTowardsBorder(pos);
          }
          return pos;
      }



/**Euler's method.
   The most simple way to solve differential systems numerically.
*/
QVector2D eulerTangent(QVector2D pos, float h)
{
    return pos + h * calcTangent(pos);
}


/**Runge-Kutta 2 (=mid-point).
   This is only a little more complex than the Euler's method,
   but significantly better.
*/
QVector2D rungeKutta2Tangent(QVector2D pos, float h)
{
    return pos + h * calcTangent(pos + calcTangent(pos) * h / 2);
}

};
