#!/usr/bin/python3
# -*- coding: utf-8 -*-
# -*- mode: Python -*-


'''

trckr
Work with clmtrackr-dev and www/trckr/trckr.html
v0.1.0


LICENCE : CC
by Sam Neurohack

To test the examples locally, you need to run a local server. 
One easy way to do this is to install http-server, a small node.js utility: 
npm install -g http-server. 

Then run http-server in the root of clmtrackr and go to 

- read a video :
http://localhost:8080/examples/trckr.html
- webcam


clmtrackr :
https://github.com/auduno/clmtrackr/tree/dev/examples


'''
import sys
import os
print()
ljpath = r'%s' % os.getcwd().replace('\\','/')

# import from shell
sys.path.append(ljpath +'/../libs3/')
#import from LJ
import log

print ("")

log.infog("Trckr Plugin v0.1")


sys.path.append('../libs3')
sys.path.append(ljpath +'/../../libs3')

from OSC3 import OSCServer, OSCClient, OSCMessage
import lj23layers as lj
import redis
import math
import time
import argparse

OSCinPort = 8017

print ("")
print ("Arguments parsing if needed...")
argsparser = argparse.ArgumentParser(description="trckr example for LJ")
argsparser.add_argument("-r","--redisIP",help="IP of the Redis server used by LJ (127.0.0.1 by default) ",type=str)
argsparser.add_argument("-s","--scene",help="LJ scene number (0 by default)",type=int)
#argsparser.add_argument("-l","--laser",help="Laser number to be displayed (0 by default)",type=int)
argsparser.add_argument("-v","--verbose",help="Verbosity level (0 by default)",type=int)
argsparser.add_argument("-m","--myIP",help="Local IP (127.0.0.1 by default) ",type=str)

args = argsparser.parse_args()

if args.scene:
  ljscene = args.scene
else:
  ljscene = 0
'''
if args.laser:
  plnumber = args.laser
else:
  plnumber = 0
'''

# Redis Computer IP
if args.redisIP  != None:
  redisIP  = args.redisIP
else:
  redisIP = '127.0.0.1'

print("redisIP",redisIP)

# myIP
if args.myIP  != None:
  myIP  = args.myIP
else:
  myIP = '127.0.0.1'

print("myIP",myIP)

if args.verbose:
  debug = args.verbose
else:
  debug = 0

# Useful variables init.
white = lj.rgb2int(255,255,255)
red = lj.rgb2int(255,0,0)
blue = lj.rgb2int(0,0,255)
green = lj.rgb2int(0,255,0)

# 3D to 2D projection parameters
fov = 256
viewer_distance = 2.2

width = 800
height = 600
centerX = width / 2
centerY = height / 2

TrckrPts = [[0,0]] * 70



#
# LJ inits
# 

layer = 0

# Setup LJ library mandatory properties for this layerugin
lj.Config(redisIP, ljscene, "trckr")

# Define properties for each drawn "element" : name, intensity, active, xy, color, red, green, blue, layer , closed
FaceForm = lj.FixedObject('Face', True, 255, [], red, 255, 0, 0, layer , False)

# 'Destination' for given layer : name, number, active, layer , scene, laser
Dest0 = lj.DestObject('0', 0, True, 0 , 0, 0)   # Dest0 will send layer 0 points to scene 0, laser 0 



#
# OSC
#

oscserver = OSCServer( (myIP, OSCinPort) )
oscserver.timeout = 0


# this method of reporting timeouts only works by convention
# that before calling handle_request() field .timed_out is 
# set to False
def handle_timeout(self):
    self.timed_out = True

# funny python's way to add a method to an instance of a class
import types
oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver)


# OSC callbacks

# /trckr/ljscene
def OSCljscene(path, tags, args, source):

    print("Got /trckr/ljscene with value", args[0])
    lj.WebStatus("trckr to virtual "+ str(args[0]))
    ljscene = args[0]
    lj.Ljscene(ljscene)

# /trckr/layer
def OSClayer(path, tags, args, source):

    print()
    print()
    print("Got /trckr/layer with value", args[0])
    lj.WebStatus("trckr to layer "+ str(args[0]))
    FaceForm.layer = int(args[0])
    Dest0.layer = int(args[0])
    Dest0.laser = int(args[0])

# /trckr/frame
def OSCtrckr(path, tags, args, source):
    global TrckrPts

    #print("trckr got frame", args[0])
    if debug != 0: 
      print("trckr plugin got frame", args[0])
      print(len(args),"args", args)
    counter =0
    TrckrPts = []

    for dot in range(1,len(args)-1,2):

      TrckrPts.append([float(args[dot]), float(args[dot+1])])
    print(TrckrPts)

# /trckr/frame layernumber framenumber points
def OSCTrckrframe(path, tags, args, source):
    global TrckrPts

    if debug != 0: 
      print("trckr plugin got frame", args[1], "for layer", args[0], "with path", path)
      print(len(args),"args", args)

    TrckrPts[args[0]] = []

    for dot in range(2,len(args)-1,2):
      TrckrPts[args[0]].append([float(args[dot]), float(args[dot+1])])




#/trckr/color/0 red ?
def OSColor():
    global FaceForm

    if debug != 0: 
        print("trckr color got ", args[1], "for layer", args[0], "with path", path)
        print(len(args),"args", args)
    FaceForm.color = args[1]


# default handler
def OSChandler(path, tags, args, source):

    oscaddress = ''.join(path.split("/"))
    print()
    print("trckr default OSC Handler :", path, "from Client :" + str(source[0]))


#
# Face Tracking 
#

# get absolute face position points 
def getPART(pose_points):

    dots = []
    for dot in pose_points:

        dots.append((TrckrPts[dot][0], TrckrPts[dot][1],0))

    return dots


# Face keypoints
def face():
    pose_points = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]
    return getPART(pose_points)

def browL():
    pose_points = [15,16,17,18]
    return getPART(pose_points)

def browR():
    pose_points = [22,21,20,19]
    return getPART(pose_points)

def eyeR():
    pose_points = [25,64,24,63,23,66,26,65,25]
    return getPART(pose_points)

def eyeL():
    pose_points = [28,67,29,68,30,69,31,28]
    return getPART(pose_points)

def pupR():
    pose_points = [27]
    return getPART(pose_points)

def pupL():
    pose_points = [32]
    return getPART(pose_points)


def nose1():
    pose_points = [62,41,33]
    return getPART(pose_points)

def nose2():
    pose_points = [40,39,38,43,37,42,36,35,34]
    return getPART(pose_points)

def mouth():
    pose_points = [50,49,48,47,46,45,44,55,54,53,52,51,50]
    return getPART(pose_points)

def mouthfull():
    pose_points = [50,49,48,47,46,45,44,55,54,53,52,51,50,59,60,61,44,56,57,58,50]
    return getPART(pose_points)




def Proj(x,y,z,angleX,angleY,angleZ):

    rad = angleX * math.pi / 180
    cosa = math.cos(rad)
    sina = math.sin(rad)
    y2 = y
    y = y2 * cosa - z * sina
    z = y2 * sina + z * cosa
    
    rad = angleY * math.pi / 180
    cosa = math.cos(rad)
    sina = math.sin(rad)
    z2 = z
    z = z2 * cosa - x * sina
    x = z2 * sina + x * cosa

    rad = angleZ * math.pi / 180
    cosa = math.cos(rad)
    sina = math.sin(rad)
    x2 = x
    x = x2 * cosa - y * sina
    y = x2 * sina + y * cosa


    """ Transforms this 3D point to 2D using a perspective projection. """
    factor = fov / (viewer_distance + z)
    x = x * factor + centerX
    y = - y * factor + centerY
    return (x,y)


#
# Main 
#

def Run():


  Facepts = []
  counter =0
  lj.WebStatus("trckr")
  lj.SendLJ("/trckr/start 1")

  # OSC Server callbacks
  print("Starting OSC server at",myIP," port",OSCinPort,"...")
  oscserver.addMsgHandler( "/trckr/ljscene", OSCljscene)
  oscserver.addMsgHandler( "/trckr/layer", OSClayer)
  oscserver.addMsgHandler( "/trckr/frame", OSCtrckr)
  oscserver.addMsgHandler( "/trckr/color", OSColor)


  # Add OSC generic layerugins commands : 'default", /ping, /quit, /pluginame/obj, /pluginame/var, /pluginame/adddest, /pluginame/deldest
  lj.addOSCdefaults(oscserver)
  oscserver.addMsgHandler( "default", OSChandler )
  try:

    while lj.oscrun:

      lj.OSCframe()
          
      #print("browL", browL(), "browR", browR(), "nose1", nose1(), "mouth", mouth())
      lj.rPolyLineOneColor(browL(), c = FaceForm.color, layer = FaceForm.layer, closed = FaceForm.closed, xpos = -300, ypos = -300, resize = 3.6, rotx = 0, roty = 0, rotz = 0)
      lj.rPolyLineOneColor(eyeL(), c = FaceForm.color, layer = FaceForm.layer, closed = FaceForm.closed, xpos = -300, ypos = -300, resize = 3.6, rotx = 0, roty = 0, rotz = 0)
      lj.rPolyLineOneColor(browR(), c = FaceForm.color, layer = FaceForm.layer, closed = FaceForm.closed, xpos = -300, ypos = -300, resize = 3.6, rotx = 0, roty = 0, rotz = 0)
      lj.rPolyLineOneColor(eyeR(), c = FaceForm.color, layer = FaceForm.layer, closed = FaceForm.closed, xpos = -300, ypos = -300, resize = 3.6, rotx = 0, roty = 0, rotz = 0)
      lj.rPolyLineOneColor(pupL(), c = FaceForm.color, layer = FaceForm.layer, closed = FaceForm.closed, xpos = -300, ypos = -300, resize = 3.6, rotx = 0, roty = 0, rotz = 0)
      lj.rPolyLineOneColor(pupR(), c = FaceForm.color, layer = FaceForm.layer, closed = FaceForm.closed, xpos = -300, ypos = -300, resize = 3.6, rotx = 0, roty = 0, rotz = 0)
      lj.rPolyLineOneColor(nose1(), c = FaceForm.color, layer = FaceForm.layer, closed = FaceForm.closed, xpos = -300, ypos = -300, resize = 3.6, rotx = 0, roty = 0, rotz = 0)
      lj.rPolyLineOneColor(nose2(), c = FaceForm.color, layer = FaceForm.layer, closed = FaceForm.closed, xpos = -300, ypos = -300, resize = 3.6, rotx = 0, roty = 0, rotz = 0)
      lj.rPolyLineOneColor(mouthfull(), c = FaceForm.color, layer = FaceForm.layer, closed = FaceForm.closed, xpos = -300, ypos = -300, resize = 3.6, rotx = 0, roty = 0, rotz = 0)

      lj.DrawDests()
  
      time.sleep(0.005)
  
      counter += 1
      if counter > 360:
        counter = 0

  except KeyboardInterrupt:
    pass

  # Gently stop on CTRL C

  finally:

    lj.ClosePlugin()


Run()
'''
//
// a template for receiving face tracking osc messages from
// Kyle McDonald's trckr https://github.com/kylemcdonald/ofxFaceTracker
//
// 2012 Dan Wilcox danomatika.com
// for the IACD Spring 2012 class at the CMU School of Art
//
// adapted from from Greg Borenstein's 2011 example
// http://www.gregborenstein.com/
// https://gist.github.com/1603230
//

//Xavier Apostol
//Generative Faces: Plotter Project Concept

import oscP5.*;
OscP5 oscP5;

// num faces found
int found;

// pose
float poseScale;
PVector posePosition = new PVector();
PVector poseOrientation = new PVector();

// gesture
float mouthHeight;
float mouthWidth;
float eyeLeft;
float eyeRight;
float eyebrowLeft;
float eyebrowRight;
float jaw;
float nostrils;

float sz = 1;
float spacing = 100;
float genSz = spacing/4;
float fcOff = genSz/2;

//Initialization of Colors
float R = random(255);
float G = random(255);
float B = random(255);

//Initialization of Head
float rotInt = 15;
float hdX = cos(sz) + random(genSz, 3*genSz);
float hdY = sin(sz) + random(genSz, 3*genSz);
float rotAngle = random(-rotInt,rotInt);

//Initialization of Eyes
float lEyeX1 = sin(sz*0) + random(genSz);
float lEyeY1 = cos(sz*0) + random(genSz);
float rEyeX1 = sin(sz*0) + random(genSz);
float rEyeY1 = cos(sz*0) + random(genSz);
float lEyeX2 = sin(sz*1) + random(genSz);
float lEyeY2 = cos(sz*1) + random(genSz);
float rEyeX2 = sin(sz*1) + random(genSz);
float rEyeY2 = cos(sz*1) + random(genSz);
float ranREye = random(7, 9);
float ranLEye = random(7, 9);

//Initialization of Mouth
float mthX = cos(sz) + random(genSz);
float mthY = sin(sz) + random(genSz);
float ranM = random(-0.1, 1.5);

//Initialization of Spine
float hdOffset = hdY/1.5;
float spineSz = random(genSz/2);
float spXOff1 = random(-8, 8);
float spYOff1 = hdOffset + random(genSz/3);
float spXOff2 = random(-8, 8)+spXOff1;
float spYOff2 = random(genSz/3)+spYOff1;
float spXOff3 = random(-8, 8)+spXOff2;
float spYOff3 = random(genSz/3)+spYOff2;
float spXOff4 = random(-8, 8)+spXOff3;
float spYOff4 = random(genSz/3)+spYOff3;
float spXOff5 = random(-8, 8)+spXOff4;
float spYOff5 = random(genSz/3)+spYOff4;

void setup() {
  size(800, 600, OPENGL);
  frameRate(30);

  oscP5 = new OscP5(this, 8338);
  oscP5.plug(this, "found", "/found");
  oscP5.plug(this, "poseScale", "/pose/scale");
  oscP5.plug(this, "posePosition", "/pose/position");
  oscP5.plug(this, "poseOrientation", "/pose/orientation");
  oscP5.plug(this, "mouthWidthReceived", "/gesture/mouth/width");
  oscP5.plug(this, "mouthHeightReceived", "/gesture/mouth/height");
  oscP5.plug(this, "eyeLeftReceived", "/gesture/eye/left");
  oscP5.plug(this, "eyeRightReceived", "/gesture/eye/right");
  oscP5.plug(this, "eyebrowLeftReceived", "/gesture/eyebrow/left");
  oscP5.plug(this, "eyebrowRightReceived", "/gesture/eyebrow/right");
  oscP5.plug(this, "jawReceived", "/gesture/jaw");
  oscP5.plug(this, "nostrilsReceived", "/gesture/nostrils");
}

void keyPressed() {
  if (key == CODED) {
    if (keyCode == UP) {
      //Create an entirely new character.
      
      //For Eyes
      lEyeX1 = sin(sz*0) + random(genSz);
      lEyeY1 = cos(sz*0) + random(genSz);
      rEyeX1 = sin(sz*0) + random(genSz);
      rEyeY1 = cos(sz*0) + random(genSz);
      lEyeX2 = sin(sz*1) + random(genSz);
      lEyeY2 = cos(sz*1) + random(genSz);
      rEyeX2 = sin(sz*1) + random(genSz);
      rEyeY2 = cos(sz*1) + random(genSz);
      ranREye = random(7, 9);
      ranLEye = random(7, 9);
      
      //For Mouth
      mthX = cos(sz) + random(genSz);
      mthY = sin(sz) + random(genSz);
      ranM = random(-0.1, 1.5); 
      
      //For Spine
      spineSz = random(genSz/2);
      spXOff1 = random(-8, 8);
      spYOff1 = hdOffset + random(genSz/3);
      spXOff2 = random(-8, 8) + spXOff1;
      spYOff2 = random(genSz/3) + spYOff1;
      spXOff3 = random(-8, 8) + spXOff2;
      spYOff3 = random(genSz/3) + spYOff2;
      spXOff4 = random(-8, 8) + spXOff3;
      spYOff4 = random(genSz/3) + spYOff3;
      spXOff5 = random(-8, 8) + spXOff4;
      spYOff5 = random(genSz/3) + spYOff4;
      
      //For Head
      hdX = cos(sz) + random(genSz, 3*genSz);
      hdY = sin(sz) + random(genSz, 3*genSz);
      rotAngle = random(-rotInt,rotInt);
      
      //For Colors
      R = random(255);
      G = random(255);
      B = random(255);
      draw();
    }
  }
}

void draw() {  
  background(0);
  strokeWeight(1);
  noFill();
  
  if(found != 0) {
    pushMatrix();
    translate(posePosition.x, posePosition.y);
    //Scales head and allows for rotations
    scale(poseScale*2);
    rotateY(0 - poseOrientation.y);
    rotateX(0 - poseOrientation.x);
    rotateZ(poseOrientation.z);
    rotate(radians(rotAngle));
    ellipse(0,0, hdX,hdY);
    popMatrix();
    
    //FACE
    translate(posePosition.x, posePosition.y);
    scale(poseScale);
    noFill();
    
      //Eyes
    float eyeFac = 1;
    float eyeBL = eyebrowLeft * 2;
    float eyeBR = eyebrowRight * 2;
    ellipse(-20,eyeLeft * -ranLEye, lEyeX1*eyeFac + eyeBL,lEyeY1*eyeFac + eyeBL);
    ellipse(20,eyeRight * -ranREye, rEyeX1*eyeFac + eyeBR,rEyeY1*eyeFac + eyeBR);
    ellipse(-20,eyeLeft * -ranLEye, lEyeX2*eyeFac + eyeBL,lEyeY2*eyeFac + eyeBL);
    ellipse(20,eyeRight * -ranREye, rEyeX2*eyeFac + eyeBR,rEyeY2*eyeFac + eyeBR);
    
      //Mouth
    ellipse(0, 20*ranM, mouthWidth* mthX/3, mouthHeight * mthY);
    
        //BODY/BUBBLES
    stroke(R,G,B);
    ellipse(spXOff1,spYOff1, spineSz,spineSz);
    ellipse(spXOff2,spYOff2, spineSz,spineSz);
    ellipse(spXOff3,spYOff3, spineSz,spineSz);
    ellipse(spXOff4,spYOff4, spineSz,spineSz);
    ellipse(spXOff5,spYOff5, spineSz,spineSz);
   
  }
}

// OSC CALLBACK FUNCTIONS

public void found(int i) {
  println("found: " + i);
  found = i;
}

public void poseScale(float s) {
  println("scale: " + s);
  poseScale = s;
}

public void posePosition(float x, float y) {
  println("pose position\tX: " + x + " Y: " + y );
  posePosition.set(x, y, 0);
}

public void poseOrientation(float x, float y, float z) {
  println("pose orientation\tX: " + x + " Y: " + y + " Z: " + z);
  poseOrientation.set(x, y, z);
}

public void mouthWidthReceived(float w) {
  println("mouth Width: " + w);
  mouthWidth = w;
}

public void mouthHeightReceived(float h) {
  println("mouth height: " + h);
  mouthHeight = h;
}

public void eyeLeftReceived(float f) {
  println("eye left: " + f);
  eyeLeft = f;
}

public void eyeRightReceived(float f) {
  println("eye right: " + f);
  eyeRight = f;
}

public void eyebrowLeftReceived(float f) {
  println("eyebrow left: " + f);
  eyebrowLeft = f;
}

public void eyebrowRightReceived(float f) {
  println("eyebrow right: " + f);
  eyebrowRight = f;
}

public void jawReceived(float f) {
  println("jaw: " + f);
  jaw = f;
}

public void nostrilsReceived(float f) {
  println("nostrils: " + f);
  nostrils = f;
}

// all other OSC messages end up here
void oscEvent(OscMessage m) {
  if(m.isPlugged() == false) {
    println("UNPLUGGED: " + m);
  }
}
'''
