public class SymbolicPeriodRelation extends PeriodRelation
{	
	public static final short AL = 1;
	public static final short AN = 2;
	public static final short AG = 4;
	public static final short BG = 8;
	public static final short BN = 16;
	public static final short BE = 32;
	public static final short CL = 64;
	public static final short CN = 128;
	public static final short CG = 256;
	public static final short N = 512; //extension to allow representation
								//of the closure of symbolic period
								//relations under transition
	public static final short ANY = 511;
	
	private static int stringReadPos = 0; 
		//used by readEndPhase and readMidPhase
	
	public SymbolicPeriodRelation()
	{
	}
	
	public SymbolicPeriodRelation(String s) throws FormatException
	{
		set(s);
	}
	
	public SymbolicPeriodRelation(int d) throws FormatException
	{
		set(d);
	}
	
    public String toString()
    {
    	final String[] endPhases = {"<<", "", "-<", ">>", "<>", ">-", "*"};
    	final String[] midPhases = {"^", "", "^-", "=", "^=", "-=", "*"};
    	
    	int aPhase = data & (AL | AN | AG);
    	int bPhase = data & (BG | BN | BE);
    	int cPhase = data & (CL | CN | CG);
    	
    	if (aPhase == 0 || bPhase == 0 || cPhase == 0)
    		return "null";
    	else if ((data & N) != 0)
    	{
    		try
    		{
    			return ("~" + 
    				(new SymbolicPeriodRelation(data - N)).toString());
    		}
    		catch (FormatException e)
    		{
    			return "invalid";
    		}
    	}
    	else if (bPhase == (BG | BN | BE))
    	{
    		if (aPhase == (AL | AN | AG))
    		{
    			if (cPhase == (CL | CN | CG))
    				return "*";
    			else 
    				return ("*" + endPhases[(cPhase>>>6)-1]);
    		}
    		else if (cPhase == (CL | CN | CG)) 
    			return (endPhases[aPhase-1] + "*");
    		else 
    			return (endPhases[aPhase-1] + "*" + endPhases[(cPhase>>>6)-1]);
    	}
    	else
    		return (endPhases[aPhase-1] + midPhases[(bPhase>>>3)-1] 
    			+ endPhases[(cPhase>>>6)-1]);
    }

    public void set(String s) throws FormatException
    {
    	int aPhase, bPhase, cPhase;
    	
    	if (s.equals("null"))
    		data = 0;
    	else if (s.equals("~<<^->>"))
    		data = (short)(N | AL | BG | BN | CG);
    	else if (s.equals("~>>^-<<"))
    		data = (short)(N | AG | BG | BN | CL);
    	else
    	{
    		stringReadPos = 0;
	    	aPhase = readEndPhase(s);
    		bPhase = readMidPhase(s);
    		cPhase = (readEndPhase(s)<<6);
    			//deal with multiple implied asterisk phases
    		if (bPhase == BN && aPhase == (AL | AN | AG))
    			bPhase = (BG | BN | BE);
    		if (cPhase == CN && bPhase == (BG | BN | BE))
    			cPhase = (CL | CN | CG);
    		if (stringReadPos != s.length())
            	throw (new FormatException
                	("Bad symbolic period relation string: " + s));
			if (valid(aPhase, bPhase, cPhase))            
	            data = (short)(aPhase | bPhase | cPhase);
	        else
            	throw (new FormatException
                	("Bad symbolic period relation string: " + s));
    	}
    }

	public void set(int d) throws FormatException
	{
		if ((d & N) != 0)
		{
			if ( (d != (N | AL | BG | BN | CG)) &&
					(d != (N | AG | BG | BN | CL)) )
				throw (new FormatException
                	("Bad symbolic period relation number: " + d));
		}
		else if (!valid((d & (AL | AN | AG)), (d & (BG | BN | BE)), 
				(d & (CL | CN | CG))))
			throw (new FormatException
                ("Bad symbolic period relation number: " + d));
		data = (short)d;
	}

	public void setRandom()
	{
		AllenPeriodRelation a = new AllenPeriodRelation();
		SymbolicPeriodRelation s;
		
		while (true)
		{
			try
			{
				a.setRandom();
				s = a.toSymbolicPeriodRelation();
				break;
			}
			catch (TranslationException e)
			{
			}
		}
		data = s.data;
	}

	public void setRandomDeterminate()
	{
		AllenPeriodRelation a = new AllenPeriodRelation();
		SymbolicPeriodRelation s;
		
		a.setRandomDeterminate();
		try
		{
			s = a.toSymbolicPeriodRelation();
		}
		catch (TranslationException e)
		{
    		throw (new RuntimeException("Unexpected Translation Exception"));
		}
		data = s.data;
	}

	public boolean isDeterminate()
	{
		return (toAllenPeriodRelation().isDeterminate());
	}

	public boolean isAny()
	{
		return (data == ANY);
	}
	
    public Relation intersection(Relation r)
    {
        // This method is derived from class relation
        SymbolicPeriodRelation s;
        
        if (!(r instanceof PeriodRelation))
        	return new SymbolicPeriodRelation();
        try
        {
        	s = ((PeriodRelation)r).toSymbolicPeriodRelation();
        	if ((data & N) != 0 || (s.data & N) != 0)
        		throw new TranslationException("");
        	try
        	{
        		return new SymbolicPeriodRelation(fixMidPhase(dataIntersection(s)));
        	}
        	catch (FormatException e)
        	{
        		return new SymbolicPeriodRelation();
        	}
        }
        catch (TranslationException e)
        {
        	AllenPeriodRelation a = 
        		(AllenPeriodRelation)toAllenPeriodRelation().intersection(r);      	
        	try
        	{
	    		return a.toSymbolicPeriodRelation();
        	}
        	catch (TranslationException e2)
        	{
        		return a;
        	}
        }
    }
    
    public boolean includes(int d)
    {
    	//overrides includes(int d) in Relation 
    	//to deal with special cases
    	if ((data & N) != 0)
    		//this is special case
    		if ((d & N) != 0)
    			//d is a special case also;
    			//return true if equal
    			return (data == d);
    		else
    			//return true if excluded cases
    			//are not included in d
    			return !((d | (data - N)) == d);
    	else if ((d & N) != 0)
    		//d is special case but this is not;
    		//return true is this include any relation
    		return (data == ANY);
    	else
    		//neither is special case; use super
    		return super.includes(d);
    }

    public Relation inverse()
    {
        // This method is derived from class relation
        int d = data & (N | AN | BG | BN | BE | CN);
        if ((data & AL) != 0)
        	d |= AG;
        if ((data & AG) != 0)
        	d |= AL;
        if ((data & CL) != 0)
        	d |= CG;
        if ((data & CG) != 0)
        	d |= CL;
        try
        {
        	return new SymbolicPeriodRelation(d);
        }
        catch (FormatException e)
        {
        	return new SymbolicPeriodRelation();
        }
    }

    public Relation transition(Relation r)
    {
        // This method is derived from class relation
        AllenPeriodRelation a;
        
        if (!(r instanceof PeriodRelation))
        	return new SymbolicPeriodRelation();	
        a = (AllenPeriodRelation)
        	(toAllenPeriodRelation().transition(
        		((PeriodRelation)r).toAllenPeriodRelation()));      	
        try
        {
        	return a.toSymbolicPeriodRelation();
        }
        catch (TranslationException e)
        {
        	return a;
        }
    }

    public Relation union(Relation r)
    {
        // This method is derived from class relation
        AllenPeriodRelation a;
        
        a = (AllenPeriodRelation)(toAllenPeriodRelation().union(r));
        try
        {
        	return a.toSymbolicPeriodRelation();;
        }
        catch (TranslationException e)
        {
        	return a;
        }
    }

    public AllenPeriodRelation toAllenPeriodRelation()
    {
		//Algorithm 4
		int a = 0, b = 0, c = 0; //2
		try
		{
    		if (data == (N | AL | BG | BN | CG)) //special case not in book
	    		return new AllenPeriodRelation(
    				AllenPeriodRelation.GMIOI 
    				| AllenPeriodRelation.EOSDFOISIDIFI);
    		else if (data == (N | AG | BG | BN | CL)) 
    				//special case not in book
	    		return new AllenPeriodRelation(
    				AllenPeriodRelation.LMO 
    				| AllenPeriodRelation.EOSDFOISIDIFI);
    		else
    		{
				if (includes(AL)) //3
					a = AllenPeriodRelation.LMODIFI;
				if (includes(AN)) //4
					a |= AllenPeriodRelation.ESSI;
				if (includes(AG)) //5
					a |= AllenPeriodRelation.DFGMIOI;
				if (includes(BG)) //6
					b = AllenPeriodRelation.LG;
				if (includes(BN)) //7
					b |= AllenPeriodRelation.MMI;
				if (includes(BE)) //8
					b |= AllenPeriodRelation.EOSDFOISIDIFI;
				if (includes(CL)) //9
					c = AllenPeriodRelation.GMIOISIDI;
				if (includes(CN)) //10
					c |= AllenPeriodRelation.EFFI;
				if (includes(CG)) //11
					c |= AllenPeriodRelation.LMOSD;
				return new AllenPeriodRelation(a & b & c);
			}
		}
		catch (FormatException e)
		{
			return new AllenPeriodRelation();
		}
    }
    
    public SymbolicPeriodRelation toSymbolicPeriodRelation()
    		throws TranslationException
    {
    	return this;
    }
    
    public PointPeriodRelation toPointPeriodRelation()
    		throws TranslationException
    {
    	//to do
    	return toAllenPeriodRelation().toPointPeriodRelation();
    }
    
	public PointPeriodRelation[] toPointPeriodRelations()
	{
		return toAllenPeriodRelation().toPointPeriodRelations();
	}

    // private methods used by set(String)
	private int readEndPhase(String s) throws FormatException
	{
		try
		{
			if (stringReadPos == s.length())
				return AN;
			char c = s.charAt(stringReadPos);
			if (c == '<')
			{
				c = s.charAt(++stringReadPos);
				if (c == '<')
				{
					stringReadPos++;
					return AL;
				}
				else if (c == '>')
				{
					stringReadPos++;
					return (AL | AG);
				}
				else
					throw (new FormatException
                		("Bad symbolic period relation string: " + s));
			}
			else if (c == '-')
			{
				if (s.charAt(++stringReadPos) == '<')
				{
					stringReadPos++;
					return (AL | AN);
				}
				else
					throw (new FormatException
                		("Bad symbolic period relation string: " + s));
			}
			else if (c == '>')
			{
				c = s.charAt(++stringReadPos);
				if (c == '>')
				{
					stringReadPos++;
					return AG;
				}
				else if (c == '-')
				{
					stringReadPos++;
					return (AN | AG);
				}
				else
					throw (new FormatException
                		("Bad symbolic period relation string: " + s));
			}
			else if (c == '*')
			{
				stringReadPos++;
				return (AL | AN | AG);
			}
			else
				return AN;
		}
		catch (StringIndexOutOfBoundsException e)
		{
			throw (new FormatException
                		("Bad symbolic period relation string: " + s));
        }
	}
	
	private int readMidPhase(String s) throws FormatException
	{
		try
		{
			if (stringReadPos == s.length())
				return BN;
			char c = s.charAt(stringReadPos);
			if (c == '^')
			{
				c = s.charAt(++stringReadPos);
				if (c == '-')
				{
					stringReadPos++;
					return (BG | BN);
				}
				else if (c == '=')
				{
					stringReadPos++;
					return (BG | BE);
				}
				else
					return BG;
			}
			else if (c == '-')
			{
				if (s.charAt(++stringReadPos) == '=')
				{
					stringReadPos++;
					return (BN | BE);
				}
				else
					throw (new FormatException
                		("Bad symbolic period relation string: " + s));
			}
			else if (c == '=')
			{
				stringReadPos++;
				return BE;
			}
			else if (c == '*')
			{
				stringReadPos++;
				return (BG | BN | BE);
			}
			else
				return BN;
		}
		catch (StringIndexOutOfBoundsException e)
		{
			throw (new FormatException
                		("Bad symbolic period relation string: " + s));
        }
	}
	
	private boolean valid(int aPhase, int bPhase, int cPhase)
	{
        //Algorithm 9
        if (( ((aPhase & AN) != 0) || ((cPhase & CN) != 0))
            	&& ((bPhase & BE) == 0)) //1
            return false;
        if ((bPhase & (BG | BN)) !=0 ) //2
        {
            if ( ((aPhase & (AL | AG)) == 0) ||
            		((cPhase & (CL | CG)) == 0) ) //2.1
            	return false;
            else if ( ((aPhase == AL) || aPhase == (AL | AN)) &&
                	((cPhase & CG) == 0) ) //2.2
            	return false;
            else if ( ((aPhase == AG) || aPhase == (AG | AN)) &&
                	((cPhase & CL) == 0) ) //2.3
            	return false;
        }
        return true; //3
	}
	
	private int fixMidPhase(int d)
	{
		int aPhase = d & (AL | AN | AG);
		int bPhase = d & (BG | BN | BE);
		int cPhase = d & (CL | CN | CG);
		if (valid(aPhase, bPhase, cPhase))
			return d;
		else if (bPhase == (BG | BN | BE)) //bPhase is *
			return (d & (~BG) & (~BN)); //make bPhase =
		else
			return d; //cannot be fixed and will still be invalid
	}
	
	public Relation propagate(Relation r2, Relation r3)
	{
		//Algorithm 20, function propagate(r1, r2, r3) [r1 = this]
		PointPeriodRelation p1, p2, p3; //1
		try {p1 = toPointPeriodRelation();}
		catch (TranslationException e)
			{return new SymbolicPeriodRelation();}
		try {p2 = ((PeriodRelation)r2).toPointPeriodRelation();}
		catch (TranslationException e)
			{return new SymbolicPeriodRelation();}
		try {p3 = ((PeriodRelation)r3).toPointPeriodRelation();}
		catch (TranslationException e)
			{return new SymbolicPeriodRelation();}
		try
		{
			return ((PointPeriodRelation)p1.propagate(p2, p3)).
				toSymbolicPeriodRelation(); //3, 4
		}
		catch (TranslationException e)
		{
			return new SymbolicPeriodRelation();
		}
	}
}