package org.fda.data;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import org.fda.alignment.ScaffoldContig;
import org.fda.checks.CheckAligner;
import org.fda.commands.Command;
import org.fda.data.Enums.AlignmentTool;
import org.fda.data.Enums.CoverageType;
import org.fda.data.Enums.HashingAlgorithm;
import org.fda.data.Enums.ScriptRunMode;
import org.fda.graphdrawing.ImageFormat;
import org.fda.regression.RegressionModel;
import org.fda.regression.RegressionSubModel;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;


/**
 *
 * @author Gokhan.Yavas
 */
public class Utilities {
    public static final HashingAlgorithm hashalg= Enums.HashingAlgorithm.SHA_256;
    
    public static final boolean IS_WINDOWS = System.getProperty("os.name").contains("indow");
    public static final int samplesize=1000;
    public static final NumberFormat numberFormatter = NumberFormat.getInstance();
    public static final NumberFormat numberFormatterSmall = NumberFormat.getInstance();
    // contig generator parameters
    public static boolean contigsOverlap = false;
    public static double nratio = 0.5;
    public static double depthCoverage=1;
    public static int simulation_mincontiglength=50;
    public static double simulation_coverage_ratio_threshold=0.05;
    
    // File names
    public static final String assemblystatsFile="reports"+File.separator+"assembly.stat";    
    public static final String recordedDataFile = "partition.args";
    public static final String chrStatFileName = "bin"+File.separator+"references.bin";
    public static final String chrGapsFileName = "reports"+File.separator+"reference_scaffoldinggaps.report";
    public static final String contigsStats = "reports"+File.separator+"scaffolds.stat";
    public static final String alignmentFile = "reports"+File.separator+"scaffolds.alignment";
    public static final String misassemblyFile = "reports"+File.separator+"misassembly.report";
    public static final String contigObjFile = "contigRecords.bin";
    public static final String contigBinFileName = "bin"+File.separator+"scaffold_stats.bin";
    public static final String modelFileName = "bin"+File.separator+"model.bin";
    public static final String[] sub_dirs = new String[]{"bin", "charts", "reports"};
    public static final String cmd_key_word = "CMD";
    public static boolean showModelData = false;
    public static boolean showModelInTabularFormat = false;
    
    public static File rootDir;
    
    public static final long maxReferenceFileSize=300000000;
    public static final long maxQueryFileSize=3000000000L;
    public static final String ls = System.lineSeparator();
    private static final int maxDigitsInRefFileNumber=5;
    public static ScriptRunMode submitJobs = ScriptRunMode.multithread;  
    public static String submitJobsParameters = "";  
    public static boolean indeldiscovery=false;
    public static final int mark4BackTrack=50000;
    public static final int intervalSize = 100000;
    public static AlignmentTool alignmentTool=AlignmentTool.minimap2;
    public static ImageFormat format=ImageFormat.jpeg;

    public static boolean contigAlignmentDraw = false;
    public static Command current_cmd ;
    public static boolean assignQuality=true ;
    
    
    // constants for alignment filtering
    public static final double maxOverlap = 90;
    public static final double epsilon = -1;
    public static int alignmentIdentityThreshold=99;
    public static final int alignmentLengthThreshold=0;

    // constants for inconsistency calculation
    public static int alignmentDistanceThreshold=1000;
    public static int initialAlignmentDistanceThreshold=100;
    public static int alignmentDistanceThresholdInc=100;
    public static int maxAlignmentDistanceThreshold=10000;
    public static double sizeratiodifference = 0.1;
    public static double scaffoldinggapoverlapratio=0.9;
    public static RegressionModel regm;
    public static int fastarecordlengththreshold=0;
    
    
    public static double alpha=1;
    public static double beta=1;
    public static int lengthScalingFactor;
    public static double maxQScore=1;
    public static int sleepTime = 100; //in ms
    
    
    // thread parameters
    public static int no_filterThreads=4;
    public static int no_threads=4;
    public static int no_ReaderThreads=2;
    public static final int queueingFactor=10;
    public static int alignmentqueuesize ;
    //public static int maxFileSize = 10;
    public static final int maxFileSize = 3000;
    public static final int mergeFactor = 4;
    public static final int mergerbuffsize = 100;
    // parameter for collecting the alignment results the globalFilter way
    public static boolean globalFilter = false;
    
    // Now set the length threshold levels
    public static int minContigLength=10;
    public static int maxContigLength=10000000;
    public static Set<Integer> lengthLevels = new TreeSet();
    
    
    // parameters for graph drawing
    public static int width=1200;
    public static int height=800;
    public static int no_hist_bins=10;
    public static double intervalLength4Histogram = Utilities.maxQScore/ (double) Utilities.no_hist_bins ;
    public static BigDecimal adder = new BigDecimal(Utilities.intervalLength4Histogram);
    public static double difference = 0.01;
    public static QUALLEVEL qlevel = new QUALLEVEL(1, 0, difference);
    
    public static String version;
    static {
        numberFormatter.setRoundingMode(RoundingMode.HALF_EVEN);
        numberFormatter.setMaximumFractionDigits(3);
        numberFormatter.setGroupingUsed(false);
        
        numberFormatterSmall.setRoundingMode(RoundingMode.HALF_EVEN);
        numberFormatterSmall.setMaximumFractionDigits(20);
        numberFormatterSmall.setGroupingUsed(false);
        
    }
    public static int determineAllowedMinSimContigLength(long l){
        if(l<100000){            
            return 50;
        }
        else{
            return 500;
        }        
    } 
    public static String byteToHex(byte[] bytes) {
        StringBuffer buff = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            buff.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
        }
        return buff.toString();
    }    
    public static String bytesToHex(byte[] in) {
        StringBuilder builder = new StringBuilder();
        for(byte b : in) {
            builder.append(String.format("%02x", b));
        }
        return builder.toString();
    }    
    public static void checkAligner(File alignerDir){
        
        CheckAligner mcheck = new CheckAligner(alignerDir);
        // check if the executables are there
        if(!mcheck.checkExecutables(Utilities.alignmentTool.getExec())){
            // check if the compilation has been tried before
            if(!new File(alignerDir.getAbsolutePath()+File.separator+"error.tch").exists()){
                // try to compile 
                Runtime r = Runtime.getRuntime();
                Process p;
                try{
                    p = r.exec(Utilities.alignmentTool.getCompileCommand(alignerDir));                             
                    int ret = p.waitFor();                      
                    if(ret!=0){
                        // create a error.tch file
                        File f = new File(alignerDir.getAbsolutePath()+File.separator+"error.tch");
                        if(!f.exists())
                            f.createNewFile();
                        System.out.println("ERROR: Alignment tool: "+Utilities.alignmentTool+" could not be compiled!");
                        System.exit(1);                                                
                    }
                }
                catch(IOException | InterruptedException io){io.printStackTrace();}
            }
            else{
                System.out.println("ERROR: Alignment tool: "+Utilities.alignmentTool+" could not be compiled!");
                System.exit(1);
            }
        }        
    }

    private static double getExpectedMisassembly(int contigSize, Reference r){
        //return contigSize*contigSizeCoefficient+alignmentDistanceThreshold*thresholdCoefficient+intercept;
        RegressionSubModel m = regm.getSubModel(r);
        return contigSize*m.getLengthCoeff()+alignmentDistanceThreshold*m.getThresholdCoeff()+m.getIntercept();
    }
    
    public static double getPenalty(int contigSize, int misNo, Reference r){
        if(misNo==0)            
            return 0;        
        double expected = getExpectedMisassembly(contigSize, r);
        double realMiss;
        if(expected>=0)
            realMiss=misNo - expected;
        else
            realMiss=misNo;
        
        if(realMiss<=1)
            return 0;
        else
            return Math.log(realMiss)/Math.log(100);                
    }
    
    
    public static double getLengthScalingCoefficient(int length){
        double d = 1/-Math.log10((double)length/((double)lengthScalingFactor*10));
        if(d<0)
            return 1;
        else
            return Math.min(d, 1);                
    }
    
    public static List sample(int size){
        Random r = new Random();
        double ratio = (double)samplesize / (size-2);
        List<Integer> toret = new ArrayList();
        toret.add(0);
        for(int i=1 ; i< size-1; i++){
            if(r.nextDouble()<=ratio){
                toret.add(i);
            }
        }
        toret.add(size-1);
        //toret.toArray(new Integer[0]);
        return toret;
    }
    public static String getRelativePath(File file){
        int baselen = rootDir.getAbsolutePath().length();
        return file.getAbsolutePath().substring(baselen+1);
    }
    public static String getRelativePath(File source, File file){
        int baselen = source.getAbsolutePath().length();
        return file.getAbsolutePath().substring(baselen+1);
    }
    
    public static void resetQSize(){
        alignmentqueuesize = Math.max(no_filterThreads*queueingFactor / no_ReaderThreads, 10);                    
    }
    
    private static void setLengthLevels(){
        int st = (int)Math.pow(10, (int)Math.log10(minContigLength));
        
        for(int i=st ; i<= maxContigLength; i=i*10)
            lengthLevels.add(i);
    }
    public static void setLengthLevels(List<Integer> l){
        lengthLevels = new TreeSet();
        lengthLevels.addAll(l);
    }
    
    public static Set<Integer> getLengthLevels(){
        if(lengthLevels.isEmpty())
            setLengthLevels();
        return lengthLevels;
    }    
    
    
    public static long scoreLocal(long scorej, int leni, int lenj, int olap, double idyi, double maxolap){
        if ( olap > 0  &&
             ((double)olap / (double)leni * 100.0 > maxolap  ||
              (double)olap / (double)lenj * 100.0 > maxolap) )
          return -1;
        else
          return (scorej + (long)((leni - olap) * Math.pow (idyi, 2)));        
    }
    public static long scoreGlobal(long scorej, long leni, long olap, double idyi)
    {
        return (scorej + (long)((leni - olap) * Math.pow (idyi, 2)));
    }
    public static long scoreGlobal2(long scorej, int leniR, int leniQ, int lenjR, int lenjQ,  int olapR, int olapQ, double idyi, double maxolap){
        if (( olapR > 0  &&
             ((double)olapR / (double)leniR * 100.0 > maxolap  ||
              (double)olapR / (double)lenjR * 100.0 > maxolap) ) 
                ||
                ( olapQ > 0  &&
                ((double)olapQ / (double)leniQ * 100.0 > maxolap  ||
              (double)olapQ / (double)lenjQ * 100.0 > maxolap) ))
            return -1;
        else{
            int leni = leniR > leniQ ? leniQ : leniR;
            int olap = olapR > olapQ ? olapR : olapQ;
            return (scorej + (long)((leni - olap) * Math.pow (idyi, 2)));        
        }
    }
        
    public static double calcPercentage(long a, long b){
        if(b==0)
            return 0;
        return 100 * (double)a / (double)b ;
    }
    public static double calcPercentage(double a, double b){
        if(b==0)
            return 0;
        return 100 * a / b ;
    }
    public static boolean deleteFolder(File f){
        File[] all = f.listFiles();
        boolean ret = true;
        for(File d:all){
            if(d.isDirectory()){
                ret = ret & deleteFolder(d);                                
            }
            else{                
                ret = ret & d.delete();
            }
        }
        if(ret){
            boolean dele = f.delete();
        }
        else
            System.out.println("Error deleting one of the subfolders/subfiles");
        return ret;
    }
    
    public static void findAllFiles(File path, List<File> arr, String[] filetypes, String[] filestartsWith){      
        for(String f : filetypes){
            f = f.trim();
        }
        for(String f : filestartsWith){
            f = f.trim();
        }
        boolean matched =false;
        if(path.isFile()){
            if(filetypes.length>0 && filestartsWith.length>0){
                for(String f:filetypes){                    
                    if(path.getName().endsWith(f)){
                        // Now check if the file starts with the desired prefix
                        for(String pref : filestartsWith){
                            if(path.getName().startsWith(pref)){
                                arr.add(path);
                                matched = true;
                                break;
                            }
                        }
                    }
                    if(matched)
                        break;
                }
            }
            else if(filetypes.length>0 && filestartsWith.length==0){
                for(String f:filetypes){
                    if(path.getName().endsWith(f)){
                        arr.add(path);
                        break;
                    }
                }                                
            }
            else if(filetypes.length==0 && filestartsWith.length>0){
                for(String f:filestartsWith){
                    
                    if(path.getName().startsWith(f)){
                        arr.add(path);
                        break;
                    }
                }                                
            }
            else{
                arr.add(path);
            }
        }else if(path.isDirectory()){
            File files[] = path.listFiles();            
            for(File dirOrFile: files){
                findAllFiles(dirOrFile, arr, filetypes, filestartsWith);
            }
        }        
    }
    public static Map<CoverageType , Integer> getEmptyCoverageMap(){
        Map<CoverageType , Integer> map = new HashMap<>();
        for(CoverageType t: CoverageType.values())
            map.put(t, 0);
        return map;
    }
    
    public static String giveName(int i){
        String s = String.valueOf(i);
        int maxrep = maxDigitsInRefFileNumber-s.length();
        
        if(maxrep<0){
            StringBuilder m=new StringBuilder();
            for(int z =0; z<maxDigitsInRefFileNumber; z++)
                m.append("9");
            System.out.println("The reference contains more than "+m.toString()+" scaffolds/contigs, which is the maximum allowed number of scaffolds/contigs in a reference");
            Utilities.deleteFolder(rootDir);
            System.exit(1);
        }
        for(int k=0; k<maxrep; k++)
            s="0"+s;                    
        return s;
    }
    public static Map<Reference, Integer> calculateNumberOfIntervals(ReferenceSet rset){
        Map<Reference, Integer> ret = new TreeMap<>();        
        int res;
        for(Reference r: rset.getRefs()){
            res = (int)(r.getLength() / intervalSize);
            if((res%intervalSize)!=0 || res ==0)
                res++;
            ret.put(r, res);
        }
        return ret;
    }
    
    // Some helper functionality for graph drawing
    public static void saveAsFile(JFreeChart chart, File file){
        FileOutputStream out = null;
        try{            
            if(file.getParentFile()!=null && !file.getParentFile().exists()){
                file.getParentFile().mkdirs();
            }
            out = new FileOutputStream(file);
            
            if (null != Utilities.format)switch (Utilities.format) {
                case png:
                    ChartUtilities.writeChartAsPNG(out, chart, Utilities.width, Utilities.height);
                    break;
                case jpeg:
                    ChartUtilities.writeChartAsJPEG(out, chart, Utilities.width, Utilities.height);
                    break;
                case pdf:
                    writeChartAsPDF(out, chart, Utilities.width, Utilities.height);
                    break;
                default:
                    break;
            }
            out.flush();
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if (out != null){
                try{
                    out.close();
                }catch(IOException e){
                }
            }
        }
    }
    // Some helper functionality for graph drawing
    public static void saveAsFile(JFreeChart chart, File file, String table){
        FileOutputStream out = null;
        int width, height;
        width = Utilities.width;
        height = Utilities.height;
        try{            
            if(file.getParentFile()!=null && !file.getParentFile().exists()){
                file.getParentFile().mkdirs();
            }
            out = new FileOutputStream(file);
            
            if (null != Utilities.format)switch (Utilities.format) {
                case png:
                    ChartUtilities.writeChartAsPNG(out, chart, width, height);
                    break;
                case jpeg:
                    ChartUtilities.writeChartAsJPEG(out, chart, width, height);
                    break;
                case pdf:
                    writeChartAsPDF(out, chart, width, height);
                    break;
                default:
                    break;
            }
            out.flush();
            
            // Now write the table 
            File tablefile = new File(file.getAbsolutePath().replaceAll(Utilities.format.getExtension(), ".txt"));
            BufferedWriter bw = new BufferedWriter(new FileWriter(tablefile));
            bw.append(table);
            bw.close();
            
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if (out != null){
                try{
                    out.close();
                }catch(IOException e){
                }
            }
        }
    }
    
    // Some helper functionality for graph drawing
    public static void saveAsFile(JFreeChart chart, File file, String table, PlotOrientation p){
        FileOutputStream out = null;
        int width, height;
        if(p == PlotOrientation.VERTICAL){
            width = Utilities.width;
            height = Utilities.height;
        }
        else{
            width = Utilities.height;
            height = Utilities.width;
            
        }
        try{            
            if(file.getParentFile()!=null && !file.getParentFile().exists()){
                file.getParentFile().mkdirs();
            }
            out = new FileOutputStream(file);
            
            if (null != Utilities.format)switch (Utilities.format) {
                case png:
                    ChartUtilities.writeChartAsPNG(out, chart, width, height);
                    break;
                case jpeg:
                    ChartUtilities.writeChartAsJPEG(out, chart, width, height);
                    break;
                case pdf:
                    writeChartAsPDF(out, chart, width, height);
                    break;
                default:
                    break;
            }
            out.flush();
            
            // Now write the table 
            File tablefile = new File(file.getAbsolutePath().replaceAll(Utilities.format.getExtension(), ".txt"));
            BufferedWriter bw = new BufferedWriter(new FileWriter(tablefile));
            bw.append(table);
            bw.close();
            
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if (out != null){
                try{
                    out.close();
                }catch(IOException e){
                }
            }
        }
    }
    
    public static void writeChartAsPDF(FileOutputStream output, JFreeChart chart, int width, int height) throws IOException{
        Rectangle pagesize = new Rectangle(width, height);
        Document document = new Document(pagesize, 50, 50, 50, 50);
        try {
            PdfWriter writer = PdfWriter.getInstance(document, output); 
            document.addAuthor("JFreeChart");
            //document.addSubject(this.chartName);
            document.open(); 
            PdfContentByte cb = writer.getDirectContent();
            PdfTemplate tp = cb.createTemplate(width, height);
            Graphics2D g2 = tp.createGraphics(width, height); 
            Rectangle2D r2D = new Rectangle2D.Double(0, 0, width, height); 
            
            chart.draw(g2, r2D); 
            g2.dispose(); 
            cb.addTemplate(tp, 0, 0);
        } catch(DocumentException de) {
        }finally {
            document.close();
        }
    }
    
    private static char complement(char c){
        switch(c){
            case 'A':
                return 'T';
            case 'T':
                return 'A';
            case 'G':
                return 'C';
            case 'C':
                return 'G';
            case 'a':
                return 'T';
            case 't':
                return 'A';
            case 'g':
                return 'C';
            case 'c':
                return 'G';
            default:
                return 'N';             
        }                    
    }
    public static String reverseComplement(String s){       
        int len=s.length();
        char[] s1= new char[len];
        for(int i =len-1; i>=0; i--)
            s1[len-i-1]= complement(s.charAt(i));            
        return new String(s1);
        
    }
    public static void waiter(List<File> files){
        // Wait until all the jobs are finished
        Set<File> tfiles = Collections.newSetFromMap(new ConcurrentHashMap<File, Boolean>());
        //tfiles.addAll(d.getTouchFiles());
        tfiles.addAll(files);
        while(!tfiles.isEmpty()){
            for(File fi : tfiles){
                if(fi.exists()){
                    tfiles.remove(fi);
                }
            }
            try{
                Thread.sleep(Utilities.sleepTime);
            }
            catch(InterruptedException ie){ie.printStackTrace();}
        }        
    }
    public static void safePrint(ScaffoldContig s) {
        synchronized (System.out) {            
            System.out.print(s.toString(true));
        }
    }
    public static String getPrefix(File file){
        return file.getName().split("\\.")[0];
    }
    

}
