/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */
package org.biojava.nbio.structure;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A chain, a start residue, and an end residue.
 *
 * Also stores a length. Because of insertion codes, this length is not necessarily {@code end − start}.
 */
public class ResidueRangeAndLength extends ResidueRange {
	private static final Logger logger = LoggerFactory.getLogger(ResidueRangeAndLength.class);

	private final int length;

	public ResidueRangeAndLength(String chain, ResidueNumber start, ResidueNumber end, int length) {
		super(chain, start, end);
		this.length = length;
	}

	public ResidueRangeAndLength(String chain, String start, String end, int length) {
		super(chain, start, end);
		this.length = length;
	}

	/**
	 * Returns a new Iterator over every {@link ResidueNumber} in this ResidueRange.
	 * Stores the contents of {@code map} until the iterator is finished, so calling code should set the iterator to {@code null} if it did not finish.
	 */
	@Override
	public Iterator<ResidueNumber> iterator(AtomPositionMap map) {
		return super.iterator(map); // just a bit faster
	}

	/**
	 * Calculates the combined number of residues of the ResidueRanges in {@code rrs}.
	 *
	 * Assumes no overlap. If two or more ranges cover the same residues, will over-count the union of the residues.
	 *
	 * @param rrs
	 *            A list of ResidueRanges
	 * @return The combined length
	 * @throws IllegalArgumentException
	 *             If the {@link #getLength() length} of one or more ResidueRange is null
	 * @see #getLength()
	 */
	public static int calcLength(List<ResidueRangeAndLength> rrs) {
		int l = 0;
		for (ResidueRangeAndLength rr : rrs) {
			l += rr.getLength();
		}
		return l;
	}

	/**
	 * Parses a residue range.
	 * 
	 * The AtomPositionMap is used to calculate the length and fill in missing
	 * information, such as for whole chains ('A:'). Supports the special chain
	 * name '_' for single-chain structures.
	 * 
	 * If residues are specified outside of the range given in the map,
	 * attempts to decrease the input range to valid values. In extreme cases
	 * where this process fails fails to find any valid indices, returns null.
	 * 
	 * For a function which more conservatively represents the input range,
	 * without chain inference and error fixes, use {@link ResidueRange#parse(String)}.
	 * @param s
	 *            A string of the form chain_start-end. For example: <code>A.5-100</code>.
	 * @return The unique ResidueRange corresponding to {@code s}.
	 */
	public static ResidueRangeAndLength parse(String s, AtomPositionMap map) {
		ResidueRange rr = parse(s);
		ResidueNumber start = rr.getStart();
		
		String chain = rr.getChainId();
		
		// handle special "_" chain
		if(chain == null || chain.equals("_")) {
			ResidueNumber first = map.getNavMap().firstKey();
			chain = first.getChainId();
			// Quick check for additional chains. Not guaranteed if the atoms are out of order.
			if( ! map.getNavMap().lastKey().getChainId().equals(chain) ) {
				logger.warn("Multiple possible chains match '_'. Using chain {}",chain);
			}
		}

		// get a non-null start and end
		// if it's the whole chain, choose the first and last residue numbers in the chain
		if (start==null) {
			start = map.getFirst(chain);
		}
		ResidueNumber end = rr.getEnd();
		if (end==null) { // should happen iff start==null
			end = map.getLast(chain);
		}

		// Replace '_'
		start.setChainId(chain);
		end.setChainId(chain);
		
		// Now fix any errors and calculate the length
		return map.trimToValidResidues(new ResidueRange(chain, start, end));
	}


	public static List<ResidueRangeAndLength> parseMultiple(List<String> ranges, AtomPositionMap map) {
		List<ResidueRangeAndLength> rrs = new ArrayList<ResidueRangeAndLength>(ranges.size());
		for (String range : ranges) {
			ResidueRangeAndLength rr = ResidueRangeAndLength.parse(range, map);
			if (rr != null) rrs.add(rr);
		}
		return rrs;
	}

	/**
	 * @param s
	 *            A string of the form chain_start-end,chain_start-end, ... For example:
	 *            <code>A.5-100,R_110-190,Z_200-250</code>.
	 * @return The unique ResidueRange corresponding to {@code s}.
	 */
	public static List<ResidueRangeAndLength> parseMultiple(String s, AtomPositionMap map) {
		String[] parts = s.split(",");
		List<ResidueRangeAndLength> list = new ArrayList<ResidueRangeAndLength>(parts.length);
		for (String part : parts) {
			list.add(parse(part, map));
		}
		return list;
	}

	/**
	 * @return The number of residues in this ResidueRange
	 */
	public int getLength() {
		return length;
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (o == null || getClass() != o.getClass()) {
			return false;
		}
		if (!super.equals(o)) {
			return false;
		}
		ResidueRangeAndLength that = (ResidueRangeAndLength) o;
		return length == that.length;
	}

	@Override
	public int hashCode() {
		int result = super.hashCode();
		result = 31 * result + length;
		return result;
	}
}
