#
# This script uses RaspberryJamMod's ability to capture all hits, not just sword hits, to provide symmetric
# drawing. Stand at the center of symmetry. By default, you get north-south and east-west mirroring, but you
# can include other transformations on the commandline:
#
#    n : northsouth flip
#    e : east west flip
#    u : up down flip
#    nw : nw - se flip
#    ne : ne - sw flip
#
#    90 : 90, 180 and 270 degrees in the plane
#    180 : 180 degrees in the plane
#
#  You can do translational symmetry with:
#    t N x y z : translate N-1 times by vector (x,y,z)
#
#  The transformations are combined in a complicated fashion to generate the list of all applied transformations. 
#  Effectively this works like this:
#  1. The group generated by all the rotations and flips is put into the list. 
#  2. For each translational symmetry "t N x y z", the list is updated as follows: For each transformation F on the list 
#     so far, the N-1 transformations lambda (x1,y1,z1) : F(x1,y1,z1) + (kx,ky,kz) are added to the list, for k from 1 to N-1.
#  3. The identity transformation is removed.
#
#  Then whenever you draw or erase a block, all the transforms of that block location are are also drawn or erased.
#
#  For instance, specifying n e t 10 0 1 0 will do north-south and east-west mirroring, plus raising up pillars 10 high on
#  top of whatever you draw. On the other hand, doing t 10 1 0 0 t 10 0 1 0 t 10 0 0 1 will draw 10x10x10 cubes wherever you
#  draw.
# 


#
# Code by Alexander Pruss under MIT license.
#

from mcpi.minecraft import Minecraft
import mcpi.block as block
from functools import partial
import sys
import time

xn = ( (1,0,0), 
       (0,1,0),
       (0,0,-1) )
xe = ( (-1,0,0),
       (0,1,0),
       (0,0,1) )
xu = ( (1,0,0),
       (0,-1,0),
       (0,0,-1) )
xnw = ( (0,0,-1),
        (0,1,0),
        (-1,0,0) )
xne = ( (0,0,1),
        (0,1,0),
        (1,0,0 ) )
x90 = ( (0,0,1),
        (0,1,0),
        (-1,0,0) )
xid = ( (1,0,0),
        (0,1,0),
        (0,0,1) )

faces = ( (0,-1,0), (0,1,0), (0,0,-1), (0,0,1), (-1,0,0), (1,0,0), (0,0,0) )

def mulMat(a,b):
    return tuple(tuple(a[i][0]*b[0][j]+a[i][1]*b[1][j]+a[i][2]*b[2][j] for j in range(3)) for i in range(3))

def mulMatVec(a,b):
    return tuple(a[i][0]*b[0]+a[i][1]*b[1]+a[i][2]*b[2] for i in range(3))

def subVec(a,b):
    return tuple(a[i]-b[i] for i in range(3))

def addVec(a,b):
    return tuple(a[i]+b[i] for i in range(3))

x180 = mulMat(x90,x90)

if __name__ == "__main__":
    def copy(v,airOnly=False):
        b = mc.getBlockWithNBT(v)
        if airOnly and b.id != block.AIR.id:
            return
        v1 = addVec(v,(0.5,0.5,0.5))
        for t in transforms:
            mc.setBlockWithNBT(t(v1),b)

    def err():
        mc.postToChat("Invalid symmetry specification. See symmetry.py comments.")
        exit()

    mc = Minecraft()

    playerPos = mc.player.getPos()

    matrices = set()
    translations = []

    if len(sys.argv) <= 1:
        matrices.add(xn)
        matrices.add(xe)

    i = 1
    while i < len(sys.argv):
        opt = sys.argv[i]
        i += 1
        if opt == 't':
            if len(sys.argv) <= i + 3:
                err()
            translations.append((int(sys.argv[i]), float(sys.argv[i+1]), float(sys.argv[i+2]), float(sys.argv[i+3])))
            i += 4
        elif opt == 'n':
            matrices.add(xn)
        elif opt == 'e':
            matrices.add(xe)
        elif opt == 'u':
            matrices.add(xu)
        elif opt == 'nw':
            matrices.add(xnw)
        elif opt == 'ne':
            matrices.add(xne)
        elif opt == '90':
            matrices.add(x90)
        elif opt == '180':
            matrices.add(x180)

    matrices.add(xid)
    old = set()
    while len(old) < len(matrices):
        old = matrices
        matrices = set()
        for a in old:
            for b in old:
                matrices.add(mulMat(a,b))
    matrices.remove(xid)

    transforms = []
    matrixApply = lambda v,mat : addVec(mulMatVec(mat,subVec(v,center)),center)
    for m in matrices:
        transforms.append(partial(matrixApply, mat=m))

    for t in translations:
        add = []
        for k in range(1,t[0]):
            delta = (t[1]*k,t[2]*k,t[3]*k)
            f = partial(addVec,b=delta)
            add.append(f)
            for g in transforms:
                add.append(lambda v,f=f,g=g : f(g(v)))
        transforms += add

    center = tuple(0.5 * round(2 * x) for x in playerPos)

#    if (len(matrices) > 0):
#        mc.conn.send("world.spawnParticle", "footstep", center, 0.0,0.0,0.0, 0, 1)

    mc.conn.send("events.setting","restrict_to_sword",0)
    mc.conn.send("events.setting","detect_left_click",1)

    mc.postToChat("Will be drawing {} copies".format(1+len(transforms)))

    mc.events.clearAll()

    while True:
        hits = mc.events.pollBlockHits()
        time.sleep(0.25)
        for h in hits:
            v = tuple(x for x in h.pos)
            copy(v,airOnly=True)
            copy(addVec(v,faces[h.face]))
