package org.biomart.processors.formatters;

import org.apache.commons.lang.StringEscapeUtils;
import java.io.IOException;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.biomart.common.resources.Log;

/**
 *
 * @author jbaran
 */
public class RDF extends TSVFormatter {

    public enum FORMAT { N3 , RDFXML};

    private FORMAT format;
    
    protected boolean exception = false;

    private boolean[] visibleColumn;
    protected String[] variableNames;
    protected String[] variableProperties;
    protected String[] variableTypes;
    protected List<String> columnValues = new LinkedList<String>();

    private int variableNo;

    public RDF(OutputStream out,
            String[] variableNames,
            String[] variableProperties,
            String[] variableTypes,
            Map<String, String> variable2URIAttributes,
            Map<String, String> namespaces,
            String prelude,
            String exception,
            FORMAT format) {
        super(out);
        
        this.format = format;
        
        try {

            if (exception != null) {
                out.write(("<exception>" + exception + "</exception>").getBytes());
                this.exception = true;
                return;
            }

            this.variableNames = variableNames;
            this.variableProperties = variableProperties;
            this.variableTypes = variableTypes;
            this.visibleColumn = new boolean[variableNames.length];

            for (int i = 0; i < variableNames.length; i++) {
                String variableName = variableNames[i];

                if (!variableName.startsWith("?") &&  // Un-projected query variables
                    !variableName.startsWith("!")) {  // Virtual variables for filters
                    visibleColumn[i] = true;
                }
            }

            switch(format) {
            case N3:
                for (String prefix : namespaces.keySet())
                    out.write(("@prefix " + prefix + " <" + namespaces.get(prefix) + ">\n").getBytes());
                break;
            case RDFXML:
                out.write("<?xml version=\"1.0\"?>\n".getBytes());
                out.write("<rdf:RDF\n xmlns:rdfs=\"http://www.w3.org/2000/01/rdf-schema#\"xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n".getBytes());
                for (String prefix : namespaces.keySet())
                    out.write((" xmlns:" + prefix + "=" + namespaces.get(prefix) + "\n").getBytes());
                out.write(" >\n".getBytes());
                break;
            default:
                break;
            }
        } catch (IOException e) {
            Log.error("IOException in RDF formatter constructor.", e);
        }
    }

    @Override
    public String transposeCharacter(int b) {
        if (exception) return "";

        return StringEscapeUtils.escapeHtml(Character.toString((char)b));
    }

    @Override
    public void writeStartOfLine() throws IOException {
    }

    @Override
    public void writeEndOfLine() throws IOException {
        switch(format) {
        case N3:
            writeN3();
            break;
        default:
            break;
        }
    }
    
    @Override
    public void writeField(int column, String value) throws IOException {
        switch(format) {
        case N3:
            columnValues.add(value);
            break;
        case RDFXML: // write out each property in its own element
            if (visibleColumn[column])
            {
                // write the opening tag
                // TODO: seems like we only support blank nodes in results right now                
                out.write("<rdf:Description rdf:ID=\"\n".getBytes());
                for (int i = 0; i < variableNames.length; i++) {
                    String variableName = variableNames[i].replaceFirst("\\?", "");
                    if (variableTypes[variableNo].equals(variableName))
                        // write the blank node ID
                        out.write( columnValues.get(i).getBytes());
                }
                out.write("\"><".getBytes());
                // write the predicate
                // FIXME: unless this is always prefixed, will need to split it into base and localname and write <localname xmlns="base" instead of this
                out.write(variableProperties[variableNo].getBytes());
                out.write(">".getBytes());
                // write out the actual value as a literal
                // FIXME: not clear how the N3 version is able to distinguish between URIs, plain literals, typed literals and language literals here
                out.write(value.getBytes());
                // write out the end of the property element
                out.write("</".getBytes());
                out.write(variableProperties[variableNo].getBytes());
                out.write(">".getBytes());
                // write out the end of the line
                out.write("</rdf:Description>\n".getBytes());
                variableNo++;
            }
            break;
        default:
            break;
        }
    }
    
    @Override
    public void close() throws IOException {
        if (exception) return;
        
        switch(format) {
        case RDFXML:
            out.write("</rdf:RDF>".getBytes());
            break;
        default:
            break;
        }

        super.close();
    }
    
    private void writeN3() throws IOException {
        variableNo = 0;
        
        for (int column = 0; column < visibleColumn.length; column++) {
            if (!visibleColumn[column])
                continue;
            
            String nodeURI = "";
            for (int i = 0; i < variableNames.length; i++) {
                String variableName = variableNames[i].replaceFirst("\\?", "");
                if (variableTypes[variableNo].equals(variableName))
                    nodeURI = "_:" + columnValues.get(i);
            }
            
            out.write(nodeURI.getBytes());
            out.write("\t".getBytes());
            out.write(variableProperties[variableNo].getBytes());
            out.write("\t\"".getBytes());
            out.write(columnValues.get(column).getBytes());
            out.write("\"\n".getBytes());
            
            variableNo++;
        }
        
        columnValues.clear();
    }
    
}
