// Copyright (C) 2011-2012 CRS4.
//
// This file is part of Seal.
//
// Seal 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.
//
// Seal 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 Seal.  If not, see <http://www.gnu.org/licenses/>.

package it.crs4.seal.common;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.*;

public class AlignOp {

	public static enum Type {
		Match,
		Insert,
		Delete,
		SoftClip,
		HardClip,
		Skip,
		Pad;

		public char getSymbol()
		{
			switch (this) {
			case Match:
				return 'M';
			case Insert:
				return 'I';
			case Delete:
				return 'D';
			case SoftClip:
				return 'S';
			case HardClip:
				return 'H';
			case Skip:
				return 'N';
			case Pad:
				return 'P';
			default:
				throw new RuntimeException("BUG!  Missing cigar operator in AlignOp::Type::getSymbol.");
			}
		}

		public static Type fromSymbol(String sym) {
			if (sym.length() != 1)
				throw new IllegalArgumentException("Unrecognized alignment operation symbol " + sym);
			else
				return fromSymbol(sym.charAt(0));
		}

		public static Type fromSymbol(char sym) {
			switch (sym) {
			case 'M':
				return Type.Match;
			case 'I':
				return Type.Insert;
			case 'D':
				return Type.Delete;
			case 'S':
				return Type.SoftClip;
			case 'H':
				return Type.HardClip;
			case 'N':
				return Type.Skip;
			case 'P':
				return Type.Pad;
			default:
				throw new IllegalArgumentException("Unrecognized alignment operation symbol " + sym);
			}
		}
	}

	protected static final Pattern CigarElementPattern = Pattern.compile("(\\d+)([MIDNSHP])");

	private Type op;
	private int len;

	public AlignOp(Type op, int len) {
		this.op = op;
		this.len = len;
	}

	public AlignOp.Type getType() { return op; }
	public int getLen() { return len; }

	public boolean equals(Object other)
	{
		if (other instanceof AlignOp)
		{
			AlignOp otherAlignment = (AlignOp) other;
			return otherAlignment.op == this.op && otherAlignment.len == this.len;
		}
		else
			return false;
	}

	public String toString() { return "(" + op + "," + len + ")"; }

	/**
	 * Compute the SAM-style CIGAR string for the given alignment.
	 */
	public static String cigarStr(List<AlignOp> alignment)
	{
		if (alignment == null || alignment.isEmpty())
			return "*";

		StringBuilder builder = new StringBuilder(50);

		for (AlignOp op: alignment)
			builder.append(op.getLen()).append(op.getType().getSymbol());

		return builder.toString();
	}

	public static List<AlignOp> scanCigar(String cigar)
	{
		if ("*".equals(cigar))
			return new ArrayList<AlignOp>(0);
		else
		{
			ArrayList<AlignOp> result = new ArrayList<AlignOp>(5);
			Matcher m = CigarElementPattern.matcher(cigar);

			int lastPositionMatched = 0;
			while (m.find())
			{
				result.add( new AlignOp(AlignOp.Type.fromSymbol(m.group(2)), Integer.parseInt(m.group(1))) );
				lastPositionMatched = m.end();
			}

			if (lastPositionMatched < cigar.length())
				throw new FormatException("Invalid CIGAR pattern " + cigar);

			if (result.isEmpty())
				throw new FormatException("Unable to parse any alignments from CIGAR pattern " + cigar);

			return result;
		}
	}
}
