package taskspaces.rna;

import java.io.*;
import java.util.*;
import java.net.*;

/**
 * Contains methods to generate random RNA 
 * sequence Strings from a sequence, generate structures from
 * sequences, generate a given number of random RNA 
 * sequences of a given length, and create random base pairs.
 *
 * @author      Rob Markel 
 * @version     1.00
 */
public class SequenceGenerator implements Serializable
{ 
   /** 
   * Generates the input number of random sequences 
   * using the input String. It is randomized using
   * the shuffleString() method of the Shuffle class.
   * @param sequence the sequence to randomize.
   * @param num number of sequences to return. 
   * @return the randomized sequences. 
   */
   public static String[] getRandomizedSequences(String seq,int num) throws Exception
   {
      String[] s=new String[num];
      for(int i=0; i<num; i++) s[i]=Shuffler.shuffleString(seq,0,seq.length());
      return s;
   }

   /**
    * Returns a random piece of the input
    * String or a lengthened randomized String of the
    * input length. The character composition is maintained. 
    * @param input sequence for input.
    * @return the shuffled sequence, either a piece or
    * shuffled concatenation of the input sequence up to the given length.
    */
   public static String getCompositionSequence(String baseSequence,int length) throws Exception
   {
      int len=baseSequence.length();
      char[] chars=new char[length];

         java.util.Random myRand = new java.util.Random(100);
         System.out.println("putting seed 100 seq 1");

	 //      for(int i=0; i<length; i++) chars[i]=baseSequence.charAt((int)(Math.random()*len));
      for(int i=0; i<length; i++) chars[i]=baseSequence.charAt((int)(myRand.nextDouble()*len));
      return new String(chars);
   }

   /**
    * Returns a base pair using the composition of the passed sequence.
    * @param baseSequence a sequence.
    * @return randomly selected base pair from the passed sequence.
    */
    public static char[] getCompositionPair(String baseSequence)
    {
        char[] pair=new char[2];
         java.util.Random myRand = new java.util.Random(1000);
         System.out.println("putting seed 1000 seq 2");
	 //	char c=baseSequence.charAt((int)(Math.random()*baseSequence.length()));
	char c=baseSequence.charAt((int)(myRand.nextDouble()*baseSequence.length()));
        pair[0]=c;
        pair[1]=getPairingBase(c);
        return pair;
    }

    /**
     * Returns the pair of the passed base.
     * @param c an RNA base.
     * @return the pairing RNA base.
     */
    public static char getPairingBase(char c)
    {
        char ch=' ';
	if(c=='A') ch='U';
        if(c=='C') ch='G';
        if(c=='U') ch='A';
        if(c=='G') ch='C';
        return ch;
    }

   /** 
   * Generates an input number of random 
   * sequences using the MersenneTwisterFast class. 
   * @param num number of sequences to generate.  
   * @param sequenceLength desired length of generated sequences.
   * @param seed a random seed.
   * @return the generated random sequences. 
   */
   public static String[] getRandomSequences(int num,int sequenceLength,long seed)
   {
      String[] sequences=new String[num];
      int k=0;
      MersenneTwisterFast r=new MersenneTwisterFast(seed);
      for(int i=0; i<num; i++)
      {
         char[] sequence=new char[sequenceLength];
         for(int j=0; j<sequenceLength; j++)
         {
            k=r.nextInt();
            int modulus=k%4;
            switch(Math.abs(modulus))
            {
               case 0: sequence[j]='U'; break;
               case 1: sequence[j]='C'; break;
               case 2: sequence[j]='A'; break;
               case 3: sequence[j]='G'; break;
            }
         }
         sequences[i]=(new String(sequence));
      }
      return sequences;
   }

   /**
   * Generates a random sequence
   * using the MersenneTwisterFast class.
   * @param sequenceLength desired length of generated sequence.
   * @param seed a random seed.
   * @return the generated random sequences.
   */
   public static String getRandomSequence(int sequenceLength,long seed)
   {
      MersenneTwisterFast r=new MersenneTwisterFast(seed);
      char[] sequenceChars=new char[sequenceLength];
      for(int j=0; j<sequenceLength; j++)
      {
         int k=r.nextInt();
         int modulus=k%4;
         switch(Math.abs(modulus))
         {
            case 0: sequenceChars[j]='U'; break;
            case 1: sequenceChars[j]='C'; break;
            case 2: sequenceChars[j]='A'; break;
            case 3: sequenceChars[j]='G'; break;
         }
      }
      return new String(sequenceChars);
   }

   /**
   * Returns a random base.
   * @param seed a random seed.
   * @return the random base.
   */
   public static char getRandomBase(long seed)
   {
      MersenneTwisterFast twister=new MersenneTwisterFast(seed);
      int num=twister.nextInt();
      int modulus=num%4;
      char base=' ';
      switch(Math.abs(modulus))
      {
         case 0: base='U'; break;
         case 1: base='C'; break;
         case 2: base='A'; break;
         case 3: base='G'; break;
      }
      return base;
   }

   /**
   * Returns a base with the given composition.
   * @param composition base composition percentages.
   * @return the selected base.
   */
   public static char getCompositionBase(float[] f)
   {
      float[] comps=new float[f.length];
      System.arraycopy(f,0,comps,0,f.length);
      for(int i=1; i<comps.length; i++) comps[i]+=comps[i-1];
      //for(int i=0; i<comps.length; i++) System.out.print(comps[i]+",");
         java.util.Random myRand = new java.util.Random(10000);
         System.out.println("putting seed 10000 seq 3");
      float d=(float)myRand.nextDouble();
      //System.out.println("\n"+d);
      char base=' ';
      if(d<comps[0]) base='A';
      if(d<comps[1] && d>=comps[0]) base='C';
      if(d<comps[2] && d>=comps[1]) base='G';
      if(d>comps[2]) base='U';
      return base;
   }

   /**
   * Returns a base with the given composition.
   * @param composition base composition percentages.
   * @return the selected base.
   */
   public static char getCompositionBase(float[] f,java.util.Random myRand)
   {
      float[] comps=new float[f.length];
      System.arraycopy(f,0,comps,0,f.length);
      for(int i=1; i<comps.length; i++) comps[i]+=comps[i-1];
      //for(int i=0; i<comps.length; i++) System.out.print(comps[i]+",");
      //         java.util.Random myRand = new java.util.Random(10000);
      //   System.out.println("putting seed 10000 seq 3");
      float d=(float)myRand.nextDouble();
      //System.out.println("\n"+d);
      char base=' ';
      if(d<comps[0]) base='A';
      if(d<comps[1] && d>=comps[0]) base='C';
      if(d<comps[2] && d>=comps[1]) base='G';
      if(d>comps[2]) base='U';
      return base;
   }

   /**
    * Returns a random base pair. 
    * @param b if <code>true</code> include GU and UG as valid pairs.
    * @return a base pair.
    */
   public static char[] getRandomPair(boolean b)
   {
      String pair=null;
      String[] basePairs={"CG","GC","AU","UA","GU","UG"};
         java.util.Random myRand = new java.util.Random(100000);
         System.out.println("putting seed 100000 seq 4");
      double d=myRand.nextDouble();
      if(!b)
      {
         if(d<0.25) pair=basePairs[0];
         if(d<0.50 && d>0.25) pair=basePairs[1];
         if(d<0.75 && d>0.50) pair=basePairs[2];
         if(d>0.75) pair=basePairs[3];
      }
      else
      {
         if(d<0.16666666666667) pair=basePairs[0];
         if(d<0.33333333333337 && d>0.16666666666667) pair=basePairs[1];
         if(d<0.50000000000004 && d>0.33333333333337) pair=basePairs[2];
         if(d<0.66666666666671 && d>0.50000000000004) pair=basePairs[3];
         if(d<0.83333333333338 && d>0.66666666666671) pair=basePairs[4];
         if(d>0.83333333333338) pair=basePairs[5];
      }
      char[] basePair=pair.toCharArray();
      return basePair;
   }


   /**
    * Returns a randomized sequence with the given base composition. 
    * @param composition base composition percentage values.
    * @return the randomized sequence String.
    */
   public static String getPartitionSequence(int[] composition)
   {  
      String str=null;
      try
      {  
         int sum=0;
         for(int i=0; i<composition.length; i++)
         {
            sum+=composition[i];
         }
         char[] sequence=new char[sum];
         int index=0;
         for(int i=0; i<composition[0]; i++)
         {     
            sequence[i]='A';
            index++;
         }
         for(int i=0; i<composition[1]; i++)
         {  
            sequence[index]='C';
            index++;
         }
         for(int i=0; i<composition[2]; i++)
         {  
            sequence[index]='G';
            index++;
         }
         for(int i=0; i<composition[3]; i++)
         {  
            sequence[index]='U';
            index++;
         }
         String s=new String(sequence);
         str=Shuffler.shuffleString(s,0,s.length());
      } catch(Exception e) {e.printStackTrace();}
      return str;
   }

   /**
    * Generates a sequence with the base composition of the
    * passed composition percentage values and with embedded database 
    * modules in random but ascending positions.
    * @param modules sequence modules.
    * @param sequenceLength the desired sequence length.
    * @param composition the desired base composition of the spacer.
    * @return the generated String.
    */
   public static List embedModules(String[] modules,int sequenceLength,float[] composition, java.util.Random myRand) 
   throws Exception
   {
      int mods=modules.length;
      int modulesLength=0;
      int[] moduleSizes=new int[mods];
      for(int i=0; i<mods; i++)
      {
         int len=modules[i].length();
         moduleSizes[i]=len;
         modulesLength+=len;
      }
      if(sequenceLength<modulesLength) return null;
      //String spacer=getCompositionSequence(baseSequence,sequenceLength-modulesLength);
      char[] spacerChars=new char[sequenceLength-modulesLength];
      for(int i=0; i<spacerChars.length; i++)
      {
         spacerChars[i]=getCompositionBase(composition,myRand);
      }
      String spacer=new String(spacerChars);
      //System.out.println("Spacer: "+spacer);
      for(int ii=0;ii<modules.length;ii++)
	  {
	      //System.out.println("Module: "+modules[ii]);
	  }
      int spacerLength=spacer.length();
      if(spacerLength+1<mods) return null;
      return embed(modules,spacer,myRand);
   }

   /**
    * Generates a sequence with the base composition of the
    * passed base sequence with embedded database modules in random but ascending
    * positions.
    * @param modules sequence modules.
    * @param sequenceLength the desired sequence length.
    * @return the generated String.
    */
   public static List embedModules(String[] modules,int sequenceLength,String baseSequence) 
   throws Exception
   {
      int mods=modules.length;
      int modulesLength=0;
      int[] moduleSizes=new int[mods];
      for(int i=0; i<mods; i++)
      {
         int len=modules[i].length();
         moduleSizes[i]=len;
         modulesLength+=len;
      }
      if(sequenceLength<modulesLength) return null;
      String spacer=getCompositionSequence(baseSequence,sequenceLength-modulesLength);
      int spacerLength=spacer.length();
      if(spacerLength<mods) return null;
      return embed(modules,spacer);
   }


   /**
    * Generates a random sequence with embedded modules in random but 
    * ascending positions.
    * @param modules module sequences.
    * @param sequenceLength the desired sequence length.
    * @return the generated String.
    */
   public static List embedModules(String[] modules,int sequenceLength)
   throws Exception
   {
      int mods=modules.length;
      int modulesLength=0;
      int[] moduleSizes=new int[mods];
      for(int i=0; i<mods; i++)
      {
         int len=modules[i].length();
         moduleSizes[i]=len;
         modulesLength+=len;
      }
      if(sequenceLength<modulesLength) return null;
      String spacer=getRandomSequence(sequenceLength-modulesLength,System.currentTimeMillis());
      int spacerLength=spacer.length();
      if(spacerLength<mods) return null;
      return embed(modules,spacer);
   }


   /**
    * Generates a random sequence with embedded
    * modules, based on the composition of the modules.
    * <b>NOTE:</b> This method only works with sequence lengths which
    * are multiples of 20. 
    * @param modules sequence modules.
    * @return the generated String.
    */
   public static List embedCompositionModules(String[] modules,int sequenceLength) throws Exception
   {
      int a=0, c=0, u=0, g=0;
      int mods=modules.length;
      int moduleLength=0;
      int modulesLength=0;
      int[] moduleSizes=new int[modules.length];
      char ch;
      for(int i=0; i<mods; i++)
      {
         moduleLength=modules[i].length();
         moduleSizes[i]=moduleLength;
         modulesLength+=moduleLength;
         for(int j=0; j<moduleLength; j++)
         {
            ch=modules[i].charAt(j);
            switch(ch)
            {
               case 'A': a++; break;
               case 'C': c++; break;
               case 'U': u++; break;
               case 'G': g++; break;
            }
         }
      }
      int composition=Math.abs(sequenceLength/4);
      int spacerA=composition-a;
      int spacerC=composition-c;
      int spacerU=composition-u;
      int spacerG=composition-g;
      if(sequenceLength<modulesLength) return null;
      char[] spacerChars=new char[sequenceLength-modulesLength];
      if(spacerChars.length<mods) return null;
      int AIndices=spacerA;
      int CIndices=spacerA+spacerC;
      int UIndices=CIndices+spacerU;
      int GIndices=UIndices+spacerG;
      for(int i=0; i<spacerChars.length; i++)
      {
         if(i<=AIndices) spacerChars[i]='A';
         if(i<=CIndices && i>AIndices) spacerChars[i]='C';
         if(i<=UIndices && i>CIndices) spacerChars[i]='U';
         if(i>UIndices) spacerChars[i]='G';
      }
      String s=new String(spacerChars);
      String spacer=Shuffler.shuffleString(s,0,s.length());
      return embed(modules,spacer);
   }


   /* PRIVATE METHODS */

   /** 
   * Generates an integer array of sorted 
   * points for inserting modules into sequences.
   * @param moduleNum the number of modules.
   * @return the random but ascending values.
   */
   private static int[] getInsertionPoints(int moduleNum,int spacerLength)
   {
                java.util.Random myRand = new java.util.Random(1000000);
         System.out.println("putting seed 1000000 seq 5");
      //System.out.print("***"+spacerLength+" ");
      int[] points=new int[moduleNum];
      for(int i=0; i<moduleNum; i++)
      {
         points[i]=(int)(myRand.nextDouble()*(spacerLength+1));
         if(i>0)
         {
            for(int j=i-1;j>=0;j--)
            {
               if(points[i]==points[j]) i--;
            }
         }
      }
      Arrays.sort(points);
      //for(int i=0;i<points.length;i++) System.out.print("*"+points[i]+" ");
      return points;
   }

   /** 
   * Generates an integer array of sorted 
   * points for inserting modules into sequences.
   * @param moduleNum the number of modules.
   * @return the random but ascending values.
   */
   private static int[] getInsertionPoints(int moduleNum,int spacerLength, java.util.Random myRand)
   {
       //         java.util.Random myRand = new java.util.Random(1000000);
       //  System.out.println("putting seed 1000000 seq 5");
      //System.out.print("***"+spacerLength+" ");
      int[] points=new int[moduleNum];
      for(int i=0; i<moduleNum; i++)
      {
         points[i]=(int)(myRand.nextDouble()*(spacerLength+1));
         if(i>0)
         {
            for(int j=i-1;j>=0;j--)
            {
               if(points[i]==points[j]) i--;
            }
         }
      }
      Arrays.sort(points);
      //for(int i=0;i<points.length;i++) System.out.print("*"+points[i]+" ");
      return points;
   }

   /**
   * Embeds modules into a background
   * sequence at specified points. 
   * @param modules the sequence modules.
   * @param sequence the background sequence.
   * @return List containing generated sequence and insertion points.
   */
   private static List embed(String[] modules,String spacer)
   {
      int mods=modules.length;
      int[] points=getInsertionPoints(mods,spacer.length());
      int[] p=new int[mods];
      String s=spacer.substring(0,points[0])+modules[0];
      p[0]=points[0];
      for(int i=1; i<mods; i++)
      {
         s+=spacer.substring(points[i-1],points[i])+modules[i];
         p[i]=p[i-1]+(points[i]-points[i-1])+modules[i-1].length();
      }
      s+=spacer.substring(points[points.length-1],spacer.length());
      //System.out.print("+++"+s.length()+" ");
      List l=new ArrayList(2);
      l.add(s);
      l.add(p);
      //System.out.println("Sequence: "+s);
      for(int ii=0;ii<mods;ii++)
	 {
	     //System.out.println("Insertion point: "+p[ii]);
	 }
     return l;
   }

   /**
   * Embeds modules into a background
   * sequence at specified points. 
   * @param modules the sequence modules.
   * @param sequence the background sequence.
   * @return List containing generated sequence and insertion points.
   */
   private static List embed(String[] modules,String spacer, java.util.Random myRand)
   {
      int mods=modules.length;
      int[] points=getInsertionPoints(mods,spacer.length(),myRand);
      int[] p=new int[mods];
      String s=spacer.substring(0,points[0])+modules[0];
      p[0]=points[0];
      for(int i=1; i<mods; i++)
      {
         s+=spacer.substring(points[i-1],points[i])+modules[i];
         p[i]=p[i-1]+(points[i]-points[i-1])+modules[i-1].length();
      }
      s+=spacer.substring(points[points.length-1],spacer.length());
      //System.out.print("+++"+s.length()+" ");
      List l=new ArrayList(2);
      l.add(s);
      l.add(p);
      //System.out.println("Sequence: "+s);
      for(int ii=0;ii<mods;ii++)
	 {
	     //System.out.println("Insertion point: "+p[ii]);
	 }
     return l;
   }

}
