/* Copyright 2014 The Johns Hopkins University Applied Physics Laboratory
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package edu.jhuapl.tinkerpop.tables.core;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import org.apache.accumulo.core.client.BatchScanner;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.client.Scanner;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.iterators.user.RegExFilter;
import org.apache.accumulo.core.util.PeekingIterator;
import org.apache.hadoop.io.Text;

import com.tinkerpop.blueprints.CloseableIterable;
import com.tinkerpop.blueprints.Edge;

import edu.jhuapl.tinkerpop.AccumuloByteSerializer;
import edu.jhuapl.tinkerpop.AccumuloEdge;
import edu.jhuapl.tinkerpop.AccumuloGraphException;
import edu.jhuapl.tinkerpop.AccumuloGraphUtils;
import edu.jhuapl.tinkerpop.AccumuloVertex;
import edu.jhuapl.tinkerpop.Constants;
import edu.jhuapl.tinkerpop.GlobalInstances;
import edu.jhuapl.tinkerpop.ScannerIterable;
import edu.jhuapl.tinkerpop.mutator.Mutators;
import edu.jhuapl.tinkerpop.mutator.edge.EdgeMutator;
import edu.jhuapl.tinkerpop.parser.EdgeParser;


/**
 * Wrapper around {@link Edge} tables.
 */
public class EdgeTableWrapper extends ElementTableWrapper {

  public EdgeTableWrapper(GlobalInstances globals) {
    super(globals, globals.getConfig().getEdgeTableName());
  }

  /**
   * Write the given edge to the edge table. Does not
   * currently write the edge's properties.
   * 
   * <p/>Note: This only adds the edge information. Vertex
   * endpoint information needs to be written to the vertex
   * table via {@link VertexTableWrapper}.
   * @param edge
   */
  public void writeEdge(Edge edge) {
    Mutators.apply(getWriter(), new EdgeMutator.Add(edge));
    globals.checkedFlush();
  }

  public void deleteEdge(Edge edge) {
    Mutators.apply(getWriter(), new EdgeMutator.Delete(edge));
    globals.checkedFlush();
  }

  public CloseableIterable<Edge> getEdges() {
    Scanner scan = getScanner();
    scan.fetchColumnFamily(new Text(Constants.LABEL));

    if (globals.getConfig().getPreloadedProperties() != null) {
      for (String key : globals.getConfig().getPreloadedProperties()) {
        scan.fetchColumnFamily(new Text(key));
      }
    }

    final EdgeParser parser = new EdgeParser(globals);

    return new ScannerIterable<Edge>(scan) {
      @Override
      public Edge next(PeekingIterator<Entry<Key, Value>> iterator) {
        // TODO could also check local cache before creating a new instance?

        String rowId = iterator.peek().getKey().getRow().toString();

        List<Entry<Key, Value>> entries =
            new ArrayList<Entry<Key, Value>>();

        // MDL 05 Jan 2014:  Why is this equalsIgnoreCase??
        while (iterator.peek() != null && rowId.equalsIgnoreCase(iterator
            .peek().getKey().getRow().toString())) {
          entries.add(iterator.next());
        }

        AccumuloEdge edge = parser.parse(rowId, entries);
        globals.getCaches().cache(edge, Edge.class);

        return edge;
      }
    };
  }

  public Iterable<Edge> getEdges(String key, Object value) {
    AccumuloGraphUtils.nullCheckProperty(key, value);
    if (key.equalsIgnoreCase("label")) {
      key = Constants.LABEL;
    }
    
    BatchScanner scan = getBatchScanner();
    scan.fetchColumnFamily(new Text(key));

    byte[] val = AccumuloByteSerializer.serialize(value);
    if (val[0] != AccumuloByteSerializer.SERIALIZABLE) {
      IteratorSetting is = new IteratorSetting(10, "filter", RegExFilter.class);
      RegExFilter.setRegexs(is, null, null, null, Pattern.quote(new String(val)), false);
      scan.addScanIterator(is);

      return new ScannerIterable<Edge>(scan) {

        @Override
        public Edge next(PeekingIterator<Entry<Key,Value>> iterator) {

          Key k = iterator.next().getKey();

          if (k.getColumnFamily().toString().equals(Constants.LABEL)) {
            String[] vals = k.getColumnQualifier().toString().split(Constants.ID_DELIM);
            return new AccumuloEdge(globals, k.getRow().toString(),
                new AccumuloVertex(globals, vals[0]),
                new AccumuloVertex(globals, vals[1]), null);
          }
          return new AccumuloEdge(globals, k.getRow().toString());
        }
      };
    } else {
      // TODO
      throw new UnsupportedOperationException("Filtering on binary data not currently supported.");
    }
  }

  public void loadEndpointsAndLabel(AccumuloEdge edge) {
    Scanner s = getScanner();

    try {
      s.setRange(new Range(edge.getId().toString()));
      s.fetchColumnFamily(new Text(Constants.LABEL));
      Iterator<Entry<Key,Value>> iter = s.iterator();
      if (!iter.hasNext()) {
        dump();
        throw new AccumuloGraphException("Unable to find edge row: "+edge);
      }

      Entry<Key, Value> entry = iter.next();

      String cq = entry.getKey().getColumnQualifier().toString();
      String[] ids = cq.split(Constants.ID_DELIM);

      String label = AccumuloByteSerializer.deserialize(entry.getValue().get());

      edge.setVertices(new AccumuloVertex(globals, ids[0]),
          new AccumuloVertex(globals, ids[1]));
      edge.setLabel(label);

    } finally {
      s.close();
    }
  }
}