#!/usr/bin/env python

'''
FysPhun by Alex P-B (chozabu@gmail.com)
licence is http://creativecommons.org/licenses/by-nc-sa/2.5/
'''

print '''ESC = quit
w = create wheel at mouse
left mouse button = create point
drag middle mouse button from one point to another creates a link (holding shift makes it invisable)
right mouse button lets you drag a point
holding space pauses the sim
tapping left and right buttons spin any on-screen wheels in that direction
down flips the wheel spin direction
up sets wheel spin to 0

todo:
deletion of stuff - Done!
selection of lines - not worth the effort?
scaling of lines
changing visabality of lines
changing elasticity of lines
better graphics
configurable screen
screen scrolling?
collideable world? - kinda done!'''

import sys
import pygame
from pygame.locals import *
from math import *
from random import random

import psyco
psyco.full()

pygame.init()
sh = 600
sw = 800

groupsize = 100

class Group:
	def __init__(self):
		self.members = []

groups = []
for x in range(0, sw/groupsize):
	groupsy = []
	for y in range(0,sh/groupsize):
		groupsy.append(Group())
	groups.append(groupsy)


global screen
screen  = pygame.display.set_mode((sw,sh),0,32)
pygame.display.set_caption("FysPhun, check command line for controls!")
mx = 100
my = 100
'''
class Player:
	name = ""
	num = -1
	pointlist = []
	score = 0
'''
class Point:
	def __init__(self):
		self.locked = 0
		self.rad=15
		self.x = 0
		self.y = 0
		self.x = 0
		self.y = 0
		self.ox = 0
		self.oy = 0
	def basicphys(self):
		self.x,self.ox = self.x+(self.x-self.ox),self.x
		self.y,self.oy = self.y+(self.y-self.oy),self.y
		self.y+=0.2
	
	def basiclimits(self):
		if self.y > sh:
			self.y = sh
			self.x = self.ox*0.5+self.x*0.5
		if self.x > sw:
			self.x = sw
			self.y = self.oy
		if self.x < 0:
			self.x = 0
			self.y = self.oy
		pass


def twopoint(p1,p2,destdist = 15,mult = 0.5):
	xd = p1.x-p2.x
	yd = p1.y-p2.y
	td = hypot(xd, yd)+0.0000000001
	rads = p1.rad+p2.rad
	diffd = (destdist-td)*mult
	xd/=td
	yd/=td
	xd*=diffd
	yd*=diffd
	if not p1.locked:
		p1.x+=xd
		p1.y+=yd
	if not p2.locked:
		p2.x-=xd
		p2.y-=yd
	

class Link:
	def __init__(self):
		self.p1=0
		self.p2=0
		self.dist = 15
		self.strength = 0.5
		self.drawme = 1
	def applyme(self):
		twopoint(self.p1,self.p2,self.dist,self.strength)
		pass
	def setstr(self,stren):
		self.strength = stren*0.6
class Terrainlink:
	def __init__(self):
		self.p1=0
		self.p2=0
		self.nx = 0
		self.ny = -1
	def fixcol(self,p):
		dp1 = self.nx*(p.x-self.p1.x)+self.ny*(p.y-self.p1.y)
		dp2 = self.nx*(p.ox-self.p1.x)+self.ny*(p.oy-self.p1.y)
		print dp1,dp2
		#if dp1*dp2 < 0:
		if p.y > self.p1.y:
			print "asd"
			p.x+=self.nx*1
			p.y+=self.ny*1
		pass

points = []
links = []
tlinks = []
wheels = []
#players = (Player(), Player())

drawlinks = 1
addinglink = 0
nearp = None
onearp = None
nearl = None
paused = 0
shiftdown = 0
draggingpoint = 0
wheelpower = 0.1

def setnearp(newnearp):
	global nearp,nearl,onearp
	onearp=nearp
	nearp = newnearp
	if onearp != None:
		templ = findlink(nearp, onearp)
		if templ != None:
			nearl = templ
def findlink(p1,p2):
	for l in links:
		found = False
		if p1 == l.p1 and p2 == l.p2:
			found = True
		if p2 == l.p1 and p1 == l.p2:
			found = True
		if found == True:
			return l

def addlinki(p1,p2, dist=30,strength=0.5):
	global drawlinks
	l= Link()
	l.p1 = points[p1]
	l.p2 = points[p2]
	l.dist = dist
	l.strength = strength
	l.drawme = drawlinks
	links.append(l)
	return l

def addlinkr(p1,p2, dist=30,strength=0.5):
	global drawlinks
	l= Link()
	l.p1 = p1
	l.p2 = p2
	l.dist = dist
	l.strength = strength
	l.drawme = drawlinks
	links.append(l)
	return l
def addlinkir(p1,p2):
	addlinkrr(points[p1],points[p2])
def addlinkrr(p1,p2,strength=0.5):
	global drawlinks
	l= Link()
	l.p1 = p1
	l.p2 = p2
	xd = p1.x-p2.x
	yd = p1.y-p2.y
	dist = hypot(xd,yd)
	if dist < 30:dist = 30
	l.dist = dist
	l.strength = strength
	l.drawme = drawlinks
	links.append(l)
	return l
def addlinkrrt(p1,p2,strength=0.5):
	global drawlinks
	l= Terrainlink()
	l.p1 = p1
	l.p2 = p2
	p1.locked = 1
	p2.locked = 1
	tlinks.append(l)
	return l
	
def addpoint(x,y):
	p= Point()
	p.x = x
	p.y = y
	p.ox = p.x
	p.oy = p.y
	points.append(p)
	return p #will i wanna use this?
	
def nearestpoint(x,y):
	mind = 10000000
	retp = 0
	for p in points:
		xd = x-p.x
		yd = y-p.y
		td = xd*xd+yd*yd
		if td < mind:
			retp = p
			mind = td
	return retp
def nearestpoint2(x,y):
	mind = 10000000
	retp = None
	oretp = None
	for p in points:
		xd = x-p.x
		yd = y-p.y
		td = xd*xd+yd*yd
		if td < mind:
			oretp = retp
			retp = p
			mind = td
	mind = 10000000
	if oretp == None:
		for p in points:
			xd = x-p.x
			yd = y-p.y
			td = xd*xd+yd*yd
			if td < mind and p != retp:
				oretp = p
				mind = td
	return oretp

def nearestlink(x,y):
	mind = 10000000
	retl = 0
	for l in links:
		xd = x-(l.p1.x+l.p2.x)/2
		yd = y-(l.p1.y+l.p2.y)/2
		td = xd*xd+yd*yd
		if td < mind:
			retpl= l
			mind = td
	return retl
from pickle import *
def save():
	FILE = open("testsave","w")
	FILE.write(dumps(points))
	#for p in points:
	#	FILE.write(str(p))
	FILE.close()
	
	#dump(points, "testsave")

class Wheel:
	#spokes = []
	#mainpoint = 0
	def __init__(self,x,y,numspokes = 8, scale = 34):
		self.mult = 1
		lastspoke = numspokes-1
		halfspoke = numspokes/2
		self.spokes = []
		global drawlinks
		drawlinks = 1
		self.mainpoint = addpoint(x,y)
		step = pi/numspokes*2
		angle = 0
		for i in range(numspokes):
			angle+=step
			self.spokes.append(addpoint(cos(angle)*scale+x,sin(angle)*scale+y))
		for i in range(0,numspokes):
			nextspoke = i+1
			if nextspoke > lastspoke:
				nextspoke = 0
			#addlinkrr(self.spokes[i],self.spokes[nextspoke])
			addlinkrr(self.spokes[i],self.mainpoint)
			pass
		
		for i in range(0,lastspoke):
			for r in range(i+1,lastspoke+1):
				addlinkrr(self.spokes[i],self.spokes[r])
		
		addlinkrr(self.spokes[0+halfspoke/2],self.spokes[halfspoke+halfspoke/2])
		addlinkrr(self.spokes[0],self.spokes[halfspoke])
	def addpower(self,power=1):
		mp = self.mainpoint
		for p in self.spokes:
			if p.y > mp.y:
				p.x-=power*self.mult
			else:
				p.x+=power*self.mult
			if p.x > mp.x:
				p.y+=power*self.mult
			else:
				p.y-=power*self.mult

iid = 0
linelen = 20
for ir in range(linelen):
	addpoint(ir*30,20)#+cos(ir)*5)
setnearp(points[0])
#addlinkir(0,1)
for ir in range(0,linelen-1):
	addlinkir(ir,ir+1)
for ir in range(0,linelen-2):
	addlinkir(ir,ir+2)
for ir in range(0,linelen-3):
	addlinkir(ir,ir+3)
#for l in links:
#	l.setstr(0.01)
nearl = links[0]
drawlinks = 1
#neck
"""addlinki(0,1)
points[0].rad = 14

#body
addlinki(1,2,60)

#leg1
addlinki(2,3)
addlinki(3,4)

#leg2
addlinki(2,5)
addlinki(5,6)

#arm1
addlinki(1,7)
addlinki(7,8)

#arm2
addlinki(1,9)
addlinki(9,10)

drawlinks = 0
addlinki(0,2,90)

addlinki(7,9,70)
addlinki(3,5,40)
"""
#addlinki(0,2,70)
#addlinki(0,2,70)

wheels.append(Wheel(300,300))

font = pygame.font.SysFont("None", 30)
s1 = font.render("FysPhun!", True, [0, 255, 0])
s2 = font.render("   press any button to start", True, [0, 255, 0])
m1 = font.render("mouse:", True, [255, 128, 128])
m2 = font.render("  lbutton:create point", True, [255, 255, 255])
m3 = font.render("  mbutton:drag to link points(holding shift makes it invisible)", True, [255, 255, 255])
m4 = font.render("  rbutton:drag points", True, [255, 255, 255])
k1 = font.render("keyboard:", True, [255, 128, 255])
k2 = font.render("  z:  no momentum while held down", True, [255, 255, 255])
k3 = font.render("  p:  pause", True, [255, 255, 255])
k4 = font.render("  space: hold to pause, release to unpause", True, [255, 255, 255])
k5 = font.render("  b/g: push/pull points in direction of mouse", True, [255, 255, 255])
k6 = font.render("  delete: remove connections from a point, or remove an unconnected point", True, [255, 255, 255])
w1 = font.render("  wheels:", True, [200, 200, 255])
w2 = font.render("    w: create a wheel at mouse", True, [255, 255, 255])
w3 = font.render("    left/right:  tap to increase wheel speed in that direction", True, [255, 255, 255])
w4 = font.render("    up:  cancel wheel speed", True, [255, 255, 255])
w5 = font.render("    down:  reverse wheel speed", True, [255, 255, 255])

objr = font.render("YOUR MISSION: build something cool!", True, [255, 0, 0])
objg = font.render("YOUR MISSION: build something cool!", True, [0, 255, 0])
objb = font.render("YOUR MISSION: build something cool!", True, [0, 0, 255])
#m1 = font.render("mouse:", True, [255, 255, 255])
inmenu = 1
while inmenu:
	screen.fill((0,0,0))
	screen.blit(s1, (20, 00))
	screen.blit(s2, (20, 20))
	
	screen.blit(m1, (20, 40))
	screen.blit(m2, (20, 60))
	screen.blit(m3, (20, 80))
	screen.blit(m4, (20, 100))
	
	screen.blit(k1, (20, 120))
	screen.blit(k2, (20, 140))
	screen.blit(k3, (20, 160))
	screen.blit(k4, (20, 180))
	screen.blit(k5, (20, 200))
	screen.blit(k6, (20, 220))
	
	screen.blit(w1, (20, 240))
	screen.blit(w2, (20, 260))
	screen.blit(w3, (20, 280))
	screen.blit(w4, (20, 300))
	screen.blit(w5, (20, 320))
	
	screen.blit(objb, (70, 420))
	screen.blit(objg, (71, 421))
	screen.blit(objr, (72, 422))
	
	
	
	pygame.display.flip()
	for e in pygame.event.get():
		if e.type is 12:#QUIT
			inmenu = 0
		if e.type is 2:#KEYDOWN
			inmenu = 0
"""
QUIT = 12
KEYDOWN = 2
KEYUP = 3
MOUSEMOTION = 4
MOUSEBUTTONDOWN = 5
MOUSEBUTTONUP = 6
K_z = 122
K_p = 112
K_g = 103
K_b = 98
K_w = 119
K_SPACE = 32
K_LSHIFT = 304
K_DELETE = 127"""


ingame = 1
while ingame:
	keys = pygame.key.get_pressed()
	#DO PHYSICS!
	if not paused:
		for p in points:
			if not p.locked:
				p.basicphys()
			if keys[K_z]:
				p.x = p.ox
				p.y = p.oy
	
	#DO INPUT!
	for e in pygame.event.get():
		if e.type is QUIT:
			ingame = 0
		if e.type is MOUSEMOTION:
			mx,my = e.pos
		elif e.type is MOUSEBUTTONDOWN:
			mx,my = e.pos
			if e.button is 1:
				tp = addpoint(mx,my)
				if shiftdown:
					addlinkrr(tp, nearp)
					setnearp(tp)
					#nearp = tp
				#awheel = Wheel(e.pos[0],e.pos[1])
			if e.button is 3:
				setnearp(nearestpoint(mx,my))
				draggingpoint = 1
				#tempp.x = mx
				#tempp.y = my
				pass
			if e.button is 2:
				setnearp(nearestpoint(mx,my))
				addinglink = 1
			pass
		elif e.type is MOUSEBUTTONUP:
			mx,my = e.pos
			if e.button is 2:
				tempp = nearestpoint(mx,my)
				if nearp is not tempp:
					drawlinks = not shiftdown
					addlinkrr(nearp,tempp)
					drawlinks = 1
				addinglink = 0
			if e.button is 3:
				draggingpoint = 0
			pass
		elif e.type is KEYDOWN:
			if e.key == K_s:
				save()
			if e.key == K_1:
				nearl.setstr(0.1)
			if e.key == K_2:
				nearl.setstr(0.2)
			if e.key == K_3:
				nearl.setstr(0.3)
			if e.key == K_4:
				nearl.setstr(0.4)
			if e.key == K_5:
				nearl.setstr(0.5)
			if e.key == K_6:
				nearl.setstr(0.6)
			if e.key == K_7:
				nearl.setstr(0.7)
			if e.key == K_8:
				nearl.setstr(0.8)
			if e.key == K_9:
				nearl.setstr(0.9)
			if e.key == K_0:
				nearl.setstr(1.0)
			if e.key == K_DELETE:
				linksleft = 1
				gotlinks = 0
				while linksleft:
					linksleft = 0
					for l in links:
						if l.p1 == nearp or l.p2 == nearp:
							links.remove(l)
							linksleft = 1
							gotlinks = 1
				for w in wheels:
					if nearp == w.mainpoint:
						wheels.remove(w)
						
				if not gotlinks:
					ntnearp = nearestpoint2(nearp.x,nearp.y)
					points.remove(nearp)
					nearp = ntnearp
				#nearp = links[0]
			if e.key == K_SPACE or e.key == K_p:
				paused = 1
			if e.key == K_LSHIFT:
				shiftdown = 1
			if e.key == K_ESCAPE:
				ingame = 0
			if e.key == K_w:
				wheels.append(Wheel(mx,my))
			if e.key == K_r:
				for w in wheels:
					if w.mainpoint == nearp:
						w.mult = -w.mult
			if e.key == K_l:
				nearp.locked = not nearp.locked
			#if e.key == K_l:
			#	mywheel.addpower(-10)
			if e.key == K_RIGHT:
				wheelpower += 0.1
			if e.key == K_LEFT:
				wheelpower -= 0.1
			if e.key == K_DOWN:
				wheelpower = -wheelpower
			if e.key == K_UP:
				wheelpower = 0
		elif e.type is KEYUP:
			if e.key == K_SPACE:
				paused = 0
			if e.key == K_LSHIFT:
				shiftdown = 0
		else:
			#print e
			pass
	
	if draggingpoint:
		nearp.x = mx
		nearp.y = my
	if True:
		for p in points:
			if p.locked:
				p.x = p.ox
				p.y = p.oy
	if not paused:
		gravmax = 0.0
		if keys[K_g]:
			for p in points:
				if not p.locked:
					xd = (p.x-mx)*0.01
					yd = (p.y-my)*0.01
					if xd > gravmax: xd = gravmax
					if yd > gravmax:yd = gravmax
					p.x -= (p.x-mx)*0.01
					p.y -= (p.y-my)*0.01
		if keys[K_b]:
			tp = Point()
			
			for p in points:
				tp.x = mx
				tp.y = my
				twopoint(tp,p,sh,0.01)
				
		#more input!
		for w in wheels:
			w.addpower(wheelpower)
		#Constraints!
		for l in links:
			l.applyme()
		#World constraints!
		for p in points:
			p.basiclimits()
		for p in points:
			for l in tlinks:
				l.fixcol(p)
		d = points[0].rad*2
		sd = d*d
		hd = d/2
		"""
		for x in groups:
			for g in x:
				g.members = []
		
		for p in (points):
			pgx = int(p.x/groupsize)
			pgy = int(p.y/groupsize)
			for x in range(pgx-2,pgx+2):
				for y in range(pgy-2,pgy+2):
					groups[x][y].members.append(p)
		
		
		
		for x in groups:
		    for gro in x:
			#for tp in range(0,len(gro.members)-1):
			#    for tq in range(p+1,len(gro.members)):
			for p in gro.members:
			    for q in gro.members:
			      if p != q:
		"""
		for tp in range(0,len(points)-1):
			for tq in range(tp+1,len(points)):
				p = points[tp]
				q = points[tq]
				xd = p.x-q.x
				yd = p.y-q.y
				td = xd*xd+yd*yd
				
				if td < sd:
					td = sqrt(td)+0.0000000001
					xd/=td
					yd/=td
					px = (p.x+q.x)/2
					py = (p.y+q.y)/2
					p.x=px+xd*hd
					q.x=px-xd*hd
					p.y=py+yd*hd
					q.y=py-yd*hd
					#q.rad-=0.0001
					#p.rad-=0.0001
					
					vx1 = p.x-p.ox
					vx2 = q.x-q.ox
					
					ap = 0.6
					bp = 1-ap
					vxa = vx1*ap+vx2*bp
					vxb = vx1*bp+vx2*ap
					if not p.locked:p.ox=p.x-vxa
					if not q.locked:q.ox=q.x-vxb
					
					vy1 = p.y-p.oy
					vy2 = q.y-q.oy
					vya = vy1*ap+vy2*bp
					vyb = vy1*bp+vy2*ap
					if not p.locked:p.oy=p.y-vya
					if not q.locked:q.oy=q.y-vyb
				#else:
				#	points[q].rad+=0.0001
				#	points[p].rad+=0.0001
	
	#draw
	screen.fill((0,0,0))
	for l in links:
		if l.drawme:pygame.draw.lines(screen, (255,0,0),0, [(int(l.p1.x),int(l.p1.y)),(int(l.p2.x),int(l.p2.y))],1)
	for l in tlinks:
		pygame.draw.lines(screen, (255,0,0),0, [(int(l.p1.x),int(l.p1.y)),(int(l.p2.x),int(l.p2.y))],1)
	for p in points:
		pygame.draw.circle(screen, (0,255,0), (int(p.x),int(p.y)), int(p.rad))
	global nearl
	if nearl != None:
		pygame.draw.lines(screen, (128,0,0),0, [(int(nearl.p1.x),int(nearl.p1.y)),(int(nearl.p2.x),int(nearl.p2.y))],3)
	pygame.draw.circle(screen, (0,0,255), (int(nearp.x),int(nearp.y)), int(nearp.rad*0.74))
	if addinglink:
		pygame.draw.lines(screen, (255,255,0),0, [(int(nearp.x),int(nearp.y)),(int(mx),int(my))],1)
	pygame.display.flip()
	pygame.time.wait(5)
	