#include "GraphOutput.h"

const string GraphOutput::graph_file_suffix = ".graph";
const string GraphOutput::nodes_file_suffix = ".nodes";
const string GraphOutput::edges_file_suffix = ".edges";
const string GraphOutput::substarters_file_suffix = ".substarters";
const string GraphOutput::starters_file_suffix = ".starters";
const string GraphOutput::xml_file_suffix = ".xgmml";
const string GraphOutput::json_nodes_file_suffix = ".json_nodes";
const string GraphOutput::json_edges_file_suffix = ".json_edges";
const string GraphOutput::json_substarters_file_suffix = ".json_substarters";
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){
    //printf("create a graph erase=%s graph_format=%d  first_id_nodes=%d first_id_edges=%d\n"", erase?"true":"false", graph_format, first_id_els.node, first_id_els.edge);
    graph_file_name=(prefix+graph_file_suffix);
    nodes_file_name=(prefix+nodes_file_suffix);
    edges_file_name=(prefix+edges_file_suffix);
    substarters_file_name=(prefix+substarters_file_suffix);
    starters_file_name=(prefix+starters_file_suffix);
    xml_file_name=(prefix+xml_file_suffix);
    json_nodes_file_name=(prefix+json_nodes_file_suffix);
    json_edges_file_name=(prefix+json_edges_file_suffix);
    json_substarters_file_name=(prefix+json_substarters_file_suffix);
    json_starters_file_name=(prefix+json_starters_file_suffix);
    json_file_name=(prefix+json_file_suffix);
    
    switch (graph_format){
        case 0:// FORMAT .GRAPH
            graph_file = fopen(graph_file_name.c_str(),erase?"w":"a");
            fprintf(graph_file,"digraph dedebruijn {\n");
            break;
            
        case 1: // FORMAT .NODES AND .EDGES
            nodes_file = fopen(nodes_file_name.c_str(),erase?"w":"a");
            edges_file = fopen(edges_file_name.c_str(),erase?"w":"a");
            break;
            
        case 2 :// FORMAT .XGMML
            graph_file = fopen(xml_file_name.c_str(),erase?"w":"a");
            break;
            
        case 3: // FORMAT .json
            nodes_file = fopen(json_nodes_file_name.c_str(),erase?"w":"a");
            edges_file = fopen(json_edges_file_name.c_str(),erase?"w":"a");
            substarters_file = fopen(json_substarters_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");
            break;
    }
}

/************************************************************************************************************************/
/* 		printf GraphOutput and initialize files (files are not erasing)						*/
/*															*/
/************************************************************************************************************************/
GraphOutput::GraphOutput(string prefix, int graph_format, id_els first_id_els)  : prefix(prefix), graph_format(graph_format), first_id_els(first_id_els)
{ // PIERRE: need something different than 0 for the first node
    printf("graph_format=%d first_id_nodes=%lu first_id_edges=%d\n", graph_format, first_id_els.node, first_id_els.edge);
    init(false);
}

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

/************************************************************************************************************************/
/* 		write graph file or sequence file									*/
/*															*/
/************************************************************************************************************************/
void GraphOutput::close()
{
    switch (graph_format){
            
        case 0:
            fprintf(graph_file,"}\n");
            fclose(graph_file);
            break;
            
        case 1:
            fclose(nodes_file);
            fclose(edges_file);
            break;
            
        case 2:
            fprintf(graph_file,"</graph>\n");
            fclose(graph_file);
            break;
            
        case 3:
            // We need to store all nodes and then all edges in the final .json file
            fclose(nodes_file);
            fclose(edges_file);
            fclose(substarters_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 substarters(json_substarters_file_name.c_str(), ios::in);
            ifstream starters(json_starters_file_name.c_str(), ios::in);
            
            if(!edges || !nodes || !substarters | !starters){fprintf(stderr,"Cannot open file %s, %s, %s or %s, exit\n", json_edges_file_suffix.c_str(), json_nodes_file_suffix.c_str(), json_substarters_file_suffix.c_str(), json_starters_file_suffix.c_str()); exit(1);}
            
            string line;
//            long count_nodes=0;
//            long count_edges=0;
//            long cumul =0;
//            long edges_per_extension = 0;
            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,"\"substarters\": [");
                getline(substarters,line);
                bool at_least_one_sub=false;
                if(continue_starter_write(line)){ // if this substater is not empty
                    at_least_one_sub=true;
                    fprintf(graph_file,"\n%s",line.c_str());
                    while(getline(substarters,line)) {
                        if (continue_starter_write(line)){
                            fprintf(graph_file,",\n%s",line.c_str());
                        }
                        else break; // end of this substarter
                    }
                }
                
                if(!at_least_one_sub) {
                    fprintf(graph_file,"\n]\n"); // there was no substarter
                    getline(nodes,line); // we read a gost line containing startNewStarter
                }
                else { // there was at least one substarter
                    fprintf(graph_file,"\n],\n");
                    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) && continue_starter_write(line)){
                        if(!first_data_xtrem_graph) fprintf(graph_file,",\n"); // separates data_xtrem_graph
                        first_data_xtrem_graph = false;
                        //getline(nodes,line);
                        //fprintf(graph_file,",\n");
                        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 and
//                        count_nodes++;
//                        cumul += sequence_length(line);
                        //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 (read_continue(line)){ // prints the other nodes
                                fprintf(graph_file,",\n\t%s",line.c_str());
//                                count_nodes++;
//                                cumul += sequence_length(line);
                            }
                            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);
//                                edges_per_extension=0;
                                if (read_continue(line)){// test of end
                                    fprintf(graph_file,"\n\t%s",line.c_str()); // prints the first edge without comma before
//                                    count_edges++;
//                                    edges_per_extension++;
                                }
                                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 (read_continue(line)){// test of end
                                        fprintf(graph_file,",\n\t%s",line.c_str()); // print other edges
//                                        count_edges++;
//                                        edges_per_extension++;
                                    }
                                    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 substarter
                
         
//                if(edges_per_extension>0){
//                    fseek(graph_file, -2, SEEK_CUR);
//                    fprintf(graph_file,"\n]\n},\n"); // ] closes the last extremGraph
//                    
//                }
                //end of graph file en close file
                fprintf(graph_file,"\n}"); // end of a starter
            } // end all starters
            
//            if(edges_per_extension>0){
//                fseek(graph_file, -13, SEEK_CUR);
//                fprintf(graph_file,"}}\n]\n}}\n]\n}\n}");
//                
//            }else{
//                fseek(graph_file, -4, SEEK_CUR);
//                fprintf(graph_file,"}}\n]\n}\n}");
//            }

            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());
            substarters.close();//remove(json_substarters_file_name.c_str());
            starters.close();//remove(json_starters_file_name.c_str());
            fclose(graph_file);
            break;
    }
}

/************************************************************************************************************************/
/* 		print head for a starter										*/
/*															*/
/************************************************************************************************************************/
void GraphOutput::print_starter_head(int index, char* sequence)
{
	if(graph_format == 2){
        /*fprintf(graph_file,"<?xml version=\"1.0\"?>\n<!DOCTYPE graph SYSTEM \"http://www.cs.rpi.edu/~puninj/XGMML/xgmml.dtd\">\n<graph directed=\"1\">\n");
         fprintf(graph_file,"<graph label=\"%s\" starterID=%d sequence=%s\n", (char *)prefix.c_str(), index, sequence);
         fprintf(graph_file,"xmlns:dc=\"http://purl.org/dc/elements/1.1/\" \n");
         fprintf(graph_file,"xmlns:xlink=\"http://www.w3.org/1999/xlink\" \n");
         fprintf(graph_file,"xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" \n");
         fprintf(graph_file,"xmlns:cy=\"http://www.cytoscape.org\" \n");
         fprintf(graph_file,"xmlns=\"http://www.cs.rpi.edu/XGMML\"  \n");
         fprintf(graph_file,"directed=\"1\">\n");*/
        
    }else if(graph_format == 3) // json format
        fprintf(starters_file,"\"Starter_%d\":{\"id\":\"S%d\", \"sequence\":\"%s\",\n", index,index, sequence);
    
}
/************************************************************************************************************************/
/* 		test if get an other kmer										*/
/*															*/
/************************************************************************************************************************/
int GraphOutput::read_continue(string line) //test if it's the end of nodes or edges for each substarters
{
    int err,match;
    regex_t preg;
    const char *line_c =NULL;
    const char *str_regex = "endOfKmer";
    
    line_c = line.c_str();
    err = regcomp (&preg, str_regex, REG_NOSUB | REG_EXTENDED);
    if (err == 0)
    {
        match = regexec (&preg, line_c, 0, NULL, 0);
        regfree (&preg);
    }
    return match;
}

/************************************************************************************************************************/
/* 		test if get an other kmer										*/
/*															*/
/************************************************************************************************************************/
int GraphOutput::continue_starter_write(string line) //test if it's the end of nodes or edges for each substarters
{
    int err,match;
    regex_t preg;
    const char *line_c =NULL;
    const char *str_regex = "startNewStarter";
    
    line_c = line.c_str();
    err = regcomp (&preg, str_regex, REG_NOSUB | REG_EXTENDED);
    if (err == 0)
    {
        match = regexec (&preg, line_c, 0, NULL, 0);
        regfree (&preg);
    }
    return match;
}

/************************************************************************************************************************/
/* 	recalculate length for a node (more efficient than capture length in string and convert the 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 substarter to a file										*/
/*															*/
/************************************************************************************************************************/
void GraphOutput::print_substarter(long index, char *ascii_node, string comment) // output a single node to a file
{
	if(graph_format==2){
        
    }else if(graph_format==3){
        fprintf(substarters_file," { \"data\": { \"id\":\"s%ld\", \"sequence\":\"%s\", %s}}\n",index,ascii_node, comment.c_str());
  	}
}

/************************************************************************************************************************/
/* 		output a single substarter to a file										*/
/*															*/
/************************************************************************************************************************/
void GraphOutput::print_substarters_end() // output a single node to a file
{
	if(graph_format==2){
        
    }else if(graph_format==3){
        fprintf(substarters_file,"startNewStarter\n");
  	}
}


/************************************************************************************************************************/
/* 		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
{
    switch (graph_format){
        case 0: // DOT format
            fprintf(graph_file,"%ld [label=\"%s\"];\n",index,ascii_node);
            break;
            
        case 1: // kissplice format
            fprintf(nodes_file,"%ld\t%s\n",index,ascii_node);
            break;
            
        case 2: // XGMML format
            fprintf(graph_file,"<node id=\"%ld\" label=\"%s\">\n</node>\n",index,ascii_node);
            break;
            
        case 3: // json format
            fprintf(nodes_file," { \"data\": { \"id\":\"n%ld\", \"sequence\":\"%s\"}}\n",index,ascii_node);
            break;
    }
}

/************************************************************************************************************************/
/* 		output a single substarter to a file										*/
/*															*/
/************************************************************************************************************************/
void GraphOutput::print_nodes_end() // output a single node to a file
{
	if(graph_format==2){
        
    }else if(graph_format==3){
        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)
{
    switch (graph_format){
        case 0: // DOT format
            fprintf(graph_file,"%ld -> %ld [label=\"%s\"];\n",id,id2,label.c_str());
            break;
            
        case 1: // kissplice format
            fprintf(edges_file,"%ld\t%ld\t%s\n",id,id2,label.c_str());
            break;
            
        case 2: // XGMML format
            fprintf(graph_file,"<edge source=\"%ld\" target=\"%ld\" label=\"%s\">\n</edge>\n",id,id2,label.c_str());
            break;
            
        case 3: // json format
            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());
            break;
    }
}

/************************************************************************************************************************/
/* 		output a single substarter to a file										*/
/*															*/
/************************************************************************************************************************/
void GraphOutput::print_edges_end() // output a single node to a file
{
	if(graph_format==2){
        
    }else if(graph_format==3){
        fprintf(edges_file,"startNewStarter\n");
  	}
}

/************************************************************************************************************************/
/* 		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
}

/************************************************************************************************************************/
/* 		make string for nodes data (length, depth, direction)							*/
/*															*/
/************************************************************************************************************************/
//TODO: debug mode
//TODO: XGMML data node
string  GraphOutput::data_regex(char* header)
{
	//bool debug = verb>=2 ? 1 : 0;
	char data[200];
    int err,match,start, end;
    regex_t preg;
    size_t nmatch, size;
	const char *str_regex ="len__([0-9]+)__depth__([0-9]+)__direction__(LEFT|RIGHT)"; //regex capture sequences characters
  	const char *str_request = header;
    
  	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, str_request, nmatch, pmatch, 0);
            regfree (&preg);
            if (match == 0)
            {
                
                int start_len = pmatch[1].rm_so;
                int end_len = pmatch[1].rm_eo;
				int start_depth = pmatch[2].rm_so;
                int end_depth = pmatch[2].rm_eo;
                size_t size_len = end_len - start_len;
				size_t size_depth =end_depth - start_depth;
                
                char * len = (char*) malloc (sizeof (*len) * (size_len + 1));
				char * depth = (char*) malloc (sizeof (*depth) * (size_depth + 1));
				
                if (len && depth)
                {
					strncpy (len, &str_request[start_len], size_len);
                    strncpy (depth, &str_request[start_depth], size_depth);
                    len[size_len] = '\0';
					depth[size_depth] = '\0';
  					sprintf (data, "\"length\":%s, \"depth\":%s", len, depth);
					//printf ("Extension data info: length=%s, depth=%s, direction=%s \n", len, depth, direction);
					
                }
            }
            
        }
    }
    else
    {
        fprintf (stderr, "LOW MEMORY !\n");
        exit (EXIT_FAILURE);
    }
    return  string(data);
}







/************************************************************************************************************************/
/* 		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;
    char *header;
    int hlen;
    bool end = true;
    Nodes->rewind_all();
    //nb_els.node = first_id_els.node;
    //nb_els.edge = first_id_els.edge;
	
    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\", \"firstNodeId\":\"n%d\",\n ",  kmerSequence.c_str(),direction.c_str(),nb_els.node);
    
    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++;*/
	
    //for(int i=0; i<nb_seq-1; i++){
    while(Nodes->get_next_seq(&rseq,&header,&readlen,&hlen)){
        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)
        //int nb_left_edges = 0;
        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_left_edges++; 
            nb_els.edge++; 
            
        }
        
        // right edges
        //int nb_right_edges = 0;
        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_right_edges++;
            nb_els.edge++; //ALEXAN
            
        }
        
        //nb_els.edge += nb_left_edges + nb_right_edges;
        
        
        //nodes
        //printf("%s\n",linear_seqs_name.c_str());
//        string data = data_regex(header);
//        string dataEnd ("endOfJson");
//        if(dataEnd.compare(data) != 0){ 
            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
}


