#include "cable.hh"
#include "game.hh"
#include "edevice.hh"
#include "material.hh"
#include "model.hh"
#include "world.hh"
#include "ifdevice.hh"

#include <cstdlib>

#define QUALITY 4
#define SEGSIZE .5f
#define WIDTH (.125f*.375f)*.5f

#define EPSILON .1f

#define NUM_SEGS 6
#define MAX_CABLES 2048
#define MAX_PLUGS 2048 /* XXX: this needs to be overlooked */

#define NUM_SUBDIVISIONS 3

struct cable_vert {
    tvec3 pos;
    tvec3 nor;
} __attribute__ ((packed));

bool cable::initialized = false;

tms_mesh     *cable::_mesh;
tms_entity   *cable::_e;
tms::varray  *cable::va = 0;
tms::gbuffer *cable::buf = 0;
tms::gbuffer *cable::ibuf = 0;

static volatile int plug_counter[3] = {0,0,0};
static struct tms_mesh   *plug_mesh[3];
static struct tms_entity *plug_e[3];
static tms::varray *plug_va[3];
static tms::gbuffer *plug_buf[3];
static tms::gbuffer *plug_ibuf;
static float _cam_x = 0.f, _cam_y = 0.f;

static int points_per_cable = 0;
static int vertices_per_plug = 0;
static int indices_per_plug = 0;

static volatile int counter = 0;

void
cable::_init(void)
{
    if (initialized)
        return;

    b2Vec2 dir;

    /* initialize cable buffers */
    {
        points_per_cable = (int)pow(2.f, (float)(NUM_SUBDIVISIONS))+1;
        buf = new tms::gbuffer(MAX_CABLES * points_per_cable * QUALITY * sizeof(struct cable_vert));
        ibuf = new tms::gbuffer(MAX_CABLES * (points_per_cable-1) * (QUALITY) * 6 * sizeof(uint16_t));

        buf->usage = GL_STREAM_DRAW;
        ibuf->usage = GL_STATIC_DRAW;
        ibuf->target = GL_ELEMENT_ARRAY_BUFFER;

        va = new tms::varray(2);
        va->map_attribute("position", 3, GL_FLOAT, buf);
        va->map_attribute("normal", 3, GL_FLOAT, buf);

        _mesh = tms_mesh_alloc(va, ibuf);
        _e = tms_entity_alloc();
        tms_entity_set_mesh(_e, _mesh);
        tms_entity_set_material(_e, static_cast<struct tms_material*>(&m_cable));
        tmat4_load_identity(_e->M);
        tmat3_load_identity(_e->N);

        uint16_t *id = (uint16_t*)ibuf->get_buffer();

        int num_i = 0;
        int v_offs = 0;

        for (int z=0; z<MAX_CABLES; z++) {
            v_offs = (z*(points_per_cable)*QUALITY);
            for (int y=0; y<(points_per_cable); y++) {
                if (y>0) {
                    for (int x=0; x<QUALITY; x++) {
                        int c = y*(QUALITY) + x%QUALITY;
                        int p = (y-1)*(QUALITY) + x%QUALITY;

                        int c1 = y*(QUALITY) + (x+1)%QUALITY;
                        int p1 = (y-1)*(QUALITY) + (x+1)%QUALITY;

                        id[num_i+1] = v_offs + c;
                        id[num_i+0] = v_offs + p;
                        id[num_i+2] = v_offs + c1;

                        id[num_i+4] = v_offs + c1;
                        id[num_i+3] = v_offs + p;
                        id[num_i+5] = v_offs + p1;

                        num_i+=6;
                    }
                }
            }
        }

        ibuf->upload();
    }

    /* initialize plug buffers, one for each layer */
    {
        struct tms_mesh *mm = mesh_factory::get_mesh(MODEL_PLUG_SIMPLE);
        vertices_per_plug = mm->v_count;
        indices_per_plug = mm->i_count;

        plug_ibuf = new tms::gbuffer(MAX_PLUGS * indices_per_plug * sizeof(uint16_t));
        plug_ibuf->usage = GL_STATIC_DRAW;
        plug_ibuf->target = GL_ELEMENT_ARRAY_BUFFER;

        for (int x=0; x<3; x++) {
            plug_buf[x] = new tms::gbuffer(MAX_PLUGS * vertices_per_plug * sizeof(struct cable_vert));
            plug_buf[x]->usage = GL_STREAM_DRAW;

            plug_va[x] = new tms::varray(2);
            plug_va[x]->map_attribute("position", 3, GL_FLOAT, plug_buf[x]);
            plug_va[x]->map_attribute("normal", 3, GL_FLOAT, plug_buf[x]);

            plug_mesh[x] = tms_mesh_alloc(plug_va[x], plug_ibuf);
            plug_e[x] = tms_entity_alloc();
            plug_e[x]->prio = x;
            tms_entity_set_mesh(plug_e[x], plug_mesh[x]);
            tms_entity_set_material(plug_e[x], static_cast<struct tms_material*>(&m_cable_red));
            tmat4_load_identity(plug_e[x]->M);
            tmat3_load_identity(plug_e[x]->N);
        }

        uint16_t *i = (uint16_t*)plug_ibuf->get_buffer();
        uint16_t *ri = (uint16_t*)((char*)tms_gbuffer_get_buffer(mm->indices)+mm->i_start*2);
        uint16_t ibase = mm->v_start / sizeof(struct cvert);

        for (int x=0; x<MAX_PLUGS; x++) {
            for (int y=0; y<indices_per_plug; y++) {
                i[x*indices_per_plug + y] = ri[y] - ibase + x*vertices_per_plug;

                /*
                if (x ==0 && y == 0) {
                    tms_infof("first set to: %d - %d + %d*%d", ri[y], ibase, x, vertices_per_plug);
                    exit(1);
                }
                */
            }
        }

        plug_ibuf->upload();
    }

    tms_entity_add_child(_e, plug_e[0]);
    tms_entity_add_child(_e, plug_e[1]);
    tms_entity_add_child(_e, plug_e[2]);

    initialized = true;
}

void
cable::reset_counter(void)
{
    counter = 0;
    for (int x=0; x<3; x++)
        plug_counter[x] = 0;

    if (G) {
        _cam_x = G->cam->_position.x;
        _cam_y = G->cam->_position.y;
    }

    if (_e) {
        tmat4_load_identity(_e->M);
        tmat4_translate(_e->M, _cam_x, _cam_y, 0.f);
        for (int x=0; x<3; x++) {
            tmat4_load_identity(plug_e[x]->M);
            tmat4_translate(plug_e[x]->M, _cam_x, _cam_y, 0.f);
        }
    }
}

struct tms_entity*
cable::get_entity(void)
{
    return _e;
}

void
cable::upload_buffers(void)
{
    if (counter > MAX_CABLES-1) counter = MAX_CABLES-1;
    _mesh->i_start = 0;
    _mesh->i_count = counter*(ibuf->size/MAX_CABLES) / sizeof(uint16_t);
    if (counter)
        buf->upload_partial(counter * points_per_cable * 4 * sizeof(struct cable_vert)); /* this 4 = QUALITY? */

    for (int x=0; x<3; x++) {
        int count = plug_counter[x];
        if (count > (MAX_PLUGS)-1) {
            count = (MAX_PLUGS)-1;
        }
        plug_mesh[x]->i_start = 0;
        plug_mesh[x]->i_count = count*(indices_per_plug);

        if (count) {
            plug_buf[x]->upload_partial((count*vertices_per_plug) * sizeof(struct cable_vert));
        }
    }
}

cable::cable(int type)
{
    cable::_init();
    this->set_flag(ENTITY_ALLOW_CONNECTIONS,    false);
    this->set_flag(ENTITY_ALLOW_ROTATION,       false);
    this->set_flag(ENTITY_CUSTOM_GHOST_UPDATE,  true);
    this->set_flag(ENTITY_DO_STEP,              true);

    this->ji = 0;
    this->freeze = false;
    this->update_method = ENTITY_UPDATE_CUSTOM;

    this->ready = true;

    this->p_in = -1;
    this->p_out = -1;

    this->type = ENTITY_CABLE;
    this->ctype = type;

    this->p[0] = 0;
    this->p[1] = 0;

    this->p[0] = new plug(this);
    this->p[1] = new plug(this);

    this->add_child(this->p[0]);
    this->add_child(this->p[1]);

    tmat4_load_identity(this->M);
    tmat3_load_identity(this->N);

    this->saved_length = 0.f;
    this->length = 8.f;

    this->joint = 0;
    this->extra_length = .0f+(rand()%100)/100.f * .2f;
    this->num_sliders = 1;
}

cable::~cable()
{
    if (this->ji) {
        this->ji->data = 0;
    }

    /*
    if (this->p[0]) {
        this->p[0]->c = 0;
    }
    if (this->p[1]) {
        this->p[1]->c = 0;
    }
    */
}

void
cable::construct()
{
    this->set_position(_pos.x, _pos.y);
}

float
cable::get_slider_snap(int s)
{
    return .1f;
}

float
cable::get_slider_value(int s)
{
    return this->extra_length / CABLE_MAX_EXTRA_LENGTH;
}

void
cable::on_slider_change(int s, float value)
{
    this->extra_length = value * CABLE_MAX_EXTRA_LENGTH;
    G->show_numfeed(this->extra_length);
}

void
cable::set_position(float x, float y, uint8_t frame/*=0*/)
{
    this->p[0]->set_position(x+.5f,y+.5f);
    this->p[1]->set_position(x-.5f,y-.5f);
}

void
cable::add_to_world()
{
    this->p[0]->add_to_world();
    this->p[1]->add_to_world();

    this->ready = true;
    this->create_joint();
}

void
cable::remove_from_world()
{
    this->p[0]->remove_from_world();
    this->p[1]->remove_from_world();
}

void
cable::pre_write(void)
{
    this->p[0]->pre_write();
    this->p[1]->pre_write();
}

void
plug::pre_write(void)
{
}

void
cable::destroy_joint()
{
    if (this->joint) {
        W->b2->DestroyJoint(this->joint);
        this->joint = 0;
    }
}


void
cable::create_joint()
{
    this->destroy_joint();

    //b2RopeJointDef rjd;
    //b2WeldJointDef rjd;
    //b2RevoluteJointDef rjd;
    //b2PrismaticJointDef rjd;

    b2Body *b[2];
    b2Vec2 a[2];
    entity *entities[2] = {0, };

    for (int x=0; x<2; x++) {
        if (this->p[x]->is_connected()) {
            entity *e = this->p[x]->plugged_edev->get_entity();
            entities[x] = e;

            if (e->get_body(0)) {
                //tms_infof("%d entity body ------", x);
                b[x] = e->get_body(0);
                a[x] = e->local_to_body(this->p[x]->s->lpos, 0);
            } else {
                b[x] = 0;
                tms_errorf("invalid body setup");
            }
        } else {
            b[x] = this->p[x]->body;
            a[x] = b2Vec2(0,0);
            //tms_infof("%d SELF body ------", x);
        }
    }

    if (entities[0] && entities[0] == entities[1]) {
        /* Both ends of the cable are plugged into the same entity,
         * set length to 0. */
        this->length = 0.f;

        return;
    }

    if (!b[0] || !b[1]) {
        /* Neither plug is connected to something with a body,
         * set length to 0. */
        this->length = 0.f;

        return;
    }

    bool same_group = false;

    if (b[0] == b[1] && entities[0] && entities[1] && entities[0]->gr == entities[1]->gr) {
        same_group = true;
    }

    if (this->saved_length > 0.f) {
        this->length = this->saved_length;
        this->saved_length = 0.f;
    } else {
        this->length = (b[0]->GetWorldPoint(a[0])-b[1]->GetWorldPoint(a[1])).Length() + EPSILON + this->extra_length;
    }

    if (!same_group) {
        b2RopeJointDef rjd;
        rjd.localAnchorA = a[0];
        rjd.localAnchorB = a[1];
        rjd.bodyA = b[0];
        rjd.bodyB = b[1];
        rjd.collideConnected = true;
        rjd.maxLength = this->length;

        if (!W->is_paused()) {
            if (!this->ji) {
                this->ji = new joint_info(JOINT_TYPE_CABLE, this);
            }
            (this->joint = (b2RopeJoint*)W->b2->CreateJoint(&rjd))->SetUserData(this->ji);
        } else {
            this->joint = 0;
        }
    }
}

void
cable::ghost_update(void)
{
    this->update();
}

void
cable::update(void)
{
    int num = __sync_fetch_and_add(&counter, 1);

    if (num > MAX_CABLES - 1) num = MAX_CABLES - 1;

    if (W->is_paused() && W->b2) {
        this->create_joint(); /* update the length of the cable every frame if we are paused */
    }

    int base = num*(((buf->size/MAX_CABLES))/sizeof(struct cable_vert));
    cable_vert *v = ((cable_vert *)buf->get_buffer())+base;

    float cable_z = fmaxf(this->p[0]->get_layer(), this->p[1]->get_layer()) * LAYER_DEPTH;

    b2Vec2 p0 = this->p[0]->get_position();
    b2Vec2 p1 = this->p[1]->get_position();

    /* .                 . 0 = 2
     *          .          1 = 3
     *      .       .      2 = 5
     *   .    .   .    .   3 = 9
     *  . .  . . . .  . .  4 = 17,
     *
     *  num points = (2^NUM_SUBDIVIES-1)+1
     *  */
    int num_p = points_per_cable;
    tvec3 pt[num_p];

    pt[0] = (tvec3){p0.x, p0.y, this->p[0]->get_layer() * LAYER_DEPTH+.6f};
    pt[num_p-1] = (tvec3){p1.x, p1.y, this->p[1]->get_layer() * LAYER_DEPTH+.6f};

    float b = (p1-p0).Length();
    float r = this->length;

    float b_bias = 0.9f;

    for (int x=0; x<NUM_SUBDIVISIONS; x++) {
        int num_steps = (int)pow(2.f, (float)x);
        int freq = (num_p/num_steps);

        /* Each subdivision needs:
         *   b, the current length
         *   r, the minimum length
         *
         * Each subdivision's job is to make sure the
         * archlength along the cable is as close to
         * the minimum length as possible.
         *
         * We compute h by constructing a right triangle
         * with b/2 as base and r/2 as the hypothenuse.
         *
         * To allow for more subdivisions, 'h' is biased
         * giving the next subdivision a guessed b and r.
         * By adjusting the bias factor (h_bias), we can
         * decide how "roundly" the cable will bend.
         * As the two end vertices gets closer, a higher
         * h_bias will form the cable into a elliptical shape,
         * while a lower h_bias forms a triangle.
         **/

        float lb = fmaxf(r-b, 0.f);
        float leftover = lb * (1.f-b_bias);
        float b_next = r/2.f-leftover;
        float r_next = r/2.f;

        b-=leftover;

        float h;
        if (r-b < .001f) {
            h = 0.f;
        } else {
            h = sqrtf(powf(r/2.f, 2.f) - powf(b/2.f, 2.f));
        }

        for (int y=freq/2; y<num_p; y+=freq) {
            int _p0 = y - freq/2;
            int _p1 = y + freq/2;

            pt[y].x = (pt[_p0].x + pt[_p1].x)/2.f;
            pt[y].y = (pt[_p0].y + pt[_p1].y)/2.f;
            pt[y].z = 0.75f + cable_z;

            tvec2 nor = (tvec2){-(pt[_p1].y-pt[_p0].y), (pt[_p1].x-pt[_p0].x)};
            float norlen = tvec2_magnitude(&nor);
            float il;
            if (norlen < 0.001f) {
                il = 0.f;
            } else {
                il = 1.f/norlen;
            }
            nor.x *= il;
            nor.y *= il;

            /* raise the midpoint along the normal */
            pt[y].x += nor.x*h;
            pt[y].y += nor.y*h;
            //
        }

        b = b_next;
        r = r_next;
    }

    for (int y=0; y<num_p; y++) {
        int _p0 = y - 1;
        int _p1 = y;
        if (_p0 < 0) {_p0 = 0; _p1=1;};

        tvec3 tangent = (tvec3){pt[_p1].x - pt[_p0].x, pt[_p1].y - pt[_p0].y, 0.f};

        float tlen = tvec3_magnitude(&tangent);
        float ilen;

        if (tlen < 0.000001f) {
            ilen = 0.f;
        } else {
            ilen = 1.f/tlen;
        }

        tangent.x *= ilen;
        tangent.y *= ilen;

        tvec3 normal = (tvec3){-tangent.y, tangent.x, 0.f};

        v[y*QUALITY+0].pos = (tvec3){pt[y].x - _cam_x, pt[y].y - _cam_y, pt[y].z-WIDTH};
        v[y*QUALITY+0].nor = (tvec3){0.f, 0.f, -1.f};

        v[y*QUALITY+1].pos = (tvec3){pt[y].x - normal.x*WIDTH - _cam_x, pt[y].y - normal.y*WIDTH - _cam_y, pt[y].z};
        v[y*QUALITY+1].nor = (tvec3){-normal.x, -normal.y, 0.f};

        v[y*QUALITY+2].pos = (tvec3){pt[y].x - _cam_x, pt[y].y - _cam_y, pt[y].z+WIDTH};
        v[y*QUALITY+2].nor = (tvec3){0.f, 0.f, 1.f};

        v[y*QUALITY+3].pos = (tvec3){pt[y].x + normal.x*WIDTH - _cam_x, pt[y].y + normal.y*WIDTH - _cam_y, pt[y].z};
        v[y*QUALITY+3].nor = (tvec3){normal.x, normal.y, 0.f};
    }

    this->p[0]->update();
    this->p[1]->update();
}

plug::plug(cable *c)
{
    this->set_flag(ENTITY_IS_HIGH_PRIO,         true);
    this->set_flag(ENTITY_ALLOW_CONNECTIONS,    false);
    this->set_flag(ENTITY_ALLOW_ROTATION,       false);
    this->set_flag(ENTITY_IS_OWNED,             true);

    this->plug_type = PLUG_PLUG;
    this->type = ENTITY_PLUG;
    this->c = c;
    this->s = 0;
    this->plugged_edev = 0;
    this->pending.clear();

    /* TODO: red wire should only have one pluggrej */

    this->update_mesh();

    tmat4_load_identity(this->M);
    tmat3_load_identity(this->N);

    switch (c->ctype) {
        case CABLE_RED: this->set_material(&m_cable_red); break;
        case CABLE_BLACK: this->set_material(&m_cable_black); break;
        case CABLE_BLUE: this->set_material(&m_cable_blue); break;
        default: tms_fatalf("Invalid cable type %d", c->ctype);
    }
}

void
plug::update_mesh()
{
    switch (this->c->ctype) {
        case CABLE_BLACK:
            this->set_mesh(mesh_factory::get_mesh(MODEL_PLUG_FEMALE));
            break;
        case CABLE_RED:
            /* no mesh for red cables, they are added to a render buffer */
            break;

        case CABLE_BLUE:
            this->set_mesh(mesh_factory::get_mesh(MODEL_IFPLUG_FEMALE));
            break;
    }
}

void
cable::on_load(bool created, bool has_state)
{
}

bool
cable::connect(plug *p, edevice *e, uint8_t s)
{
    isocket *ss;

    if (s > 127) {
        ss = &e->s_out[s-128];
    } else {
        ss = &e->s_in[s];
    }

    bool ret = this->connect(p, e, ss);

    p->update_mesh();

    return ret;
}

void
cable::disconnect(plug *p)
{
    if (p->plugged_edev) {
        edevice *e = p->plugged_edev;
        int dir = e->get_socket_dir(p->s);

        switch (dir) {
            case CABLE_IN: this->p_out = -1; break;
            case CABLE_OUT: this->p_in = -1; break;
        }

        p->plugged_edev = 0;
        p->s->p = 0;
        p->s = 0;

        if (!this->freeze) {
            this->create_joint();

            p->update_mesh();
        }
    }
}

bool
cable::connect(plug *p, edevice *e, isocket *s)
{
    int index;

    if (s->ctype != this->ctype) {
        tms_warnf("incompatible cable types");
        return false;
    }

    if (p == this->p[0]) {
        index = 0;
    } else if (p == this->p[1]) {
        index = 1;
    } else {
        tms_errorf("cable: this is not my plug! :(");
        return false;
    }

    if (p->s) {
        tms_warnf("already have a socket");
        return false;
    }

    int dir = e->get_socket_dir(s);

    switch (dir) {
        case CABLE_IN: this->p_out = index; break;
        case CABLE_OUT: this->p_in = index; break;
        default: tms_errorf("cable: socket does not belong to edevice (%d)", dir); return false;
    }

    p->s = s;
    p->plugged_edev = e;
    //p->set_layer(p->plugged_edev->get_entity()->get_layer());
    s->p = p;

    p->set_prio(e->get_entity()->get_layer());

    if (this->ready) {
        this->create_joint();
    }

    return true;
}

void
plug::remove_from_world()
{
    if (!this->c->freeze) {
        this->disconnect();
    }

    if (this->body != 0) {
        this->body->GetWorld()->DestroyBody(this->body);
        this->body = 0;
    }
}

void
plug::create_body()
{
    if (W && !this->body) {
        b2BodyDef bd;
        bd.type = this->get_dynamic_type();
        bd.fixedRotation = true;
        bd.angle = 0.f;
        bd.position = this->_pos;

        b2CircleShape c;
        c.m_radius = .25f;
        c.m_p = b2Vec2(0,0);

        b2FixtureDef fd;
        fd.shape = &c;
        fd.density = 0.2f;
        fd.friction = .5f;
        fd.restitution = .2f;
        fd.isSensor = true;

        b2Body *b = W->b2->CreateBody(&bd);
        b->SetAngularDamping(1.f);
        b->SetLinearDamping(1.f);
        (b->CreateFixture(&fd))->SetUserData(this);
        this->body = b;
        this->set_layer(this->prio);
    }
}

float
plug::get_angle()
{
    if (this->is_connected()) {
        return this->plugged_edev->get_entity()->get_angle()+this->s->angle;
    } else if (this->body) {
        b2Vec2 p0 = this->get_other()->get_position() - this->get_position();
        p0 *= 1.f/p0.Length();
        float a = atan2f(p0.y, p0.x);
        return a;
    }

    return -M_PI/2.f;
}

b2Vec2
plug_base::get_position()
{
    if (this->is_connected()) {
        return this->plugged_edev->get_entity()->local_to_world(
                this->s->lpos/* + b2Vec2(cosf(this->s->angle)*.15f, sinf(this->s->angle)*.15f)*/
                , 0);
    } else {
        return entity::get_position();
        //return this->body->GetPosition();
    }
}

void
plug_base::update(void)
{
    b2Vec2 p = this->get_position();
    float a = this->get_angle() + M_PI/2.f;
    float cs,sn;
    tmath_sincos(a, &sn, &cs);

    int l = this->prio;

    if (this->c && this->c->ctype == CABLE_RED) {
        int num = __sync_fetch_and_add(&plug_counter[l], 1);

        if (num > MAX_PLUGS - 1) num = MAX_PLUGS - 1;

        int base = (num*vertices_per_plug);
        cable_vert *v = ((cable_vert *)plug_buf[l]->get_buffer());
        v += base;

        struct tms_mesh *mm = mesh_factory::get_mesh(MODEL_PLUG_SIMPLE);
        struct tms_gbuffer *r_vbuf = mm->vertex_array->gbufs[0].gbuf;

        cvert *rv = (cvert *)((char*)tms_gbuffer_get_buffer(r_vbuf)+mm->v_start);
        int num_rv = mm->v_count;

        for (int x=0; x<num_rv; x++) {
            v[x].pos = (tvec3){
                rv[x].p.x * cs - rv[x].p.y * sn + p.x - _cam_x,
                rv[x].p.x * sn + rv[x].p.y * cs + p.y - _cam_y,
                rv[x].p.z + this->get_layer() + CABLE_Z
            };
            v[x].nor = (tvec3){
                rv[x].n.x * cs - rv[x].n.y * sn,
                rv[x].n.x * sn + rv[x].n.y * cs,
                rv[x].n.z
            };
        }
    } else {
        this->M[0] = cs;
        this->M[1] = sn;
        this->M[4] = -sn;
        this->M[5] = cs;
        this->M[12] = p.x;
        this->M[13] = p.y;
        this->M[14] = this->prio * LAYER_DEPTH + CABLE_Z;

        tmat3_copy_mat4_sub3x3(this->N, this->M);
    }
}

void
plug_base::add_to_world()
{
    this->pending.clear();

    if (!this->is_connected()) {
        this->create_body();
    } else {
        if (this->get_dir() == CABLE_IN) {
            ((socket_in*)this->s)->reset();
        }
    }
}

/* permanently disconnect this plug */
void
plug::disconnect()
{
    if (this->is_connected()) {
        /* displace the plug a little to visualize the disconnect */
        float cs = cosf(this->s->angle);
        float sn = sinf(this->s->angle);

        this->_pos = this->get_position() + b2Vec2(cs*.5f,sn*.5f);
        this->_angle = this->get_angle();
        this->create_body();
        this->c->disconnect(this);
    }
}

int
plug::connect(edevice *e, isocket *s)
{
    plug_base *other = this->get_other();
    int dz = std::abs(this->get_layer() - other->get_layer());

    if (dz > 1) {
        return 1;
    }

    if (!this->c->connect(this, e, s)) {
        return 2;
    }

    if (this->body) {
        this->body->GetWorld()->DestroyBody(this->body);
        this->body = 0;
    }

    return T_OK;
}

ifdevice*
plug::find_ifdevice()
{
    int limit = 0;

    plug *curr = this;
    ifdevice *i;

    while (curr && limit < 20 && (curr = curr->c->get_other(curr))) {
        if (curr->plugged_edev) {
            if ((i = curr->plugged_edev->get_ifdevice())) {
                return i;
            }

            for (int x=0; x<curr->plugged_edev->num_s_out; x++) {
                if (curr->plugged_edev->s_out[x].ctype == CABLE_BLUE) {
                    curr = static_cast<plug*>(curr->plugged_edev->s_out[x].p);
                    break;
                }
            }
        } else {
            break;
        }

        limit ++;
    }

    return 0;
}

void
plug_base::on_grab(game *g)
{
    this->pending.clear();

    //entity::on_grab(g);
}

void
plug_base::setup()
{
    this->pending.clear();
}

void
plug_base::on_pause()
{
    this->pending.clear();
}

void
plug_base::on_paused_touch(b2Fixture *my, b2Fixture *other)
{
    entity *o = (entity*)other->GetUserData();
    //tms_infof("TOUCH");

    if (o && (G->state.sandbox || o->get_property_entity()->is_moveable())) {
        if (o->flag_active(ENTITY_IS_EDEVICE)) {
            edevice *e = o->get_edevice();

            int type;
            if (!this->c)
                type = CABLE_RED;
            else
                type = this->c->ctype;

            int m1;
            int m2 = e->get_inout_mask(type);

            if (this->c)
                m1 = this->c->get_inout_mask(type);
            else
                m1 = m2; /*XXX uglyhack */

            if (m1 & m2) {
                G->add_highlight(o, false);
                this->pending.insert(e);
            }
        }
    }
}

void
plug_base::set_layer(int z)
{
    plug_base *other = this->get_other();

    if (other) {
        int dz = std::abs(z - other->get_layer());

        if (dz > 1) {
            this->disconnect();
            //return;
        }
    }
    entity::set_layer(z);
}

uint8_t
plug_base::get_socket_index(void)
{
    if (this->is_connected())
        return this->plugged_edev->get_socket_index(this->s);

    return 0;
}

void
plug_base::on_paused_untouch(b2Fixture *my, b2Fixture *other)
{
    //tms_infof("untoch");
    this->pending.erase(static_cast<edevice*>(other->GetUserData()));
}

int
plug::get_dir()
{
    if (this->plugged_edev) {
        return this->plugged_edev->get_socket_dir(this->s);
    } else {
        if (this->c->get_other(this)
                && this->c->get_other(this)->plugged_edev) {
            return 3 - this->c->get_other(this)->plugged_edev->get_socket_dir(this->s);
        }
    }

    return 0;
}

void
plug_base::on_release(game *g)
{
    if (this->pending.size() > 0) {
        tms_infof("num pending: %lu", (unsigned long)this->pending.size());
        /* find the nearest pending */
        edevice *nearest_edev = 0;
        float dist = 3.f;
        b2Vec2 p = this->get_position();

        for (std::set<edevice*>::iterator i = this->pending.begin(); i != this->pending.end(); i++) {
            /* loop through all connections and find the clostest */
            edevice *e = *i;
            for (int x=0; x<e->num_s_in; x++) {
                float d = (p - e->get_entity()->local_to_world(e->s_in[x].lpos, 0)).LengthSquared();

                if (d < dist) {
                    nearest_edev = e;
                    dist = d;
                }
            }
            for (int x=0; x<e->num_s_out; x++) {
                float d = (p - e->get_entity()->local_to_world(e->s_out[x].lpos, 0)).LengthSquared();

                if (d < dist) {
                    nearest_edev = e;
                    dist = d;
                }
            }
        }

        if (nearest_edev && (G->state.sandbox || nearest_edev->get_entity()->get_property_entity()->is_moveable())) {
            G->add_highlight(nearest_edev->get_entity(), false);
            g->open_socket_selector(this, nearest_edev);
        }
    }
}

#undef QUALITY
#undef WIDTH
