#include "LatticeWorld.h"
#include <stdlib.h>
#include <time.h>
#include <string.h>


static uint8_t RuleTable[256];

static void collide(LatticeWorld *world);
static void transport(LatticeWorld *world);
static void copyEdges(LatticeWorld *world);

void UpdateWorld(LatticeWorld *world) {
    collide(world);
    transport(world);
    world->currentSite = (world->currentSite + 1) % 2;
}

static void collide(LatticeWorld *world)
{
    unsigned int i;
    uint8_t *site = world->sites[world->currentSite];
    for(i = 0; i < world->width * world->height; ++i) {
        /* use a random number to handle the nondeterministic colissions */
        uint8_t chooser = rand() & 0x80;
        site[i] = RuleTable[chooser | site[i]];
    }
}

// TODO: if I ever use this for anything serious I need to write some unit
// tests to make sure everything is copying and transporting correctly
static void transport(LatticeWorld *world)
{
    uint8_t *site = world->sites[world->currentSite];
    uint8_t *newSite = world->sites[(world->currentSite + 1) % 2];
    // bot represents the index of the bottom row
    unsigned int bot = world->height - 1;
    unsigned int w = world->width;
    unsigned int i, i0, i1, i2, i3, i4, i5;
    int x, y;

    copyEdges(world);

    // now we can actually do transport on the world
    i = w + 1;
    for(y = 1; y < bot; y += 2) {
        for(x = 1; x < w-1; ++x) {
            i0 = y * w + (x+1);
            i1 = (y+1) * w + (x+1);
            i2 = (y+1) * w + x;
            i3 = y * w + (x-1);
            i4 = (y-1) * w + x;
            i5 = (y-1) * w + (x+1);
            newSite[i] =
                (site[i0] & 0x01) |
                (site[i1] & 0x02) |
                (site[i2] & 0x04) |
                (site[i3] & 0x08) |
                (site[i4] & 0x10) |
                (site[i5] & 0x20);
            ++i;
        }
        i += (w + 2);
    }
    i = 2 * w + 1;
    for(y = 2; y < bot; y += 2) {
        for(x = 1; x < w-1; ++x) {
            i0 = y * w + (x+1);
            i1 = (y+1) * w + x;
            i2 = (y+1) * w + (x-1);
            i3 = y * w + (x-1);
            i4 = (y-1) * w + (x-1);
            i5 = (y-1) * w + x;
            newSite[i] =
                (site[i0] & 0x01) |
                (site[i1] & 0x02) |
                (site[i2] & 0x04) |
                (site[i3] & 0x08) |
                (site[i4] & 0x10) |
                (site[i5] & 0x20);
            ++i;
        }
        i += (w + 2);
    }
}

static void copyEdges(LatticeWorld *world) {
    uint8_t *site = world->sites[world->currentSite];
    uint8_t *newSite = world->sites[(world->currentSite + 1) % 2];
    // bot represents the index of the bottom row
    unsigned int bot = world->height - 1;
    unsigned int w = world->width;
    unsigned int y;
    // copy the boundaries of the world into the opposite edges

    // top edge
    memcpy(&site[1], &site[(bot-1) * w + 1], w-2);
    // bottom edge
    memcpy(&site[bot * w + 1], &site[w+1], w-2);
    for(y = 1; y < bot; ++y) {
        // left edge
        site[y * w] = site[(y+1) * w - 2];
        // right edge
        site[(y+1) * w - 1] = site[y * w + 1];
    }
    // now the corners

    //top left
    site[0] = site[bot * w - 2];
    // top right
    site[w - 1] = site[(bot - 1) * w + 1];
    // bottom left
    site[bot * w] = site[2 * w - 2];
    // bottom right
    site[bot * w - 1] = site[w + 1];
}

/* The rightmost 6 bits are the incoming particles for the site, starting with
 * the right and going clockwise. Note that after the collision step the
 * particles are actually reversed, so that in the transport step they can just
 * be copied.
 *
 * For the nondeterminstic rules, the 2 options are stored in the table as
 * RuleTable[x] and RuleTable[0x80 | x]
 */

void InitRules(void){
    unsigned int i;
    for(i = 0; i < sizeof(RuleTable) / 2; ++i) {
        RuleTable[i] = i;
        RuleTable[0x80 | i] = i;
    }
    // 2 particles colliding will scatter
    RuleTable[0x09] = 0x12;
    RuleTable[0x80 | 0x09] = 0x24;
    RuleTable[0x12] = 0x09;
    RuleTable[0x80 | 0x12] = 0x24;
    RuleTable[0x24] = 0x09;
    RuleTable[0x80 | 0x24] = 0x12;

    // 3 particles colliding get a 60deg rotation
    RuleTable[0x15] = 0x2a;
    RuleTable[0x80 | 0x15] = 0x2a;
    RuleTable[0x2a] = 0x15;
    RuleTable[0x80 | 0x2a] = 0x15;
}
