/*
 VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
 Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
 electronic mail : Yann.Ponty@lri.fr
 paper mail : LRI, bat 490 Universit� Paris-Sud 91405 Orsay Cedex France

 This file is part of VARNA version 3.1.
 VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

 VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with VARNA version 3.1.
 If not, see http://www.gnu.org/licenses.
 */
package fr.orsay.lri.varna.models.naView;


import java.util.ArrayList;

import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
import fr.orsay.lri.varna.interfaces.InterfaceVARNAListener;
import fr.orsay.lri.varna.interfaces.InterfaceVARNAObservable;

public class NAView {
	private final double ANUM = 9999.0;
	private final int MAXITER = 500;

	private ArrayList<Base> bases;
	private int nbase, nregion, loop_count;

	private Loop root = new Loop();
	private ArrayList<Loop> loops;

	private ArrayList<Region> regions;

	private Radloop rlphead = new Radloop();

	private double lencut=0.8;
	private final double RADIUS_REDUCTION_FACTOR = 1.4;

	// show algorithm step by step
	private boolean debug = false;

	private double angleinc;

	private double _h;

	private ArrayList<InterfaceVARNAListener> _listeVARNAListener = new ArrayList<InterfaceVARNAListener>();

	private boolean noIterationFailureYet = true;

	double HELIX_FACTOR = 0.6;
	double BACKBONE_DISTANCE = 27;

	public int naview_xy_coordinates(ArrayList<Short> pair_table2,
			ArrayList<Double> x, ArrayList<Double> y)
			throws ExceptionNAViewAlgorithm {
		if (debug)
			System.out.println("naview_xy_coordinates");
		if (pair_table2.size() == 0)
			return 0;
		int i;
		ArrayList<Integer> pair_table = new ArrayList<Integer>(pair_table2
				.size() + 1);
		pair_table.add(pair_table2.size());

		for (int j = 0; j < pair_table2.size(); j++) {
			pair_table.add(pair_table2.get(j) + 1);
		}

		if (debug) {
			infoStructure(pair_table);
		}
		// length
		nbase = pair_table.get(0);
		bases = new ArrayList<Base>(nbase + 1);

		for (int index = 0; index < bases.size(); index++) {
			bases.add(new Base());
		}

		regions = new ArrayList<Region>();
		for (int index = 0; index < nbase + 1; index++) {
			regions.add(new Region());
		}

		read_in_bases(pair_table);

		if (debug)
			infoBasesMate();

		rlphead = null;

		find_regions();

		if (debug)
			infoRegions();

		loop_count = 0;
		loops = new ArrayList<Loop>(nbase + 1);
		for (int index = 0; index < nbase + 1; index++) {
			loops.add(new Loop());
		}

		construct_loop(0);

		if (debug)
			infoBasesExtracted();

		find_central_loop();

		if (debug)
			infoRoot();

		if (debug)
			dump_loops();

		traverse_loop(root, null);

		for (i = 0; i < nbase; i++) {
			x.add(100 + BACKBONE_DISTANCE * bases.get(i + 1).getX());
			y.add(100 + BACKBONE_DISTANCE * bases.get(i + 1).getY());
		}

		return nbase;
	}

	private void infoStructure(ArrayList<Integer> pair_table) {
		System.out.println("structure:");
		for (int j = 0; j < pair_table.size(); j++) {
			System.out.print("#" + j + ":" + pair_table.get(j) + "\t");
			if (j % 10 == 0)
				System.out.println();
		}
		System.out.println();
	}

	private void infoBasesMate() {
		System.out.println("Bases mate:");
		for (int index = 0; index < bases.size(); index++) {
			System.out.print("#" + index + ":" + bases.get(index).getMate()
					+ "\t");
			if (index % 10 == 0)
				System.out.println();
		}
		System.out.println();
	}

	private void infoRegions() {
		System.out.println("regions:");
		for (int index = 0; index < regions.size(); index++) {
			System.out.print("(" + regions.get(index).getStart1() + ","
					+ regions.get(index).getStart2() + ";"
					+ regions.get(index).getEnd1() + ","
					+ regions.get(index).getEnd2() + ")\t\t");
			if (index % 5 == 0)
				System.out.println();
		}
		System.out.println();
	}

	private void infoBasesExtracted() {
		System.out.println("Bases extracted:");
		for (int index = 0; index < bases.size(); index++) {
			System.out.print("i=" + index + ":"
					+ bases.get(index).isExtracted() + "\t");
			if (index % 5 == 0)
				System.out.println();
		}
		System.out.println();
	}

	private void infoRoot() {
		System.out.println("root" + root.getNconnection() + ";"
				+ root.getNumber());
		System.out.println("\troot : ");
		System.out.println("\tdepth=" + root.getDepth());
		System.out.println("\tmark=" + root.isMark());
		System.out.println("\tnumber=" + root.getNumber());
		System.out.println("\tradius=" + root.getRadius());
		System.out.println("\tx=" + root.getX());
		System.out.println("\ty=" + root.getY());
		System.out.println("\tnconnection=" + root.getNconnection());
	}

	private void read_in_bases(ArrayList<Integer> pair_table) {
		if (debug)
			System.out.println("read_in_bases");

		int i, npairs;

		// Set up an origin.
		bases.add(new Base());
		bases.get(0).setMate(0);
		bases.get(0).setExtracted(false);
		bases.get(0).setX(ANUM);
		bases.get(0).setY(ANUM);

		for (npairs = 0, i = 1; i <= nbase; i++) {
			bases.add(new Base());
			bases.get(i).setExtracted(false);
			bases.get(i).setX(ANUM);
			bases.get(i).setY(ANUM);
			bases.get(i).setMate(pair_table.get(i));
			if ((int) pair_table.get(i) > i)
				npairs++;
		}
		// must have at least 1 pair to avoid segfault
		if (npairs == 0) {
			bases.get(1).setMate(nbase);
			bases.get(nbase).setMate(1);
		}
	}

	/**
	 * Identifies the regions in the structure.
	 */
	private void find_regions()

	{
		if (debug)
			System.out.println("find_regions");
		int i, mate, nb1;
		nb1 = nbase + 1;
		ArrayList<Boolean> mark = new ArrayList<Boolean>(nb1);
		for (i = 0; i < nb1; i++)
			mark.add(false);
		nregion = 0;
		for (i = 0; i <= nbase; i++) {
			if ((mate = bases.get(i).getMate()) != 0 && !mark.get(i)) {
				regions.get(nregion).setStart1(i);
				regions.get(nregion).setEnd2(mate);
				mark.set(i, true);
				mark.set(mate, true);
				bases.get(i).setRegion(regions.get(nregion));
				bases.get(mate).setRegion(regions.get(nregion));
				for (i++, mate--; i < mate && bases.get(i).getMate() == mate; i++, mate--) {
					mark.set(mate, true);
					mark.set(i, true);
					bases.get(i).setRegion(regions.get(nregion));
					bases.get(mate).setRegion(regions.get(nregion));
				}
				regions.get(nregion).setEnd1(--i);
				regions.get(nregion).setStart2(mate + 1);
				if (debug) {
					if (nregion == 0)
						System.out.printf("\nRegions are:\n");
					System.out.printf(
							"Region %d is %d-%d and %d-%d with gap of %d.\n",
							nregion + 1, regions.get(nregion).getStart1(),
							regions.get(nregion).getEnd1(), regions
									.get(nregion).getStart2(), regions.get(
									nregion).getEnd2(), regions.get(nregion)
									.getStart2()
									- regions.get(nregion).getEnd1() + 1);
				}
				nregion++;
			}
		}
	}

	/**
	 * Starting at residue ibase, recursively constructs the loop containing
	 * said base and all deeper bases.
	 * 
	 * @throws ExceptionNAViewAlgorithm
	 */
	private Loop construct_loop(int ibase) throws ExceptionNAViewAlgorithm {
		if (debug)
			System.out.println("construct_loop");
		int i, mate;
		Loop retloop = new Loop(), lp = new Loop();
		Connection cp = new Connection();
		Region rp = new Region();
		Radloop rlp = new Radloop();
		retloop = loops.get(loop_count++);
		retloop.setNconnection(0);
		//System.out.println(""+ibase+" "+nbase);
		//ArrayList<Connection> a = new ArrayList<Connection>(nbase + 1); 
		//retloop.setConnections(a);
//		for (int index = 0; index < nbase + 1; index++)
//			retloop.getConnections().add(new Connection());
		//for (int index = 0; index < nbase + 1; index++)
		//	retloop.addConnection(index,new Connection());
		retloop.setDepth(0);
		retloop.setNumber(loop_count);
		retloop.setRadius(0.0);
		for (rlp = rlphead; rlp != null; rlp = rlp.getNext())
			if (rlp.getLoopnumber() == loop_count)
				retloop.setRadius(rlp.getRadius());
		i = ibase;
		do {
			if ((mate = bases.get(i).getMate()) != 0) {
				rp = bases.get(i).getRegion();
				if (!bases.get(rp.getStart1()).isExtracted()) {
					if (i == rp.getStart1()) {
						bases.get(rp.getStart1()).setExtracted(true);
						bases.get(rp.getEnd1()).setExtracted(true);
						bases.get(rp.getStart2()).setExtracted(true);
						bases.get(rp.getEnd2()).setExtracted(true);
						lp = construct_loop(rp.getEnd1() < nbase ? rp.getEnd1() + 1
								: 0);
					} else if (i == rp.getStart2()) {
						bases.get(rp.getStart2()).setExtracted(true);
						bases.get(rp.getEnd2()).setExtracted(true);
						bases.get(rp.getStart1()).setExtracted(true);
						bases.get(rp.getEnd1()).setExtracted(true);
						lp = construct_loop(rp.getEnd2() < nbase ? rp.getEnd2() + 1
								: 0);
					} else {
						throw new ExceptionNAViewAlgorithm(
								"naview:Error detected in construct_loop. i = "
										+ i + " not found in region table.\n");
					}
					retloop.setNconnection(retloop.getNconnection() + 1);
					cp = new Connection();
					retloop.setConnection(retloop.getNconnection() - 1,	cp);
					retloop.setConnection(retloop.getNconnection(), null);
					cp.setLoop(lp);
					cp.setRegion(rp);
					if (i == rp.getStart1()) {
						cp.setStart(rp.getStart1());
						cp.setEnd(rp.getEnd2());
					} else {
						cp.setStart(rp.getStart2());
						cp.setEnd(rp.getEnd1());
					}
					cp.setExtruded(false);
					cp.setBroken(false);
					lp.setNconnection(lp.getNconnection() + 1);
					cp = new Connection();
					lp.setConnection(lp.getNconnection() - 1, cp);
					lp.setConnection(lp.getNconnection(), null);
					cp.setLoop(retloop);
					cp.setRegion(rp);
					if (i == rp.getStart1()) {
						cp.setStart(rp.getStart2());
						cp.setEnd(rp.getEnd1());
					} else {
						cp.setStart(rp.getStart1());
						cp.setEnd(rp.getEnd2());
					}
					cp.setExtruded(false);
					cp.setBroken(false);
				}
				i = mate;
			}
			if (++i > nbase)
				i = 0;
		} while (i != ibase);
		return retloop;
	}

	/**
	 * Displays all the loops.
	 */
	private void dump_loops() {
		System.out.println("dump_loops");
		int il, ilp, irp;
		Loop lp;
		Connection cp;

		System.out.printf("\nRoot loop is #%d\n", loops.indexOf(root) + 1);
		for (il = 0; il < loop_count; il++) {
			lp = loops.get(il);
			System.out.printf("Loop %d has %d connections:\n", il + 1, lp
					.getNconnection());
			for (int i = 0; (cp = lp.getConnection(i)) != null; i++) {
				ilp = (loops.indexOf(cp.getLoop())) + 1;
				irp = (regions.indexOf(cp.getRegion())) + 1;
				System.out.printf("  Loop %d Region %d (%d-%d)\n", ilp, irp, cp
						.getStart(), cp.getEnd());
			}
		}
	}

	/**
	 * Find node of greatest branching that is deepest.
	 */
	private void find_central_loop() {
		if (debug)
			System.out.println("find_central_loop");
		Loop lp = new Loop();
		int maxconn, maxdepth, i;

		determine_depths();
		maxconn = 0;
		maxdepth = -1;
		for (i = 0; i < loop_count; i++) {
			lp = loops.get(i);
			if (lp.getNconnection() > maxconn) {
				maxdepth = lp.getDepth();
				maxconn = lp.getNconnection();
				root = lp;
			} else if (lp.getDepth() > maxdepth
					&& lp.getNconnection() == maxconn) {
				maxdepth = lp.getDepth();
				root = lp;
			}
		}
	}

	/**
	 * Determine the depth of all loops.
	 */
	private void determine_depths() {
		if (debug)
			System.out.println("determine_depths");
		Loop lp = new Loop();
		int i, j;

		for (i = 0; i < loop_count; i++) {
			lp = loops.get(i);
			for (j = 0; j < loop_count; j++)
				loops.get(j).setMark(false);
			lp.setDepth(depth(lp));
		}
	}

	/**
	 * Determines the depth of loop, lp. Depth is defined as the minimum
	 * distance to a leaf loop where a leaf loop is one that has only one or no
	 * connections.
	 */
	private int depth(Loop lp) {
		if (debug)
			System.out.println("depth");
		int count, ret, d;

		if (lp.getNconnection() <= 1)
			return 0;
		if (lp.isMark())
			return -1;
		lp.setMark(true);
		count = 0;
		ret = 0;
		for (int i = 0; lp.getConnection(i) != null; i++) {
			d = depth(lp.getConnection(i).getLoop());
			if (d >= 0) {
				if (++count == 1)
					ret = d;
				else if (ret > d)
					ret = d;
			}
		}
		lp.setMark(false);
		return ret + 1;
	}

	/**
	 * This is the workhorse of the display program. The algorithm is recursive
	 * based on processing individual loops. Each base pairing region is
	 * displayed using the direction given by the circle diagram, and the
	 * connections between the regions is drawn by equally spaced points. The
	 * radius of the loop is set to minimize the square error for lengths
	 * between sequential bases in the loops. The "correct" length for base
	 * links is 1. If the least squares fitting of the radius results in loops
	 * being less than 1/2 unit apart, then that segment is extruded.
	 * 
	 * The variable, anchor_connection, gives the connection to the loop
	 * processed in an previous level of recursion.
	 * 
	 * @throws ExceptionNAViewAlgorithm
	 */
	private void traverse_loop(Loop lp, Connection anchor_connection)
			throws ExceptionNAViewAlgorithm {
		if (debug)
			System.out.println("  traverse_loop");
		double xs, ys, xe, ye, xn, yn, angleinc, r;
		double radius, xc, yc, xo, yo, astart, aend, a;
		Connection cp, cpnext, acp, cpprev;
		int i, j, n, ic;
		double da, maxang;
		int count, icstart, icend, icmiddle, icroot;
		boolean done, done_all_connections, rooted;
		int sign;
		double midx, midy, nrx, nry, mx, my, vx, vy, dotmv, nmidx, nmidy;
		int icstart1, icup, icdown, icnext, direction;
		double dan, dx, dy, rr;
		double cpx, cpy, cpnextx, cpnexty, cnx, cny, rcn, rc, lnx, lny, rl, ac, acn, sx, sy, dcp;
		int imaxloop = 0;

		angleinc = 2 * Math.PI / (nbase + 1);
		acp = null;
		icroot = -1;
		int indice = 0;
		
		for (ic = 0; (cp = lp.getConnection(indice)) != null; indice++, ic++) {
			// xs = cos(angleinc*cp.setStart(); ys = sin(angleinc*cp.setStart();
			// xe =
			// cos(angleinc*cp.setEnd()); ye = sin(angleinc*cp.setEnd());
			xs = -Math.sin(angleinc * cp.getStart());
			ys = Math.cos(angleinc * cp.getStart());
			xe = -Math.sin(angleinc * cp.getEnd());
			ye = Math.cos(angleinc * cp.getEnd());
			xn = ye - ys;
			yn = xs - xe;
			r = Math.sqrt(xn * xn + yn * yn);
			cp.setXrad(xn / r);
			cp.setYrad(yn / r);
			cp.setAngle(Math.atan2(yn, xn));
			if (cp.getAngle() < 0.0)
				cp.setAngle(cp.getAngle() + 2 * Math.PI);
			if (anchor_connection != null
					&& anchor_connection.getRegion() == cp.getRegion()) {
				acp = cp;
				icroot = ic;
			}
		}
		// remplacement d'une etiquette de goto
		set_radius: while (true) {
			determine_radius(lp, lencut);
			radius = lp.getRadius()/RADIUS_REDUCTION_FACTOR;
			if (anchor_connection == null)
				xc = yc = 0.0;
			else {
				xo = (bases.get(acp.getStart()).getX() + bases
						.get(acp.getEnd()).getX()) / 2.0;
				yo = (bases.get(acp.getStart()).getY() + bases
						.get(acp.getEnd()).getY()) / 2.0;
				xc = xo - radius * acp.getXrad();
				yc = yo - radius * acp.getYrad();
			}

			// The construction of the connectors will proceed in blocks of
			// connected connectors, where a connected connector pairs means two
			// connectors that are forced out of the drawn circle because they
			// are too close together in angle.

			// First, find the start of a block of connected connectors

			if (icroot == -1)
				icstart = 0;
			else
				icstart = icroot;
			cp = lp.getConnection(icstart);
			count = 0;
			if (debug)
			{
				System.out.printf("Now processing loop %d\n", lp.getNumber());
				System.out.println("  "+lp);
			}
			done = false;
			do {
				j = icstart - 1;
				if (j < 0)
					j = lp.getNconnection() - 1;
				cpprev = lp.getConnection(j);
				if (!connected_connection(cpprev, cp)) {
					done = true;
				} else {
					icstart = j;
					cp = cpprev;
				}
				if (++count > lp.getNconnection()) {
					// Here everything is connected. Break on maximum angular
					// separation between connections.

					maxang = -1.0;
					for (ic = 0; ic < lp.getNconnection(); ic++) {
						j = ic + 1;
						if (j >= lp.getNconnection())
							j = 0;
						cp = lp.getConnection(ic);
						cpnext = lp.getConnection(j);
						ac = cpnext.getAngle() - cp.getAngle();
						if (ac < 0.0)
							ac += 2 * Math.PI;
						if (ac > maxang) {
							maxang = ac;
							imaxloop = ic;
						}
					}
					icend = imaxloop;
					icstart = imaxloop + 1;
					if (icstart >= lp.getNconnection())
						icstart = 0;
					cp = lp.getConnection(icend);
					cp.setBroken(true);
					done = true;
				}
			} while (!done);
			done_all_connections = false;
			icstart1 = icstart;
			if (debug)
				System.out.printf("  Icstart1 = %d\n", icstart1);
			while (!done_all_connections) {
				count = 0;
				done = false;
				icend = icstart;
				rooted = false;
				while (!done) {
					cp = lp.getConnection(icend);
					if (icend == icroot)
						rooted = true;
					j = icend + 1;
					if (j >= lp.getNconnection()) {
						j = 0;
					}
					cpnext = lp.getConnection(j);
					if (connected_connection(cp, cpnext)) {
						if (++count >= lp.getNconnection())
							break;
						icend = j;
					} else {
						done = true;
					}
				}
				icmiddle = find_ic_middle(icstart, icend, anchor_connection,
						acp, lp);
				ic = icup = icdown = icmiddle;
				if (debug)
					System.out.printf("  IC start = %d  middle = %d  end = %d\n",
							icstart, icmiddle, icend);
				done = false;
				direction = 0;
				while (!done) {
					if (direction < 0) {
						ic = icup;
					} else if (direction == 0) {
						ic = icmiddle;
					} else {
						ic = icdown;
					}
					if (ic >= 0) {
						cp = lp.getConnection(ic);
						if (anchor_connection == null || acp != cp) {
							if (direction == 0) {
								astart = cp.getAngle()
										- Math.asin(1.0 / 2.0 / radius);
								aend = cp.getAngle()
										+ Math.asin(1.0 / 2.0 / radius);
								bases.get(cp.getStart()).setX(
										xc + radius * Math.cos(astart));
								bases.get(cp.getStart()).setY(
										yc + radius * Math.sin(astart));
								bases.get(cp.getEnd()).setX(
										xc + radius * Math.cos(aend));
								bases.get(cp.getEnd()).setY(
										yc + radius * Math.sin(aend));
							} else if (direction < 0) {
								j = ic + 1;
								if (j >= lp.getNconnection())
									j = 0;
								cp = lp.getConnection(ic);
								cpnext = lp.getConnection(j);
								cpx = cp.getXrad();
								cpy = cp.getYrad();
								ac = (cp.getAngle() + cpnext.getAngle()) / 2.0;
								if (cp.getAngle() > cpnext.getAngle())
									ac -= Math.PI;
								cnx = Math.cos(ac);
								cny = Math.sin(ac);
								lnx = cny;
								lny = -cnx;
								da = cpnext.getAngle() - cp.getAngle();
								if (da < 0.0)
									da += 2 * Math.PI;
								if (cp.isExtruded()) {
									if (da <= Math.PI / 2)
										rl = 2.0;
									else
										rl = 1.5;
								} else {
									rl = 1.0;
								}
								bases.get(cp.getEnd()).setX(
										bases.get(cpnext.getStart()).getX()
												+ rl * lnx);
								bases.get(cp.getEnd()).setY(
										bases.get(cpnext.getStart()).getY()
												+ rl * lny);
								bases.get(cp.getStart()).setX(
										bases.get(cp.getEnd()).getX() + cpy);
								bases.get(cp.getStart()).setY(
										bases.get(cp.getEnd()).getY() - cpx);
							} else {
								j = ic - 1;
								if (j < 0)
									j = lp.getNconnection() - 1;
								cp = lp.getConnection(j);
								cpnext = lp.getConnection(ic);
								cpnextx = cpnext.getXrad();
								cpnexty = cpnext.getYrad();
								ac = (cp.getAngle() + cpnext.getAngle()) / 2.0;
								if (cp.getAngle() > cpnext.getAngle())
									ac -= Math.PI;
								cnx = Math.cos(ac);
								cny = Math.sin(ac);
								lnx = -cny;
								lny = cnx;
								da = cpnext.getAngle() - cp.getAngle();
								if (da < 0.0)
									da += 2 * Math.PI;
								if (cp.isExtruded()) {
									if (da <= Math.PI / 2)
										rl = 2.0;
									else
										rl = 1.5;
								} else {
									rl = 1.0;
								}
								bases.get(cpnext.getStart()).setX(
										bases.get(cp.getEnd()).getX() + rl
												* lnx);
								bases.get(cpnext.getStart()).setY(
										bases.get(cp.getEnd()).getY() + rl
												* lny);
								bases.get(cpnext.getEnd()).setX(
										bases.get(cpnext.getStart()).getX()
												- cpnexty);
								bases.get(cpnext.getEnd()).setY(
										bases.get(cpnext.getStart()).getY()
												+ cpnextx);
							}
						}
					}
					if (direction < 0) {
						if (icdown == icend) {
							icdown = -1;
						} else if (icdown >= 0) {
							if (++icdown >= lp.getNconnection()) {
								icdown = 0;
							}
						}
						direction = 1;
					} else {
						if (icup == icstart)
							icup = -1;
						else if (icup >= 0) {
							if (--icup < 0) {
								icup = lp.getNconnection() - 1;
							}
						}
						direction = -1;
					}
					done = icup == -1 && icdown == -1;
				}
				icnext = icend + 1;
				if (icnext >= lp.getNconnection())
					icnext = 0;
				if (icend != icstart
						&& (!(icstart == icstart1 && icnext == icstart1))) {

					// Move the bases just constructed (or the radius) so that
					// the bisector of the end points is radius distance away
					// from the loop center.

					cp = lp.getConnection(icstart);
					cpnext = lp.getConnection(icend);
					dx = bases.get(cpnext.getEnd()).getX()
							- bases.get(cp.getStart()).getX();
					dy = bases.get(cpnext.getEnd()).getY()
							- bases.get(cp.getStart()).getY();
					midx = bases.get(cp.getStart()).getX() + dx / 2.0;
					midy = bases.get(cp.getStart()).getY() + dy / 2.0;
					rr = Math.sqrt(dx * dx + dy * dy);
					mx = dx / rr;
					my = dy / rr;
					vx = xc - midx;
					vy = yc - midy;
					rr = Math.sqrt(dx * dx + dy * dy);
					vx /= rr;
					vy /= rr;
					dotmv = vx * mx + vy * my;
					nrx = dotmv * mx - vx;
					nry = dotmv * my - vy;
					rr = Math.sqrt(nrx * nrx + nry * nry);
					nrx /= rr;
					nry /= rr;

					// Determine which side of the bisector the center should
					// be.

					dx = bases.get(cp.getStart()).getX() - xc;
					dy = bases.get(cp.getStart()).getY() - yc;
					ac = Math.atan2(dy, dx);
					if (ac < 0.0)
						ac += 2 * Math.PI;
					dx = bases.get(cpnext.getEnd()).getX() - xc;
					dy = bases.get(cpnext.getEnd()).getY() - yc;
					acn = Math.atan2(dy, dx);
					if (acn < 0.0)
						acn += 2 * Math.PI;
					if (acn < ac)
						acn += 2 * Math.PI;
					if (acn - ac > Math.PI)
						sign = -1;
					else
						sign = 1;
					nmidx = xc + sign * radius * nrx;
					nmidy = yc + sign * radius * nry;
					if (rooted) {
						xc -= nmidx - midx;
						yc -= nmidy - midy;
					} else {
						for (ic = icstart;;) {
							cp = lp.getConnection(ic);
							i = cp.getStart();
							bases.get(i).setX(
									bases.get(i).getX() + nmidx - midx);
							bases.get(i).setY(
									bases.get(i).getY() + nmidy - midy);
							i = cp.getEnd();
							bases.get(i).setX(
									bases.get(i).getX() + nmidx - midx);
							bases.get(i).setY(
									bases.get(i).getY() + nmidy - midy);
							if (ic == icend)
								break;
							if (++ic >= lp.getNconnection())
								ic = 0;
						}
					}
				}
				icstart = icnext;
				done_all_connections = icstart == icstart1;
			}
			for (ic = 0; ic < lp.getNconnection(); ic++) {
				cp = lp.getConnection(ic);
				j = ic + 1;
				if (j >= lp.getNconnection())
					j = 0;
				cpnext = lp.getConnection(j);
				dx = bases.get(cp.getEnd()).getX() - xc;
				dy = bases.get(cp.getEnd()).getY() - yc;
				rc = Math.sqrt(dx * dx + dy * dy);
				ac = Math.atan2(dy, dx);
				if (ac < 0.0)
					ac += 2 * Math.PI;
				dx = bases.get(cpnext.getStart()).getX() - xc;
				dy = bases.get(cpnext.getStart()).getY() - yc;
				rcn = Math.sqrt(dx * dx + dy * dy);
				acn = Math.atan2(dy, dx);
				if (acn < 0.0)
					acn += 2 * Math.PI;
				if (acn < ac)
					acn += 2 * Math.PI;
				dan = acn - ac;
				dcp = cpnext.getAngle() - cp.getAngle();
				if (dcp <= 0.0)
					dcp += 2 * Math.PI;
				if (Math.abs(dan - dcp) > Math.PI) {
					if (cp.isExtruded()) {
						warningEmition("Warning from traverse_loop. Loop "
								+ lp.getNumber() + " has crossed regions\n");
					} else if ((cpnext.getStart() - cp.getEnd()) != 1) {
						cp.setExtruded(true);
						continue set_radius; // remplacement du goto
					}
				}
				if (cp.isExtruded()) {
					construct_extruded_segment(cp, cpnext);
				} else {
					n = cpnext.getStart() - cp.getEnd();
					if (n < 0)
						n += nbase + 1;
					angleinc = dan / n;
					for (j = 1; j < n; j++) {
						i = cp.getEnd() + j;
						if (i > nbase)
							i -= nbase + 1;
						a = ac + j * angleinc;
						rr = rc + (rcn - rc) * (a - ac) / dan;
						bases.get(i).setX(xc + rr * Math.cos(a));
						bases.get(i).setY(yc + rr * Math.sin(a));
					}
				}
			}
			break;
		}
		for (ic = 0; ic < lp.getNconnection(); ic++) {
			if (icroot != ic) {
				cp = lp.getConnection(ic);
				generate_region(cp);
				traverse_loop(cp.getLoop(), cp);
			}
		}
		n = 0;
		sx = 0.0;
		sy = 0.0;
		for (ic = 0; ic < lp.getNconnection(); ic++) {
			j = ic + 1;
			if (j >= lp.getNconnection())
				j = 0;
			cp = lp.getConnection(ic);
			cpnext = lp.getConnection(j);
			n += 2;
			sx += bases.get(cp.getStart()).getX()
					+ bases.get(cp.getEnd()).getX();
			sy += bases.get(cp.getStart()).getY()
					+ bases.get(cp.getEnd()).getY();
			if (!cp.isExtruded()) {
				for (j = cp.getEnd() + 1; j != cpnext.getStart(); j++) {
					if (j > nbase)
						j -= nbase + 1;
					n++;
					sx += bases.get(j).getX();
					sy += bases.get(j).getY();
				}
			}
		}
		lp.setX(sx / n);
		lp.setY(sy / n);
	}

	/**
	 * For the loop pointed to by lp, determine the radius of the loop that will
	 * ensure that each base around the loop will have a separation of at least
	 * lencut around the circle. If a segment joining two connectors will not
	 * support this separation, then the flag, extruded, will be set in the
	 * first of these two indicators. The radius is set in lp.
	 * 
	 * The radius is selected by a least squares procedure where the sum of the
	 * squares of the deviations of length from the ideal value of 1 is used as
	 * the error function.
	 */
	private void determine_radius(Loop lp, double lencut) {
		if (debug)
			System.out.println("  Determine_radius");
		double mindit, ci, dt, sumn, sumd, radius, dit;
		int i, j, end, start, imindit = 0;
		Connection cp = new Connection(), cpnext = new Connection();
		double rt2_2 = 0.7071068;

		do {
			mindit = 1.0e10;
			for (sumd = 0.0, sumn = 0.0, i = 0; i < lp.getNconnection(); i++) {
				cp = lp.getConnection(i);
				j = i + 1;
				if (j >= lp.getNconnection())
					j = 0;
				cpnext = lp.getConnection(j);
				end = cp.getEnd();
				start = cpnext.getStart();
				if (start < end)
					start += nbase + 1;
				dt = cpnext.getAngle() - cp.getAngle();
				if (dt <= 0.0)
					dt += 2 * Math.PI;
				if (!cp.isExtruded())
					ci = start - end;
				else {
					if (dt <= Math.PI / 2)
						ci = 2.0;
					else
						ci = 1.5;
				}
				sumn += dt * (1.0 / ci + 1.0);
				sumd += dt * dt / ci;
				dit = dt / ci;
				if (dit < mindit && !cp.isExtruded() && ci > 1.0) {
					mindit = dit;
					imindit = i;
				}
			}
			radius = sumn / sumd;
			if (radius < rt2_2)
				radius = rt2_2;
			if (mindit * radius < lencut) {
				lp.getConnection(imindit).setExtruded(true);
			}
		} while (mindit * radius < lencut);
		if (lp.getRadius() > 0.0)
			radius = lp.getRadius();
		else
			lp.setRadius(radius);
	}

	/**
	 * Determines if the connections cp and cpnext are connected
	 */
	private boolean connected_connection(Connection cp, Connection cpnext) {
		if (debug)
			System.out.println("  Connected_connection");
		if (cp.isExtruded()) {
			return true;
		} else if (cp.getEnd() + 1 == cpnext.getStart()) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Finds the middle of a set of connected connectors. This is normally the
	 * middle connection in the sequence except if one of the connections is the
	 * anchor, in which case that connection will be used.
	 * 
	 * @throws ExceptionNAViewAlgorithm
	 */
	private int find_ic_middle(int icstart, int icend,
			Connection anchor_connection, Connection acp, Loop lp)
			throws ExceptionNAViewAlgorithm {
		if (debug)
			System.out.println("  Find_ic_middle");
		int count, ret, ic, i;
		boolean done;

		count = 0;
		ret = -1;
		ic = icstart;
		done = false;
		while (!done) {
			if (count++ > lp.getNconnection() * 2) {
				throw new ExceptionNAViewAlgorithm(
						"Infinite loop detected in find_ic_middle");
			}
			if (anchor_connection != null && lp.getConnection(ic) == acp) {
				ret = ic;
			}
			done = ic == icend;
			if (++ic >= lp.getNconnection()) {
				ic = 0;
			}
		}
		if (ret == -1) {
			for (i = 1, ic = icstart; i < (count + 1) / 2; i++) {
				if (++ic >= lp.getNconnection())
					ic = 0;
			}
			ret = ic;
		}
		return ret;
	}

	/**
	 * Generates the coordinates for the base pairing region of a connection
	 * given the position of the starting base pair.
	 * 
	 * @throws ExceptionNAViewAlgorithm
	 */
	private void generate_region(Connection cp) throws ExceptionNAViewAlgorithm {
		if (debug)
			System.out.println("  Generate_region");
		int l, start, end, i, mate;
		Region rp;

		rp = cp.getRegion();
		l = 0;
		if (cp.getStart() == rp.getStart1()) {
			start = rp.getStart1();
			end = rp.getEnd1();
		} else {
			start = rp.getStart2();
			end = rp.getEnd2();
		}
		if (bases.get(cp.getStart()).getX() > ANUM - 100.0
				|| bases.get(cp.getEnd()).getX() > ANUM - 100.0) {
			throw new ExceptionNAViewAlgorithm(
					"Bad region passed to generate_region. Coordinates not defined.");
		}
		for (i = start + 1; i <= end; i++) {
			l++;
			bases.get(i).setX(
					bases.get(cp.getStart()).getX() + HELIX_FACTOR * l
							* cp.getXrad());
			bases.get(i).setY(
					bases.get(cp.getStart()).getY() + HELIX_FACTOR * l
							* cp.getYrad());
			mate = bases.get(i).getMate();
			bases.get(mate).setX(
					bases.get(cp.getEnd()).getX() + HELIX_FACTOR * l
							* cp.getXrad());
			bases.get(mate).setY(
					bases.get(cp.getEnd()).getY() + HELIX_FACTOR * l
							* cp.getYrad());
			
		}
	}

	/**
	 * Draws the segment of residue between the bases numbered start through
	 * end, where start and end are presumed to be part of a base pairing
	 * region. They are drawn as a circle which has a chord given by the ends of
	 * two base pairing regions defined by the connections.
	 * 
	 * @throws ExceptionNAViewAlgorithm
	 */
	private void construct_circle_segment(int start, int end)
			throws ExceptionNAViewAlgorithm {
		if (debug)
			System.out.println("  Construct_circle_segment");
		double dx, dy, rr, midx, midy, xn, yn, nrx, nry, mx, my, a;
		int l, j, i;

		dx = bases.get(end).getX() - bases.get(start).getX();
		dy = bases.get(end).getY() - bases.get(start).getY();
		rr = Math.sqrt(dx * dx + dy * dy);
		l = end - start;
		if (l < 0)
			l += nbase + 1;
		if (rr >= l) {
			dx /= rr;
			dy /= rr;
			for (j = 1; j < l; j++) {
				i = start + j;
				if (i > nbase)
					i -= nbase + 1;
				bases.get(i).setX(
						bases.get(start).getX() + dx * (double) j / (double) l);
				bases.get(i).setY(
						bases.get(start).getY() + dy * (double) j / (double) l);
			}
		} else {
			find_center_for_arc((l - 1), rr);
			dx /= rr;
			dy /= rr;
			midx = bases.get(start).getX() + dx * rr / 2.0;
			midy = bases.get(start).getY() + dy * rr / 2.0;
			xn = dy;
			yn = -dx;
			nrx = midx + _h * xn;
			nry = midy + _h * yn;
			mx = bases.get(start).getX() - nrx;
			my = bases.get(start).getY() - nry;
			rr = Math.sqrt(mx * mx + my * my);
			a = Math.atan2(my, mx);
			for (j = 1; j < l; j++) {
				i = start + j;
				if (i > nbase)
					i -= nbase + 1;
				bases.get(i).setX(nrx + rr * Math.cos(a + j * angleinc));
				bases.get(i).setY(nry + rr * Math.sin(a + j * angleinc));
			}
		}
	}

	/**
	 * Constructs the segment between cp and cpnext as a circle if possible.
	 * However, if the segment is too large, the lines are drawn between the two
	 * connecting regions, and bases are placed there until the connecting
	 * circle will fit.
	 * 
	 * @throws ExceptionNAViewAlgorithm
	 */
	private void construct_extruded_segment(Connection cp, Connection cpnext)
			throws ExceptionNAViewAlgorithm {
		if (debug)
			System.out.println("  Construct_extruded_segment");
		double astart, aend1, aend2, aave, dx, dy, a1, a2, ac, rr, da, dac;
		int start, end, n, nstart, nend;
		boolean collision;

		astart = cp.getAngle();
		aend2 = aend1 = cpnext.getAngle();
		if (aend2 < astart)
			aend2 += 2 * Math.PI;
		aave = (astart + aend2) / 2.0;
		start = cp.getEnd();
		end = cpnext.getStart();
		n = end - start;
		if (n < 0)
			n += nbase + 1;
		da = cpnext.getAngle() - cp.getAngle();
		if (da < 0.0) {
			da += 2 * Math.PI;
		}
		if (n == 2)
			construct_circle_segment(start, end);
		else {
			dx = bases.get(end).getX() - bases.get(start).getX();
			dy = bases.get(end).getY() - bases.get(start).getY();
			rr = Math.sqrt(dx * dx + dy * dy);
			dx /= rr;
			dy /= rr;
			if (rr >= 1.5 && da <= Math.PI / 2) {
				nstart = start + 1;
				if (nstart > nbase)
					nstart -= nbase + 1;
				nend = end - 1;
				if (nend < 0)
					nend += nbase + 1;
				bases.get(nstart).setX(bases.get(start).getX() + 0.5 * dx);
				bases.get(nstart).setY(bases.get(start).getY() + 0.5 * dy);
				bases.get(nend).setX(bases.get(end).getX() - 0.5 * dx);
				bases.get(nend).setY(bases.get(end).getY() - 0.5 * dy);
				start = nstart;
				end = nend;
			}
			do {
				collision = false;
				construct_circle_segment(start, end);
				nstart = start + 1;
				if (nstart > nbase)
					nstart -= nbase + 1;
				dx = bases.get(nstart).getX() - bases.get(start).getX();
				dy = bases.get(nstart).getY() - bases.get(start).getY();
				a1 = Math.atan2(dy, dx);
				if (a1 < 0.0)
					a1 += 2 * Math.PI;
				dac = a1 - astart;
				if (dac < 0.0)
					dac += 2 * Math.PI;
				if (dac > Math.PI)
					collision = true;
				nend = end - 1;
				if (nend < 0)
					nend += nbase + 1;
				dx = bases.get(nend).getX() - bases.get(end).getX();
				dy = bases.get(nend).getY() - bases.get(end).getY();
				a2 = Math.atan2(dy, dx);
				if (a2 < 0.0)
					a2 += 2 * Math.PI;
				dac = aend1 - a2;
				if (dac < 0.0)
					dac += 2 * Math.PI;
				if (dac > Math.PI)
					collision = true;
				if (collision) {
					ac = minf2(aave, astart + 0.5);
					bases.get(nstart).setX(
							bases.get(start).getX() + Math.cos(ac));
					bases.get(nstart).setY(
							bases.get(start).getY() + Math.sin(ac));
					start = nstart;
					ac = maxf2(aave, aend2 - 0.5);
					bases.get(nend).setX(bases.get(end).getX() + Math.cos(ac));
					bases.get(nend).setY(bases.get(end).getY() + Math.sin(ac));
					end = nend;
					n -= 2;
				}
			} while (collision && n > 1);
		}
	}

	/**
	 * Given n points to be placed equidistantly and equiangularly on a polygon
	 * which has a chord of length, b, find the distance, h, from the midpoint
	 * of the chord for the center of polygon. Positive values mean the center
	 * is within the polygon and the chord, whereas negative values mean the
	 * center is outside the chord. Also, the radial angle for each polygon side
	 * is returned in theta.
	 * 
	 * The procedure uses a bisection algorithm to find the correct value for
	 * the center. Two equations are solved, the angles around the center must
	 * add to 2*Math.PI, and the sides of the polygon excluding the chord must
	 * have a length of 1.
	 * 
	 * @throws ExceptionNAViewAlgorithm
	 */
	private void find_center_for_arc(double n, double b)
			throws ExceptionNAViewAlgorithm {
		if (debug)
			System.out.println("  Find_center_for_arc");
		double h, hhi, hlow, r, disc, theta, e, phi;
		int iter;

		hhi = (n + 1.0) / Math.PI;
		// changed to prevent div by zero if (ih)
		hlow = -hhi - b / (n + 1.000001 - b);
		if (b < 1)
			// otherwise we might fail below (ih)
			hlow = 0;
		iter = 0;
		do {
			h = (hhi + hlow) / 2.0;
			r = Math.sqrt(h * h + b * b / 4.0);
			// if (r<0.5) {r = 0.5; h = 0.5*Math.sqrt(1-b*b);}
			disc = 1.0 - 0.5 / (r * r);
			if (Math.abs(disc) > 1.0) {
				throw new ExceptionNAViewAlgorithm(
						"Unexpected large magnitude discriminant = " + disc
								+ " " + r);
			}
			theta = Math.acos(disc);
			// theta = 2*Math.acos(Math.sqrt(1-1/(4*r*r)));
			phi = Math.acos(h / r);
			e = theta * (n + 1) + 2 * phi - 2 * Math.PI;
			if (e > 0.0) {
				hlow = h;
			} else {
				hhi = h;
			}
		} while (Math.abs(e) > 0.0001 && ++iter < MAXITER);
		if (iter >= MAXITER) {
			if (noIterationFailureYet) {
				warningEmition("Iteration failed in find_center_for_arc");
				noIterationFailureYet = false;
			}
			h = 0.0;
			theta = 0.0;
		}
		_h = h;
		angleinc = theta;
	}

	private double minf2(double x1, double x2) {
		return ((x1) < (x2)) ? (x1) : (x2);
	}

	private double maxf2(double x1, double x2) {
		return ((x1) > (x2)) ? (x1) : (x2);
	}

	public void warningEmition(String warningMessage) throws ExceptionNAViewAlgorithm {
		throw (new ExceptionNAViewAlgorithm(warningMessage));
	}
}
