# -*- encoding=utf-8 -*-
from __future__ import print_function
#CW BOON 2018
# Use the following algorithms:
# CW Boon, GT Houlsby, S Utili (2012).  A new algorithm for contact detection between convex polygonal and polyhedral particles in the discrete element method.  Computers and Geotechnics 44, 73-82.
# CW Boon, GT Houlsby, S Utili (2015).  A new rock slicing method based on linear programming.  Computers and Geotechnics 65, 12-29.

#The code runs some steps to stabilise.  After that a vertical cut is carried out.  And the simulations shows the failure mechanism of the slope.
#Display is saved to a vtk file in the "vtk" folder and the user is required to load it using ParaView.  Control the frequency of printing a vtk file using vtkRecorder.iterPeriod in python

#Disclaimer: This script is just for illustration to demonstrate the function of Block Gen and PotentialBlock Code, and not for other purpose outside for this intended use

#To use this script:
#Compile with
#ENABLE_POTENTIAL_BLOCKS=ON, and add  sudo apt-get install
#coinor-clp,
#coinor-libclp-dev,
#coinor-libclp1,
#coinor-libosi1v5

import os
import errno
try:
	os.mkdir('./vtk/')
except OSError as exc:
	if exc.errno != errno.EEXIST:
		raise
	pass

name = 'BlockGeneration'

p = BlockGen()
p.maxRatio = 10000.0
p.minSize = 7.0
p.density = 2700  # 23.6/9.81*1000.0 	#kg/m3
p.dampingMomentum = 0.8
p.viscousDamping = 0.0
p.Kn = 2.0e8  #
p.Ks = 0.1e8  #
p.frictionDeg = 50.0  #degrees
p.traceEnergy = False
p.defaultDt = 1e-4
p.rForPP = 0.2  #0.04
p.kForPP = 0.0
p.RForPP = 1800.0
p.gravity = [0, 0, 9.81]
#p.inertiaFactor = 1.0
p.initialOverlap = 1e-6
p.exactRotation = True
#p.shrinkFactor = 1.0
p.boundarySizeXmin = 20.0
#South
p.boundarySizeXmax = 20.0
#North
p.boundarySizeYmin = 20.0
#West
p.boundarySizeYmax = 20
#East 4100
p.boundarySizeZmin = 0.0
#Up
p.boundarySizeZmax = 40.0
#Down
p.persistentPlanes = False
p.jointProbabilistic = True
p.opening = False
p.boundaries = True
p.slopeFace = False
p.calContactArea = True
p.twoDimension = False
p.unitWidth2D = 9.0
p.intactRockDegradation = True
#p.useFaceProperties = False
p.neverErase = False  # Must be used when tension is on
p.sliceBoundaries = True
p.filenamePersistentPlanes = ''
p.filenameProbabilistic = './joints/jointC.csv'  #  PLEASE CHEK THIS
p.filenameOpening = ''  #'./joints/opening.csv'
p.filenameBoundaries = ''
p.filenameSlopeFace = ''
p.filenameSliceBoundaries = ''
p.directionA = Vector3(1, 0, 0)
p.directionB = Vector3(0, 1, 0)
p.directionC = Vector3(0, 0, 1)

p.saveBlockGenData = True  #if True, data of the BlockGen are written in a file; if False, they are displayed on the terminal
p.outputFile = "BlockGen_Output.txt"  #if empty, the block generation data are not written at all

p.load()
O.engines[1].avoidSelfInteractionMask = 2
O.engines[2].lawDispatcher.functors[0].initialOverlapDistance = p.initialOverlap - 1e-6
O.engines[2].lawDispatcher.functors[0].allowBreakage = False
O.engines[2].lawDispatcher.functors[0].allowViscousAttraction = True
O.engines[2].lawDispatcher.functors[0].traceEnergy = False

O.engines[2].physDispatcher.functors[0].kn_i = 2e9
O.engines[2].physDispatcher.functors[0].ks_i = 0.1e9

from yade import plot

rockFriction = 40.0
boundaryFriction = 40.0
targetFriction = 40.0
waterHeight = 460.0  # PLEASE CHEK THIS 460.0 #
startCountingBrokenBonds = False
minTimeStep = 1000000.0
westBodyId = 10  #PLEASE CHEK THIS
midBodyId = 10  #PLEASE CHEK THIS
eastBodyId = 10  #PLEASE CHEK THIS
originalPositionW = O.bodies[westBodyId].state.pos
originalPositionE = O.bodies[eastBodyId].state.pos
originalPositionM = O.bodies[midBodyId].state.pos
velocityDependency = False

# ----------------------------------------------------------------------------------------------------------------------------------------------- #
# Engines
O.engines[3].label = 'integration'
O.dt = 10.0e-5  #10e-4
O.engines = O.engines + [
        PotentialBlockVTKRecorder(
                fileName='./vtk/Wedge',
                iterPeriod=2000,
                twoDimension=False,
                sampleX=70,
                sampleY=70,
                sampleZ=70,
                maxDimension=100.0,
                REC_INTERACTION=True,
                REC_VELOCITY=True,
                label='vtkRecorder'
        )
]
O.engines = O.engines + [PyRunner(iterPeriod=2000, command='calTimeStep()')]
O.engines = O.engines + [PyRunner(iterPeriod=200, command='myAddPlotData()')]
O.engines = O.engines + [PyRunner(iterPeriod=200, command='goToNextStage2()', label='dispChecker')]

# ----------------------------------------------------------------------------------------------------------------------------------------------- #
# Material
O.materials.append(FrictMat(young=-1, poisson=-1, density=p.density, frictionAngle=radians(0.0), label='frictionless'))

# ----------------------------------------------------------------------------------------------------------------------------------------------- #
# Boundary Plates
thickness = 0.1 * p.boundarySizeZmax
wire = False
highlight = False
kPP = p.kForPP
rPP = p.rForPP

# ----------------------------------------------------------------------------------------------------------------------------------------------- #
# Bottom plate
bbb = Body()
bbb.mask = 3
color = [0, 0.5, 1]
aPP = [1, -1, 0, 0, 0, 0]
bPP = [0, 0, 1, -1, 0, 0]
cPP = [0, 0, 0, 0, 1, -1]
dPP = [p.boundarySizeXmax - rPP, p.boundarySizeXmax - rPP, p.boundarySizeYmax - rPP, p.boundarySizeYmax - rPP, thickness - rPP, thickness - rPP]
minmaxAabb = 1.05 * Vector3(dPP[0], dPP[2], dPP[4])
bbb.shape = PotentialBlock(
        k=kPP,
        r=rPP,
        R=0.0,
        a=aPP,
        b=bPP,
        c=cPP,
        d=dPP,
        id=len(O.bodies),
        isBoundary=True,
        color=color,
        wire=wire,
        highlight=highlight,
        AabbMinMax=True,
        minAabb=minmaxAabb,
        maxAabb=minmaxAabb,
        fixedNormal=True,
        boundaryNormal=Vector3(0, 0, -1)
)
utils._commonBodySetup(bbb, bbb.shape.volume, bbb.shape.inertia, material='frictionless', pos=[0, 0, p.boundarySizeZmax + thickness], fixed=True)
bbb.state.ori = bbb.shape.orientation
O.bodies.append(bbb)

# ----------------------------------------------------------------------------------------------------------------------------------------------- #
# Lateral plate A
bA = Body()
bA.mask = 3
color = [0, 0.5, 1]
aPP = [1, -1, 0, 0, 0, 0]
bPP = [0, 0, 1, -1, 0, 0]
cPP = [0, 0, 0, 0, 1, -1]
dPP = [p.boundarySizeXmax - rPP, p.boundarySizeXmax - rPP, thickness - rPP, thickness - rPP, 0.5 * p.boundarySizeZmax - rPP, 0.5 * p.boundarySizeZmax - rPP]
minmaxAabb = 1.05 * Vector3(dPP[0], dPP[2], dPP[4])
bA.shape = PotentialBlock(
        k=kPP,
        r=rPP,
        R=0.0,
        a=aPP,
        b=bPP,
        c=cPP,
        d=dPP,
        id=len(O.bodies),
        isBoundary=True,
        color=color,
        wire=wire,
        highlight=highlight,
        AabbMinMax=True,
        minAabb=minmaxAabb,
        maxAabb=minmaxAabb,
        fixedNormal=True,
        boundaryNormal=Vector3(0, -1, 0)
)
utils._commonBodySetup(
        bA, bA.shape.volume, bA.shape.inertia, material='frictionless', pos=[0, p.boundarySizeYmax + thickness, 0.5 * p.boundarySizeZmax], fixed=True
)
bA.state.ori = bA.shape.orientation
O.bodies.append(bA)

# ----------------------------------------------------------------------------------------------------------------------------------------------- #
# Lateral plate B
bB = Body()
bB.mask = 3
color = [0, 0.5, 1]
aPP = [1, -1, 0, 0, 0, 0]
bPP = [0, 0, 1, -1, 0, 0]
cPP = [0, 0, 0, 0, 1, -1]
dPP = [p.boundarySizeXmax - rPP, p.boundarySizeXmax - rPP, thickness - rPP, thickness - rPP, 0.5 * p.boundarySizeZmax - rPP, 0.5 * p.boundarySizeZmax - rPP]
minmaxAabb = 1.05 * Vector3(dPP[0], dPP[2], dPP[4])
bB.shape = PotentialBlock(
        k=kPP,
        r=rPP,
        R=0.0,
        a=aPP,
        b=bPP,
        c=cPP,
        d=dPP,
        id=len(O.bodies),
        isBoundary=True,
        color=color,
        wire=wire,
        highlight=highlight,
        AabbMinMax=True,
        minAabb=minmaxAabb,
        maxAabb=minmaxAabb,
        fixedNormal=True,
        boundaryNormal=Vector3(0, 1, 0)
)
utils._commonBodySetup(
        bB, bB.shape.volume, bB.shape.inertia, material='frictionless', pos=[0, -p.boundarySizeYmax - thickness, 0.5 * p.boundarySizeZmax], fixed=True
)
bB.state.ori = bB.shape.orientation
O.bodies.append(bB)

# ----------------------------------------------------------------------------------------------------------------------------------------------- #
# Lateral plate C
bC = Body()
bC.mask = 3
color = [0, 0.5, 1]
aPP = [1, -1, 0, 0, 0, 0]
bPP = [0, 0, 1, -1, 0, 0]
cPP = [0, 0, 0, 0, 1, -1]
dPP = [thickness - rPP, thickness - rPP, p.boundarySizeYmax - rPP, p.boundarySizeYmax - rPP, 0.5 * p.boundarySizeZmax - rPP, 0.5 * p.boundarySizeZmax - rPP]
minmaxAabb = 1.05 * Vector3(dPP[0], dPP[2], dPP[4])
bC.shape = PotentialBlock(
        k=kPP,
        r=rPP,
        R=0.0,
        a=aPP,
        b=bPP,
        c=cPP,
        d=dPP,
        id=len(O.bodies),
        isBoundary=True,
        color=color,
        wire=wire,
        highlight=highlight,
        AabbMinMax=True,
        minAabb=minmaxAabb,
        maxAabb=minmaxAabb,
        fixedNormal=True,
        boundaryNormal=Vector3(1, 0, 0)
)
utils._commonBodySetup(
        bC, bC.shape.volume, bC.shape.inertia, material='frictionless', pos=[-p.boundarySizeXmax - thickness, 0, 0.5 * p.boundarySizeZmax], fixed=True
)
bC.state.ori = bC.shape.orientation
O.bodies.append(bC)

# ----------------------------------------------------------------------------------------------------------------------------------------------- #
# Lateral plate D
bD = Body()
bD.mask = 3
color = [0, 0.5, 1]
aPP = [1, -1, 0, 0, 0, 0]
bPP = [0, 0, 1, -1, 0, 0]
cPP = [0, 0, 0, 0, 1, -1]
dPP = [thickness - rPP, thickness - rPP, p.boundarySizeYmax - rPP, p.boundarySizeYmax - rPP, 0.5 * p.boundarySizeZmax - rPP, 0.5 * p.boundarySizeZmax - rPP]
minmaxAabb = 1.05 * Vector3(dPP[0], dPP[2], dPP[4])
bD.shape = PotentialBlock(
        k=kPP,
        r=rPP,
        R=0.0,
        a=aPP,
        b=bPP,
        c=cPP,
        d=dPP,
        id=len(O.bodies),
        isBoundary=True,
        color=color,
        wire=wire,
        highlight=highlight,
        AabbMinMax=True,
        minAabb=minmaxAabb,
        maxAabb=minmaxAabb,
        fixedNormal=True,
        boundaryNormal=Vector3(-1, 0, 0)
)
utils._commonBodySetup(
        bD, bD.shape.volume, bD.shape.inertia, material='frictionless', pos=[p.boundarySizeXmax + thickness, 0, 0.5 * p.boundarySizeZmax], fixed=True
)
bD.state.ori = bD.shape.orientation
O.bodies.append(bD)


# ----------------------------------------------------------------------------------------------------------------------------------------------- #
def calTimeStep():
	global minTimeStep
	mkratio = 99999999.9
	maxK = 0.0
	minMass = 1.0e15
	for i in O.interactions:
		if i.isReal == True:
			dt1 = O.bodies[i.id1].state.mass / i.phys.kn
			dt2 = O.bodies[i.id2].state.mass / i.phys.kn
			if dt1 < dt2:
				presentDt = 0.15 * sqrt(dt1)
				if minTimeStep > presentDt:
					minTimeStep = presentDt
					O.dt = minTimeStep
			else:
				presentDt = 0.15 * sqrt(dt2)
				if minTimeStep > presentDt:
					minTimeStep = presentDt
					O.dt = minTimeStep


# ----------------------------------------------------------------------------------------------------------------------------------------------- #
def excavate():
	for b in O.bodies:
		if NorthWall(b.state.pos[0], b.state.pos[1], b.state.pos[2]) < 0.001:
			if b.isClumpMember == True and b.shape.isBoundary == False:
				O.bodies.deleteClumpMember(O.bodies[b.clumpId], b)
			elif b.isClump == False and b.shape.isBoundary == False:
				O.bodies.erase(b.id)
				continue
	O.bodies.erase(bA.id)
	O.bodies.erase(bC.id)


prevDistance = O.bodies[westBodyId].state.pos[0]
tolDistance = 0.003  #0.1
tolDistance2 = 0.05
SRinProgress = False
SRcounter = 0
checkIter = 0
prevKE = 0.0
currentKE = 0.0
tolKE = 10e10
initBondedContacts = 0
initDispRate = -1.0
prevDispRate = 0


# ----------------------------------------------------------------------------------------------------------------------------------------------- #
def changeKE(newKE):
	global tolKE
	tolKE = newKE


# ----------------------------------------------------------------------------------------------------------------------------------------------- #
def changeTolDist(newTol):
	global tolDistance
	tolDistance = newTol


# ----------------------------------------------------------------------------------------------------------------------------------------------- #
removeDampingBool = False


def goToNextStage2():
	global startCountingBrokenBonds
	global velocityDependency
	global waterHeight
	global boundaryFriction
	global rockFriction
	global targetFriction
	global prevDistance
	global originalPositionW
	global tolDistance
	global tolDistance2
	global checkIter
	global SRinProgress
	global SRcounter
	global prevKE
	global currentKE
	global tolKE
	global initDispRate
	global prevDispRate
	global removeDampingBool
	prevKE = currentKE
	KE = utils.kineticEnergy()
	currentKE = KE
	uf = utils.unbalancedForce()
	if O.iter > 500 and removeDampingBool == False:
		removeDamping()
		removeDampingBool = True
	if O.iter > 2000 and SRcounter == 0:  # and uf<0.005:
		print(O.iter)
		O.pause()
		vtkRecorder.iterPeriod = 1
		for b in O.bodies:
			b.state.vel = Vector3(0.0, 0.0, 0.0)
			b.state.angVel = Vector3(0.0, 0.0, 0.0)
		calTimeStep()
		excavate()
		dispChecker.iterPeriod = 1
		SRcounter = 1
		O.step()
		vtkRecorder.iterPeriod = 500
		O.run(20000)
		return


# ----------------------------------------------------------------------------------------------------------------------------------------------- #
def SouthWall(x, y, z):
	Xcentre1 = 0.0
	Ycentre1 = 0.0
	Zcentre1 = -0.0
	dip = 90.0
	dipdir = 315.0
	dipdirN = 0.0
	dipN = 90.0 - dip
	if dipdir > 180.0:
		dipdirN = dipdir - 180.0
	else:
		dipdirN = dipdir + 180.0
	dipRad = dipN / 180.0 * pi
	dipdirRad = dipdirN / 180.0 * pi
	a = cos(dipdirRad) * cos(dipRad)
	b = sin(dipdirRad) * cos(dipRad)
	c = sin(dipRad)
	l = sqrt(a * a + b * b + c * c)
	a = a / l
	b = b / l
	c = c / l
	d = a * Xcentre1 + b * Ycentre1 + c * Zcentre1
	plane = a * x + b * y + c * z - d
	if plane < 0.0:
		plane = 0.0
	return plane


# ----------------------------------------------------------------------------------------------------------------------------------------------- #
def NorthWall(x, y, z):
	Xcentre1 = 0.0
	Ycentre1 = 0.0
	Zcentre1 = -0.0
	dip = 90.0
	dipdir = 315.0
	dipdirN = 0.0
	dipN = 90.0 - dip
	if dipdir > 180.0:
		dipdirN = dipdir - 180.0
	else:
		dipdirN = dipdir + 180.0
	dipRad = dipN / 180.0 * pi
	dipdirRad = dipdirN / 180.0 * pi
	a = cos(dipdirRad) * cos(dipRad)
	b = sin(dipdirRad) * cos(dipRad)
	c = sin(dipRad)
	l = sqrt(a * a + b * b + c * c)
	a = -a / l
	b = -b / l
	c = -c / l
	d = a * Xcentre1 + b * Ycentre1 + c * Zcentre1
	plane = a * x + b * y + c * z - d
	if plane < 0.0:
		plane = 0.0
	return plane


# ----------------------------------------------------------------------------------------------------------------------------------------------- #
def removeDamping():
	for i in O.interactions:
		i.phys.viscousDamping = 0.5
		i.phys.cumulative_us = 0.0
	O.engines[2].physDispatcher.functors[0].viscousDamping = 0.5
	integration.damping = 0.0


# ----------------------------------------------------------------------------------------------------------------------------------------------- #
def myAddPlotData():
	global westBodyId
	global midBodyId
	global eastBodyId
	global originalPositionW
	global originalPositionM
	global originalPositionE
	global boundaryFriction
	KE = utils.kineticEnergy()
	uf = utils.unbalancedForce()
	displacementWx = O.bodies[westBodyId].state.pos[0] - originalPositionW[0]
	displacementW = (O.bodies[westBodyId].state.pos - originalPositionW).norm()
	displacementMx = O.bodies[midBodyId].state.pos[0] - originalPositionM[0]
	displacementM = (O.bodies[midBodyId].state.pos - originalPositionM).norm()
	displacementEx = O.bodies[eastBodyId].state.pos[0] - originalPositionE[0]
	displacementE = (O.bodies[eastBodyId].state.pos - originalPositionE).norm()
	plot.addData(
	        timeStep=O.iter,
	        timeStep1=O.iter,
	        timeStep2=O.iter,
	        timeStep3=O.iter,
	        timeStep4=O.iter,
	        timeStep5=O.iter,
	        kineticEn=KE,
	        unbalancedForce=uf,
	        waterLevel=waterHeight,
	        boundary_phi=boundaryFriction,
	        displacement=displacementW,
	        displacementWest=displacementW,
	        dispWx=displacementWx,
	        displacementMid=displacementM,
	        dispMx=displacementMx,
	        displacementEast=displacementE,
	        dispEx=displacementEx
	)


plot.plots = {
        'timeStep2': ('kineticEn'),
        'timeStep3': (('displacementWest', 'ro-'), ('dispWx', 'go-')),
        'timeStep1': (('displacementMid', 'ro-'), ('dispMx', 'go-')),
        'timeStep5': (('displacementEast', 'ro-'), ('dispEx', 'go-')),
        'timeStep4': ('unbalancedForce')
}  #PLEASE CHECK
plot.plot()  #Uncomment to view plots

# ----------------------------------------------------------------------------------------------------------------------------------------------- #
from yade import qt
try:
	v = qt.View()
	vaxes = True
	v.viewDir = Vector3(0, -1, 0)
	v.eyePosition = Vector3(0, 200, 0)

	v.eyePosition = Vector3(-77.42657409847706, 84.2619166834641, -17.41745783023423)
	v.upVector = Vector3(0.1913254208509842, -0.25732151742252396, -0.9471959776136951)
	v.viewDir = Vector3(0.6412359985709529, -0.697845344639707, 0.3191054200439123)
except:
	pass

O.step()
O.step()
#excavate()
O.step()
calTimeStep()
#O.run(20000)
