package org.intermine.webservice.server.clob;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.intermine.api.InterMineAPI;
import org.intermine.api.profile.InterMineBag;
import org.intermine.api.query.MainHelper;
import org.intermine.model.FastPathObject;
import org.intermine.objectstore.ObjectStoreException;
import org.intermine.objectstore.query.Query;
import org.intermine.pathquery.Path;
import org.intermine.pathquery.PathException;
import org.intermine.pathquery.PathQuery;
import org.intermine.webservice.server.core.JSONService;
import org.intermine.webservice.server.exceptions.BadRequestException;
import org.intermine.webservice.server.exceptions.InternalErrorException;
import org.intermine.webservice.server.exceptions.NotImplementedException;
import org.intermine.webservice.server.output.JSONFormatter;
import org.intermine.webservice.server.query.AbstractQueryService;
import org.intermine.webservice.server.query.QueryRequestParser;
import org.intermine.webservice.server.query.result.PathQueryBuilder;

/**
 * <p>A service to provide access to substrings of <code>ClobAccess</code> data. Ideally this
 * could be replaced by enhancing path queries to provide access to functions
 * such as <code>SUBSTR</code>.</p>
 * 
 * <p>This service expects the following parameters:</p>
 * <ul>
 *   <li><em>query</em>: A query with a single selected path, which must resolve to a string or clob.</li>
 *   <li><em>start</em>: The index of the first character of the string or clob to return.</li>
 *   <li><em>end</em>: The index of the first character not to return.</li>
 * </ul>
 * 
 * @author Alex Kalderimis
 *
 */
public class SequenceService extends JSONService {

    public SequenceService(InterMineAPI im) {
        super(im);
    }

    /**
     * Why is the results key "features", pray? and not something more sensible, like,
     * "results", say. Well, this it just seemed sensible to make this service
     * directly consumable by <em>jbrowse</em>, which is obviously the whole point of this
     * service. <em>Sigh</em>.
     */
    @Override
    protected Map<String, Object> getHeaderAttributes() {
        final Map<String, Object> attributes = new HashMap<String, Object>();
        attributes.putAll(super.getHeaderAttributes());
        if (formatIsJSON()) {
            attributes.put(JSONFormatter.KEY_INTRO, "\"features\":[");
            attributes.put(JSONFormatter.KEY_OUTRO, "]");
        }
        return attributes;
    }

    @Override
    protected void execute() {
        Integer start = getIntParameter("start", 0);
        Integer end = getIntParameter("end", null);
        PathQuery pq = getQuery();

        Iterator<CharSequence> sequences = getSequences(pq);
        while(sequences.hasNext()) {
            CharSequence chars = sequences.next();
            addResultItem(makeFeature(chars, start, end), sequences.hasNext());
        }
    }

    private Iterator<CharSequence> getSequences(final PathQuery pq) {
        validateQuery(pq);
        final Query q;
        try {
            q = MainHelper.makeQuery(pq, getListManager().getListMap(), new HashMap(), null, new HashMap());
        } catch (ObjectStoreException e) {
            throw new InternalErrorException(e);
        }

        final Iterator<Object> results = im.getObjectStore().executeSingleton(q).iterator();

        return new Iterator<CharSequence>() {

            @Override
            public boolean hasNext() {
                return results.hasNext();
            }

            @Override
            public CharSequence next() {
                FastPathObject obj = (FastPathObject) results.next();

                CharSequence chars;
                try {
                    chars = (CharSequence) obj.getFieldValue(pq.makePath(pq.getView().get(0)).getEndFieldDescriptor().getName());
                } catch (IllegalAccessException e) {
                    throw new InternalErrorException(e);
                } catch (PathException e) {
                    throw new InternalErrorException(e);
                }
                return chars;
            }

            @Override
            public void remove() {
                throw new NotImplementedException("remove is not a valid operation for this iterator.");
            }
        };

    }

    private void validateQuery(PathQuery pq) {
        List<String> view = pq.getView();
        if (view.size() != 1) {
            throw new BadRequestException("Expected only a single view column, got: " + view);
        }
        Path column;
        try {
            column = pq.makePath(view.get(0));
        } catch (PathException e) {
            throw new BadRequestException(e);
        }
        if (!CharSequence.class.isAssignableFrom(column.getEndType())) {
            throw new BadRequestException("Expected the column to provide a CharSequence value, got: " + column.getEndType());
        }
    }

    private Map<String, Object> makeFeature(CharSequence chars, Integer start, Integer end) {
        CharSequence subSequence;
        try {
            if (end == null) {
                subSequence = chars.subSequence(start, chars.length());
            } else {
                subSequence = chars.subSequence(start, end);
            }
        } catch (IndexOutOfBoundsException e) {
            throw new BadRequestException("Illegal start/end values: " + e.getMessage());
        }
        Map<String, Object> feat = new HashMap<String, Object>();
        feat.put("start", start);
        feat.put("end", subSequence.length() + start);
        feat.put("seq", subSequence);

        return feat;
    }

    private PathQuery getQuery() {
        String xml                     = new QueryRequestParser(im.getQueryStore(), request).getQueryXml();
        String schemaUrl               = AbstractQueryService.getSchemaLocation(request);
        Map<String, InterMineBag> bags = getListManager().getListMap();
        PathQueryBuilder builder       = new PathQueryBuilder(xml, schemaUrl, bags);

        return builder.getQuery();
    }

}

@@ -5639,21 +5639,89 @@
 q%22, 
-subSequence);
+String.valueOf(subSequence)); // Have to do this, otherwise json.org goes insane.
 %0A%0A  

We have to stringify the clobs. Grumble grumble json.org