package com.srbenoit.modeling.cell;

import com.srbenoit.geom.Vector2;
import com.srbenoit.modeling.grid.GridMember2Int;
import com.srbenoit.modeling.grid.PointGridMember2;

/**
 * An actin filament. Each filament is characterized by a point position of its pointed end, its
 * length, and a set of crosslinks to other objects (filaments or membrane elements). Each
 * maintains an upstream link to the prior filament (null if this filament is adjacent to the
 * membrane and subject to polymerization, a downstream link to the next filament in the chain
 * (null if this filament is the furthest from the membrane and subject to deploymerization), and a
 * reference to the membrane element this filament ultimately attaches to.
 */
public class ActinFilament extends PointGridMember2 {

    /** the next actin filament toward the membrane (null if attached to membrane) */
    private ActinFilament upstream;

    /** the next actin filament away from the membrane (null if furthest from membrane) */
    private ActinFilament downstream;

    /** the length of the filament */
    private double length;

    /** the radius of the filament */
    private double radius;

    /** the force on the element */
    private final Vector2 force;

    /** working vector */
    private final Vector2 vec;

    /**
     * Constructs a new <code>ActinFilament</code>.
     *
     * @param  xCoord  the X coordinate of the pointed end
     * @param  yCoord  the Y coordinate of the pointed end
     * @param  len     the initial length of the filament
     * @param  rad     the initial radius of the filament
     * @param  prior   the prior (closer to the membrane) filament in the chain
     */
    public ActinFilament(final double xCoord, final double yCoord, final double len,
        final double rad, final ActinFilament prior) {

        super(xCoord, yCoord);

        this.length = len;
        this.radius = rad;

        this.upstream = prior;
        this.downstream = null;
        this.force = new Vector2();

        if (prior != null) {
            prior.setDownstream(this);
        }

        this.vec = new Vector2();
    }

    /**
     * Gets the radius to use when drawing the grid member.
     *
     * @return  the radius (0 to draw as a point)
     */
    @Override public double getRadius() {

        return this.radius;
    }

    /**
     * Gets this filament's downstream (further from the membrane) attached filament.
     *
     * @return  the downstream filament
     */
    public ActinFilament getDownstream() {

        return this.downstream;
    }

    /**
     * Sets this filament's downstream (further from the membrane) attached filament.
     *
     * @param  filament  the downstream filament
     */
    public void setDownstream(final ActinFilament filament) {

        this.downstream = filament;
    }

    /**
     * Gets this filament's upstream (closer to the membrane) attached filament.
     *
     * @return  the upstream filament
     */
    public ActinFilament getUpstream() {

        return this.upstream;
    }

    /**
     * Sets this filament's upstream (closer to the membrane) attached filament.
     *
     * @param  filament  the upstream filament
     */
    public void setUpstream(final ActinFilament filament) {

        this.upstream = filament;
    }

    /**
     * Gets the length of the filament.
     *
     * @return  the length
     */
    public double getLength() {

        return this.length;
    }

    /**
     * Sets the length of the filament
     *
     * @param  len  the new length
     */
    public void setLength(final double len) {

        this.length = len;
    }

    /**
     * Alters the length of the filament.
     *
     * @param  delta  the change to the length
     */
    public void adjustLength(final double delta) {

        this.length += delta;
        this.radius += delta;
    }

    /**
     * Gets the force vector for the filament.
     *
     * @return  the force vector
     */
    public Vector2 getForce() {

        return this.force;
    }

    /**
     * Computes the force on the element.
     */
    public void computeInnerForce() {

        ActinFilament filament;
        double dist;
        double delta;

        // Force from connection to downstream actin, if any
        filament = this.downstream;

        if (filament != null) {
            dist = filament.dist(this);
            delta = dist - filament.getLength();
            this.vec.vectorBetween(this, filament);
            this.vec.normalize();
            this.vec.scaleVec(delta * Simulation.FILAMENT_SPRING);
            this.force.addVec(this.vec);
            filament.getForce().subVec(this.vec); // Don't compute again
        }
    }

    /**
     * Computes the force on the element due to interactions.
     *
     * @param  maxForce  the maximum force from non-interaction sources
     */
    public void computeInteractionForce(final double maxForce) {

        double range;
        int count;
        GridMember2Int nbr;
        double dist;
        double scale;

        count = getNumNeighbors();

        for (int i = 0; i < count; i++) {
            nbr = getNeighbor(i);

            if (nbr instanceof MembraneElement) {

                range = Simulation.getInstance().getMaxSep() + getRadius();
                dist = nbr.dist(this);

                if (dist < range) {
                    scale = Simulation.SS_FORCE.forceTimesEqDist(dist / range) / range;

                    if (scale > maxForce) {
                        scale = maxForce;
                    }

                    vec.vectorBetween(nbr, this);
                    vec.normalize();
                    vec.scaleVec(scale);
                    this.force.addVec(vec);
                }
            } else if (nbr instanceof ActinFilament) {
                range = nbr.getRadius() + getRadius();
                dist = nbr.dist(this);

                if (dist < (2.5 * range)) {
                    scale = Simulation.ACTIN_FORCE1.forceTimesEqDist(dist / range) / range;

                    if (scale > maxForce) {
                        scale = maxForce;
                    }

                    vec.vectorBetween(nbr, this);
                    vec.normalize();
                    vec.scaleVec(scale);
                    this.force.addVec(vec);

                    scale = Simulation.ACTIN_FORCE2.forceTimesEqDist(dist / range) / range;

                    if (scale > maxForce) {
                        scale = maxForce;
                    }

                    vec.vectorBetween(nbr, this);
                    vec.normalize();
                    vec.scaleVec(scale);
                    this.force.addVec(vec);
                }
            }
        }
    }
}
