#include "GraphOutput.h"


const string GraphOutput::json_nodes_file_suffix = ".json_nodes";
const string GraphOutput::json_edges_file_suffix = ".json_edges";
const string GraphOutput::json_starters_file_suffix = ".json_starters";
const string GraphOutput::json_file_suffix = ".json";

/************************************************************************************************************************/
/* init function initialize the files need to construct graph file or sequences file					*/
/*															*/
/************************************************************************************************************************/  
void GraphOutput::init(bool erase){
    json_nodes_file_name=(prefix+json_nodes_file_suffix);
    json_edges_file_name=(prefix+json_edges_file_suffix);
    json_starters_file_name=(prefix+json_starters_file_suffix);
    json_file_name=(prefix+json_file_suffix);
    
    nodes_file = fopen(json_nodes_file_name.c_str(),erase?"w":"a");
    edges_file = fopen(json_edges_file_name.c_str(),erase?"w":"a");
    starters_file = fopen(json_starters_file_name.c_str(),erase?"w":"a");
    graph_file = fopen(json_file_name.c_str(),erase?"w":"a");
}
    /************************************************************************************************************************/
/* 		printf GraphOutput and initialize files (files are not erasing)						*/
/*															*/
/************************************************************************************************************************/
GraphOutput::GraphOutput(string prefix, id_els first_id_els)  : prefix(prefix),  first_id_els(first_id_els)
{ // PIERRE: need something different than 0 for the first node
    init(true);
}

/*************************************************************************************************************************/
/* 		Initialize first elements and files  (files are erasing)						*/
/*															*/
/************************************************************************************************************************/
GraphOutput::GraphOutput(string prefix) : prefix(prefix)
{
    first_id_els.node=0;
    first_id_els.edge=0;
    init(true);
}

/************************************************************************************************************************/
/* 		write graph file or sequence file									*/
/*															*/
/************************************************************************************************************************/
void GraphOutput::close()
{
            fclose(nodes_file);
            fclose(edges_file);
            fclose(starters_file);
            ifstream nodes(json_nodes_file_name.c_str(), ios::in);
            ifstream edges(json_edges_file_name.c_str(), ios::in);
            ifstream starters(json_starters_file_name.c_str(), ios::in);
            
            if(!edges || !nodes || !starters){fprintf(stderr,"Cannot open file %s, %s or %s exit\n", json_edges_file_suffix.c_str(), json_nodes_file_suffix.c_str(), json_starters_file_suffix.c_str()); exit(1);}
            
            string line;
            fprintf(graph_file,"{\n"); // overall json {
            bool first=true;
            while(getline(starters,line)){
                if(!first) fprintf(graph_file,",\n"); // separates starters
                first = false;
                fprintf(graph_file,"%s\n", line.c_str());

                    fprintf(graph_file,"\"extremGraphs\":[\n"); // we print this line only if we had at least one
                    
                    int i =0;
                    
                    //fprintf(graph_file," %s \n"line.c_str());
                    bool first_data_xtrem_graph=true;
                    while(getline(nodes,line) && strstr((const char*)line.c_str(), "startNewStarter")==NULL){ //test if is the end of the nodes file and if it's a new 															starter written
                        if(!first_data_xtrem_graph) fprintf(graph_file,",\n"); // separates data_xtrem_graph
                        first_data_xtrem_graph = false;
                        fprintf(graph_file,"{\"data\":{\"id\":\"k%d\", %s \n\"nodes\":[",i,line.c_str()); // prints data for the starter
                        
                        // prints the first node (that necessarly exists)
                        getline(nodes,line);
                        fprintf(graph_file,"\n\t%s",line.c_str()); // prints the first node without comma before
                        //for each node of each extension
                        while(getline(nodes,line)){
                            //test if there are at the end of the array of node for this extension
                            if (strstr((const char*)line.c_str(), "endOfKmer")==NULL){ // test of end if it's an other kmer
                                fprintf(graph_file,",\n\t%s",line.c_str());// prints the other nodes

                            }
                            else{ // prints the edges of nodes of this extension
                                fprintf(graph_file,"\n],\n"); // end of nodes
                                fprintf(graph_file,"\"edges\": ["); // starts of edges
                                getline(edges,line);

                                if (strstr((const char*)line.c_str(), "endOfKmer")==NULL){// test of end if it's an other kmer
                                    fprintf(graph_file,"\n\t%s",line.c_str()); // prints the first edge without comma before

                                }
                                else{ // there was NO edge
                                    break;
                                    //getline(edges,line);
                                }
                                // We already read one edge, lets continue (starting with a ',')
                                //for each edges of each extension
                                while(getline(edges,line)) {
                                    if (strstr((const char*)line.c_str(), "endOfKmer")==NULL){// test of end if it's an other kmer
                                        fprintf(graph_file,",\n\t%s",line.c_str()); // print other edges

                                    }
                                    else{
                                        break;
                                    }
                                }
                                
                                break;
                            }
                            
                        } // end all edges
                        
                        if( ! first_data_xtrem_graph) // we read at least one xtrem graph data
                            fprintf(graph_file,"\n]\n}}"); // END: ] edges, } data, } xtrem graph

                        i++;
                    } // end all xtrem graphs of one starter
                    fprintf(graph_file,"]"); // end all xtrem graphs of this starter
                 // end at least one starter
                
         
                fprintf(graph_file,"\n}}"); // end of a starter
            } // end all starters
 
            fprintf(graph_file,"\n}"); // end of the json
            
            nodes.close(); remove(json_nodes_file_name.c_str());
            edges.close(); remove(json_edges_file_name.c_str());
            starters.close();remove(json_starters_file_name.c_str());
            fclose(graph_file);
    
}

/************************************************************************************************************************/
/* 		print head for a starter										*/
/*															*/
/************************************************************************************************************************/
void GraphOutput::print_starter_head(int index, char* sequence)
{
        fprintf(starters_file,"\"Starter_%d\":{\"data\":{\"id\":\"S%d\", \"sequence\":\"%s\", \"length\":%ld,\n", index,index, sequence, strlen(sequence));    
}


/************************************************************************************************************************/
/* 	recalculate length for a node (more efficient than capture length in fasta file (in string) 			*/
/*      and convert it in integer) 											*/
/*															*/
/************************************************************************************************************************/
long GraphOutput::sequence_length(string line)
{
	string  seq_char;
    int err,match,start, end;
    regex_t preg;
    long seq_len=0;
    size_t nmatch, size;
	const char *str_regex ="([A-Z]+)"; //regex capture sequences characters
  	const char *line_c =NULL;
    
	line_c = line.c_str();
  	err = regcomp (&preg, str_regex, REG_EXTENDED);
    
	if (err == 0)//security for error string snapshot and if regex match
   	{
		nmatch = 0;
        nmatch = preg.re_nsub;
		regmatch_t *pmatch=NULL;
        pmatch = (regmatch_t*) malloc (sizeof (*pmatch) * nmatch);
        if (pmatch)
        {
            match = regexec (&preg, line_c, nmatch, pmatch, 0);
            regfree (&preg);
            if (match == 0)
            {
                char *seq_char =NULL;
                start = pmatch[0].rm_so;
                end = pmatch[0].rm_eo;
                size = end - start;
                seq_len = sizeof(line_c[start])*(size);
				
            }
        }
    }
    else
    {
        fprintf (stderr, "LOW MEMORY !\n");
        exit (EXIT_FAILURE);
    }
    return  seq_len;
}


/************************************************************************************************************************/
/* 		output a single node to a file										*/
/*															*/
/************************************************************************************************************************/
//TODO: comment with xgmml format
void GraphOutput::print_node(long index, char *ascii_node) // output a single node to a file
{   
            fprintf(nodes_file," { \"data\": { \"id\":\"n%ld\", \"sequence\":\"%s\", \"length\":%ld}}\n",index,ascii_node, strlen(ascii_node));
}

/************************************************************************************************************************/
/* 		write mark for end of nodes list 									*/
/*															*/
/************************************************************************************************************************/
void GraphOutput::print_starter_end() // output a single node to a file
{	
        fprintf(nodes_file,"startNewStarter\n");	
}


/************************************************************************************************************************/
/* 		output a single edges to a file										*/
/*															*/
/************************************************************************************************************************/
//TODO: comment with xgmml format
void GraphOutput::print_edge(long index, long id, long id2, string label,string comment)
{
               fprintf(edges_file,"{ \"data\":{ \"id\": \"e%ld\", \"source\": \"n%ld\",\"target\": \"n%ld\",\"direction\": \"%s\"%s}}\n",index, id,id2,label.c_str(), comment.c_str());
   
}


/************************************************************************************************************************/
/* 		load nodes extremities											*/
/*															*/
/************************************************************************************************************************/
void GraphOutput::load_nodes_extremities(string linear_seqs_name)
{
    kmer_links.clear(); // PIERRE: reset previous stored kmer links
    
    Bank *Nodes = new Bank((char *)linear_seqs_name.c_str());
    long nb_nodes = first_id_els.node; //PIERRE;
    char * rseq;
    int readlen;
    
    sizeKmer--; // nodes extremities overlap by (k-1)-mers, so let's extract (k-1)-mers
    
    while (Nodes->get_next_seq(&rseq,&readlen))
    {
        kmer_type left_kmer, right_kmer, left_kmer_fw, left_kmer_rc, right_kmer_fw, right_kmer_rc;
        left_kmer = extractKmerFromRead(rseq,0,&left_kmer_fw,&left_kmer_rc, false);
        right_kmer = extractKmerFromRead(rseq,readlen-sizeKmer,&right_kmer_fw,&right_kmer_rc, false);
        Strand left_strand = (left_kmer == left_kmer_fw)?FW:RC;
        Strand right_strand = (right_kmer == right_kmer_fw)?FW:RC;
        
        kmer_links[left_kmer].insert(node_strand(nb_nodes, left_strand, LEFT));
        kmer_links[right_kmer].insert(node_strand(nb_nodes, right_strand, RIGHT));
        
        nb_nodes++;
    }
    
    Nodes->close();
    delete Nodes;
    
    sizeKmer++; // make sure to restore k
}


/************************************************************************************************************************/
/* 		construct node file and edge file for graph file							*/
/*															*/
/************************************************************************************************************************/
id_els GraphOutput::construct_graph(string linear_seqs_name, const string direction) // PIERRE: i need to know the last nb_nodes
{
    Bank *Nodes = new Bank((char *)linear_seqs_name.c_str());
    id_els nb_els = first_id_els; //Alexan: stucture for print id elements in graph output
    char * rseq;
    int readlen;
    bool end = true;
    Nodes->rewind_all();
	
    string kmerSequence =  linear_seqs_name;
    kmerSequence.erase(0, 13);
    strtok( (char*) kmerSequence.c_str(), "_");
//    char* direction =strtok( NULL, "_");
    fprintf(nodes_file, "\"sequence\":\"%s\", \"direction\":\"%s\", \"type\":\"%s\",\n ",  kmerSequence.c_str(),direction.c_str(), original?"original":"degenerated");
    
    sizeKmer--; // nodes extremities overlap by (k-1)-mers, so let's extract (k-1)-mers
    // for each node, output all the out-edges (in-edges will correspond to out-edges of neighbors)
    /* int nb_seq =0;
     string s;
     while (getline(file, s))
     nb_seq++;*/
	
    while(Nodes->get_next_seq(&rseq,&readlen)){
        end = false;
        kmer_type left_kmer, right_kmer, left_kmer_fw, left_kmer_rc, right_kmer_fw, right_kmer_rc;
        set<node_strand>::iterator it;
        left_kmer = extractKmerFromRead(rseq,0,&left_kmer_fw,&left_kmer_rc, false);
        right_kmer = extractKmerFromRead(rseq,readlen-sizeKmer,&right_kmer_fw,&right_kmer_rc, false);
        Strand left_strand = (left_kmer == left_kmer_fw)?FW:RC;
        Strand right_strand = (right_kmer == right_kmer_fw)?FW:RC;
        
        
        // left edges (are revcomp extensions)
        for (it = kmer_links[left_kmer].begin(); it != kmer_links[left_kmer].end(); it++)
        {
            long cur_node = it->node;
            Strand cur_strand = it->strand;
            LeftOrRight cur_left_or_right = it->left_or_right;
            
            
            
            if (cur_node == nb_els.node) // prevent self loops on same kmer
                if (readlen == sizeKmer)
                    continue;
            
            string label = "R";
            
            if (cur_left_or_right == LEFT)
            {
                if (cur_strand != left_strand)
                    label+=(string)"F";
                else
                    continue;
            }
            else
            {
                if (cur_strand == left_strand)
                    label+=(string)"R";
                else
                    continue;
            }
            
            
            print_edge(nb_els.edge, nb_els.node,cur_node,label,"");
            nb_els.edge++; 
            
        }
        
        // right edges
        for (it = kmer_links[right_kmer].begin(); it != kmer_links[right_kmer].end(); it++)
        {
            
            long cur_node = it->node;
            Strand cur_strand = it->strand;
            LeftOrRight cur_left_or_right = it->left_or_right;
            
            if (cur_node == nb_els.node) // prevent self loops on same kmer
                if (readlen == sizeKmer)
                    continue;
            
            string label = "F";
            
            if (cur_left_or_right == LEFT)
            {
                if (cur_strand == right_strand)
                    label+=(string)"F";
                else
                    continue;
            }
            else
            {
                if (cur_strand != right_strand)
                    label+=(string)"R";
                else
                    continue;
            }
            
            print_edge(nb_els.edge, nb_els.node,cur_node,label,"");
            nb_els.edge++; //ALEXAN
            
        }
        
   
            print_node(nb_els.node, rseq);
        	nb_els.node++;

        end = true ;
    }
    if(end){
     	fprintf(edges_file, "endOfKmer\n");
    	fprintf(nodes_file, "endOfKmer\n");
    };
    
    sizeKmer++; // make sure to restore k
    Nodes->close();
    delete Nodes;
    return nb_els; // PIERRE
}


