/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.blur.trace.hdfs;

import static org.apache.blur.utils.BlurConstants.BLUR_HDFS_TRACE_PATH;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import org.apache.blur.BlurConfiguration;
import org.apache.blur.log.Log;
import org.apache.blur.log.LogFactory;
import org.apache.blur.trace.Trace.TraceId;
import org.apache.blur.trace.TraceCollector;
import org.apache.blur.trace.TraceStorage;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.json.JSONException;
import org.json.JSONObject;

public class HdfsTraceStorage extends TraceStorage {

  private final static Log LOG = LogFactory.getLog(HdfsTraceStorage.class);

  private Path _storePath;
  private BlockingQueue<TraceCollector> _queue = new LinkedBlockingQueue<TraceCollector>();
  private Thread _daemon;
  private FileSystem _fileSystem;

  public HdfsTraceStorage(BlurConfiguration configuration) throws IOException {
    super(configuration);
  }

  public void init(Configuration conf) throws IOException {
    _storePath = new Path(_configuration.get(BLUR_HDFS_TRACE_PATH));
    _fileSystem = _storePath.getFileSystem(conf);
    _fileSystem.mkdirs(_storePath);
    _daemon = new Thread(new Runnable() {
      @Override
      public void run() {
        Random random = new Random();
        while (true) {
          TraceCollector collector;
          try {
            collector = _queue.take();
          } catch (InterruptedException e) {
            return;
          }
          try {
            writeCollector(collector, random);
          } catch (Throwable t) {
            LOG.error("Unknown error while trying to write collector.", t);
          }
        }
      }
    });
    _daemon.setDaemon(true);
    _daemon.setName("ZooKeeper Trace Queue Writer");
    _daemon.start();
  }

  @Override
  public void store(TraceCollector collector) {
    try {
      _queue.put(collector);
    } catch (InterruptedException e) {
      LOG.error("Unknown error while trying to add collector on queue", e);
    }
  }

  private void writeCollector(TraceCollector collector, Random random) throws IOException {
    TraceId id = collector.getId();
    String storeId = id.getRootId();
    String requestId = id.getRequestId();
    if (requestId == null) {
      requestId = "";
    }
    Path tracePath = getTracePath(storeId);
    JSONObject jsonObject;
    try {
      jsonObject = collector.toJsonObject();
    } catch (JSONException e) {
      throw new IOException(e);
    }
    storeJson(new Path(tracePath, getRequestIdPathName(requestId, random)), jsonObject);
  }

  private String getRequestIdPathName(String requestId, Random random) {
    return requestId + "_" + random.nextLong();
  }

  public void storeJson(Path storePath, JSONObject jsonObject) throws IOException {
    FSDataOutputStream outputStream = _fileSystem.create(storePath, false);
    try {
      OutputStreamWriter writer = new OutputStreamWriter(outputStream);
      jsonObject.write(writer);
      writer.close();
    } catch (JSONException e) {
      throw new IOException(e);
    }
  }

  private Path getTracePath(String traceId) {
    return new Path(_storePath, traceId);
  }

  @Override
  public void close() throws IOException {
    _fileSystem.close();
  }

  @Override
  public List<String> getTraceIds() throws IOException {
    FileStatus[] listStatus = _fileSystem.listStatus(_storePath);
    List<String> traceIds = new ArrayList<String>();
    for (FileStatus status : listStatus) {
      traceIds.add(status.getPath().getName());
    }
    return traceIds;
  }

  @Override
  public List<String> getRequestIds(String traceId) throws IOException {
    List<String> requestIds = new ArrayList<String>();
    Path tracePath = getTracePath(traceId);
    FileStatus[] listStatus = _fileSystem.listStatus(tracePath);
    for (FileStatus status : listStatus) {
      String name = status.getPath().getName();
      int indexOf = name.lastIndexOf('_');
      if (indexOf > 0) {
        requestIds.add(name.substring(0, indexOf));
      }
    }
    return requestIds;
  }

  @Override
  public String getRequestContentsJson(String traceId, String requestId) throws IOException {
    Path path = findPath(traceId, requestId);
    FSDataInputStream inputStream = _fileSystem.open(path);
    try {
      return IOUtils.toString(inputStream);
    } finally {
      inputStream.close();
    }
  }

  private Path findPath(String traceId, String requestId) throws IOException {
    Path tracePath = getTracePath(traceId);
    if (!_fileSystem.exists(tracePath)) {
      throw new IOException("Trace [" + traceId + "] not found.");
    }
    FileStatus[] listStatus = _fileSystem.listStatus(tracePath);
    for (FileStatus status : listStatus) {
      Path path = status.getPath();
      String name = path.getName();
      int indexOf = name.lastIndexOf('_');
      if (indexOf > 0) {
        if (name.substring(0, indexOf).equals(requestId)) {
          return path;
        }
      }
    }
    throw new IOException("Request [" + requestId + "] not found.");
  }

  @Override
  public void removeTrace(String traceId) throws IOException {
    Path storePath = getTracePath(traceId);
    _fileSystem.delete(storePath, true);
  }

}