package ee.ut.ga.model;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import ee.ut.ga.util.ConstantsEnum;
import ee.ut.ga.util.PwmUtil;

/**
 * Position-WeightMatrix object. Comparable by fitness function quality.
 */
public class PWM implements Comparable {
  public static Logger log = Logger.getLogger(PWM.class);

  private Map pam = new HashMap(); // Map <MatrixKey, Long> Position-Align-Matrix
  private Map pwm = new HashMap(); // Map <MatrixKey, BigDecimal>
  private String pattern; // Approximate pattern of Position-Weight-Matrix
  private BigDecimal fitness; // Fitness function comparable value.
  private int length; // Length of each row in PAM and PWM.
  private int dataGoodAll;
  private int dataGood;
  private int dataBadAll;
  private int dataBad;
  private double weightThreshold;

  public PWM(Map pam, Map priors) throws Exception {
    this.pam = new HashMap(pam);
    this.length = PwmUtil.getLength(pam);
    this.pwm = new HashMap(PwmUtil.getPwm(pam, priors, length));
    this.pattern = PwmUtil.getPattern(pwm, length);
  }
  
  public int getDataBad() {
    return dataBad;
  }

  public void setDataBad(int dataBad) {
    this.dataBad = dataBad;
  }

  public int getDataBadAll() {
    return dataBadAll;
  }

  public void setDataBadAll(int dataBadAll) {
    this.dataBadAll = dataBadAll;
  }
  
  public int getDataGood() {
    return dataGood;
  }

  public void setDataGood(int dataGood) {
    this.dataGood = dataGood;
  }
  
  public int getDataGoodAll() {
    return dataGoodAll;
  }

  public void setDataGoodAll(int dataGoodAll) {
    this.dataGoodAll = dataGoodAll;
  }
  
  public double getWeightThreshold() {
    return weightThreshold;
  }

  public void setWeightThreshold(double weightThreshold) {
    this.weightThreshold = weightThreshold;
  }
  
  public void setFitness(BigDecimal fitness) {
    this.fitness = fitness;
  }

  public String getPattern() {
    return pattern;
  }

  public int getLength() {
    return length;
  }

  public Map getPam() {
    return pam;
  }

  public Map getPwm() {
    return pwm;
  }

  public BigDecimal getFitness() {
    return fitness;
  }

  public int compareTo(Object o) {
    if (!(o instanceof PWM))
      throw new ClassCastException("Must be a PWM object!");
    return - getFitness().compareTo(((PWM) o).getFitness());
  }

  public String toString() {
    StringBuffer result = new StringBuffer();
    result.append("##########################################################################################\n");
    
    result.append(">").append(pattern).append("\n");
    
    result.append(">").append("pwm-props").append("\n");
    result.append(constructPwmProps());
    
    result.append(">").append("pam").append("\n");
    for (int i = 0; i < ConstantsEnum.NUCLEOTIDES.length; i++) {
      result.append(ConstantsEnum.NUCLEOTIDES[i]).append("\t");
      for (int j = 0; j < length; j++) {
        result.append(pam.get(new MatrixKey(ConstantsEnum.NUCLEOTIDES[i], j))).append("\t");
      }
      result.append("\n");
    }
    
    result.append(">").append("pwm").append("\n");
    for (int i = 0; i < ConstantsEnum.NUCLEOTIDES.length; i++) {
      result.append("#").append(ConstantsEnum.NUCLEOTIDES[i]).append("\t");
      for (int j = 0; j < length; j++) {
        result.append(((BigDecimal) pwm.get(new MatrixKey(ConstantsEnum.NUCLEOTIDES[i], j))).setScale(3, BigDecimal.ROUND_HALF_UP)).append("\t");
      }
      result.append("\n");
    }
    
    result.append("##########################################################################################\n");
    return result.toString();
  }
  
  /**
   * Construct the string of properties to write it into log-file and the last solution.
   */
  private String constructPwmProps() {
    StringBuffer props = new StringBuffer(">");
    props.append(fitness).append(":").append(weightThreshold).append(":");
    props.append(dataGoodAll).append(":").append(dataBadAll).append(":");
    props.append(dataGood).append(":").append(dataBad).append("\n");
    
    return props.toString();
  }
  
  /**
   * Reads and writes properties into object. 
   */
  public void setPwmProps(String propsString) {
    String[] props = propsString.trim().substring(1).trim().split(":");
    setFitness(new BigDecimal(props[0]));
    setWeightThreshold(Double.valueOf(props[1]).doubleValue());
    setDataGoodAll(Long.valueOf(props[2]).intValue());
    setDataBadAll(Long.valueOf(props[3]).intValue());
    setDataGood(Long.valueOf(props[4]).intValue());
    setDataBad(Long.valueOf(props[5]).intValue());
  }
}