#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# C++ version Copyright (c) 2006-2007 Erin Catto http://www.box2d.org
# Python version Copyright (c) 2010 kne / sirkne at gmail dot com
#
# Implemented using the pybox2d SWIG interface for Box2D (pybox2d.googlecode.com)
#
# This software is provided 'as-is', without any express or implied
# warranty.  In no event will the authors be held liable for any damages
# arising from the use of this software.
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it
# freely, subject to the following restrictions:
# 1. The origin of this software must not be misrepresented; you must not
# claim that you wrote the original software. If you use this software
# in a product, an acknowledgment in the product documentation would be
# appreciated but is not required.
# 2. Altered source versions must be plainly marked as such, and must not be
# misrepresented as being the original software.
# 3. This notice may not be removed or altered from any source distribution.

# Ported from the Cloth test by Paril, originally for Box2CS:
#   http://www.box2d.org/forum/viewtopic.php?f=6&t=6124
#
from Box2D.examples.framework import (Framework, Keys, main)
from Box2D import (b2CircleShape, b2FixtureDef, b2Random, b2Vec2)


def create_cloth(world, segment_count, body_size, position=(0, 30),
                 group_index=-1, bar_height=0.5, base_hz=15,
                 base_damping=0.11, density=0.2):
    segment_w, segment_h = segment_count
    body_spacing_w = body_size * 2
    total_w = body_spacing_w * segment_w
    position = b2Vec2(*position)

    # The static bar at the top which holds the cloth
    bar = world.CreateStaticBody(position=position)
    bar.CreatePolygonFixture(box=(total_w / 2.0 + body_spacing_w,
                                  bar_height / 2.0),
                             groupIndex=group_index)

    box_fixture = b2FixtureDef(shape=b2CircleShape(radius=body_size),
                               groupIndex=group_index, density=density)

    weld_joints = []
    distance_joints = []
    cloth = [[None] * segment_h for x in range(segment_w)]
    for y in range(segment_h):
        pos = position - (total_w / 2.0, y * body_spacing_w)
        for x in range(segment_w):
            pos += (body_spacing_w, 0.0)
            body = world.CreateDynamicBody(position=pos, fixtures=box_fixture)
            cloth[x][y] = body

            if y == 0:
                joint = world.CreateWeldJoint(bodyA=body, bodyB=bar,
                                              anchor=body.position)
                weld_joints.append(joint)

    connect_bodies = []
    for y in range(segment_h):
        for x in range(segment_w):
            if x <= segment_w - 2:
                left_body = cloth[x][y]
                right_body = cloth[x + 1][y]
                connect_bodies.append((left_body, right_body))
            if y > 0:
                left_body = cloth[x][y]
                right_body = cloth[x][y - 1]
                connect_bodies.append((left_body, right_body))

    for bodyA, bodyB in connect_bodies:
        joint = world.CreateDistanceJoint(
            bodyA=bodyA,
            bodyB=bodyB,
            anchorA=bodyA.position,
            anchorB=bodyB.position,
            frequencyHz=base_hz + b2Random(0, base_hz / 2.0),
            dampingRatio=base_damping + b2Random(0.01, base_damping),
        )
        distance_joints.append(joint)

    return cloth, weld_joints, distance_joints


def step_cloth(world, cloth, wind, body_size, segment_count, distance_joints,
               wind_dir=(1, 1), wind_rand=0.0, distance_factor=1.45):
    segment_w, segment_h = segment_count
    body_spacing_w = body_size * 2
    if wind:
        for x in range(segment_w):
            w = (b2Random(wind_dir[0] - wind_rand / 2.0,
                          wind_dir[0] + wind_rand / 2.0),
                 b2Random(wind_dir[1] - wind_rand / 2.0,
                          wind_dir[1] + wind_rand / 2.0))
            cloth[x][-1].linearVelocity += w

    # If any two points are too far from one another, find the joint connecting
    # them and destroy it.
    check_segments = []
    for y in range(segment_h):
        for x in range(segment_w):
            if y > 0:
                check_segments.append((cloth[x][y], cloth[x][y - 1]))
            if x <= segment_w - 2:
                check_segments.append((cloth[x][y], cloth[x + 1][y]))

    thresh = body_spacing_w * distance_factor
    for c1, c2 in check_segments:
        if (c1.worldCenter - c2.worldCenter).length <= thresh:
            continue

        for joint in distance_joints:
            if ((joint.bodyA == c1 and joint.bodyB == c2) or
                    (joint.bodyA == c2 and joint.bodyB == c1)):
                world.DestroyJoint(joint)
                distance_joints.remove(joint)
                break


class Cloth(Framework):
    name = "Cloth"
    description = "(w) Toggle wind"

    def __init__(self):
        super(Cloth, self).__init__()
        self.wind = False
        self.segment_count = (18, 25)
        self.body_size = 0.22

        cloth_info = create_cloth(
            self.world, self.segment_count, self.body_size)
        self.cloth, self.weld_joints, self.dist_joints = cloth_info

    def Keyboard(self, key):
        if key == Keys.K_w:
            self.wind = not self.wind

    def Step(self, settings):
        super(Cloth, self).Step(settings)
        if self.wind:
            self.Print('Wind enabled')
        step_cloth(self.world, self.cloth, self.wind, self.body_size,
                   self.segment_count, self.dist_joints)

if __name__ == "__main__":
    main(Cloth)
