package com.avaje.ebeaninternal.server.type;

import com.avaje.ebean.text.TextException;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.SQLException;
import java.sql.Types;

/**
 * ScalarType for streaming between a File and the database.
 */
public class ScalarTypeFile extends ScalarTypeBase<File> {

  private static final Logger logger = LoggerFactory.getLogger(ScalarTypeFile.class);

  private final String prefix;

  private final String suffix;

  private final File directory;

  private final int bufferSize;

  /**
   * Construct with reasonable defaults of Blob and 8096 buffer size.
   */
  public ScalarTypeFile() {
    this(Types.LONGVARBINARY, "db-", null, null, 8096);
  }

  /**
   * Create the ScalarTypeFile.
   */
  public ScalarTypeFile(int jdbcType, String prefix, String suffix, File directory, int bufferSize) {
    super(File.class, false, jdbcType);
    this.prefix = prefix;
    this.suffix = suffix;
    this.directory = directory;
    this.bufferSize = bufferSize;
  }

  @Override
  public boolean isBinaryType() {
    return true;
  }

  private InputStream getInputStream(File value) throws IOException {
    FileInputStream fi = new FileInputStream(value);
    return new BufferedInputStream(fi, bufferSize);
  }

  private OutputStream getOutputStream(File value) throws IOException {
    FileOutputStream fi = new FileOutputStream(value);
    return new BufferedOutputStream(fi, bufferSize);
  }

  @Override
  public File read(DataReader dataReader) throws SQLException {

    InputStream is = dataReader.getBinaryStream();
    if (is == null) {
      return null;
    }

    try {
      // stream from db into our temp file
      File tempFile = File.createTempFile(prefix, suffix, directory);
      OutputStream os = getOutputStream(tempFile);
      pump(is, os);

      return tempFile;

    } catch (IOException e) {
      throw new SQLException("Error reading db file inputStream", e);
    }
  }


  @Override
  public void bind(DataBind b, File value) throws SQLException {
    if (value == null) {
      b.setNull(jdbcType);
    } else {
      try {
        // stream from our file to the db
        InputStream fi = getInputStream(value);
        b.setBinaryStream(fi, value.length());
      } catch (IOException e) {
        throw new SQLException("Error trying to set file inputStream", e);
      }
    }
  }

  @Override
  public Object toJdbcType(Object value) {
    return value;
  }

  @Override
  public File toBeanType(Object value) {
    return (File) value;
  }

  @Override
  public void jsonWrite(JsonGenerator writer, File value) throws IOException {
    InputStream is = getInputStream(value);
    writer.writeBinary(is, (int) value.length());
  }

  @Override
  public File jsonRead(JsonParser parser) throws IOException {
    File tempFile = File.createTempFile(prefix, suffix, directory);
    OutputStream os = getOutputStream(tempFile);
    parser.readBinaryValue(os);
    os.flush();
    os.close();
    return tempFile;
  }

  @Override
  public DocPropertyType getDocType() {
    return DocPropertyType.BINARY;
  }

  @Override
  public String formatValue(File file) {
    throw new TextException("Not supported");
  }

  @Override
  public File parse(String value) {
    throw new TextException("Not supported");
  }

  @Override
  public File convertFromMillis(long systemTimeMillis) {
    throw new TextException("Not supported");
  }

  @Override
  public boolean isDateTimeCapable() {
    return false;
  }

  @Override
  public File readData(DataInput dataInput) throws IOException {
    // skip reading large file
    return null;
  }

  public void writeData(DataOutput dataOutput, File file) throws IOException {
    // skip writing large file
  }

  /**
   * Helper method to pump bytes from input to output.
   */
  public long pump(InputStream is, OutputStream out) throws IOException {

    long totalBytes = 0;
    InputStream input = null;
    OutputStream output = null;

    try {
      input = new BufferedInputStream(is, bufferSize);
      output = new BufferedOutputStream(out, bufferSize);

      byte[] buffer = new byte[bufferSize];
      int length;
      while (((length = input.read(buffer)) > 0)) {
        output.write(buffer, 0, length);
        totalBytes += length;
      }

      output.flush();

      return totalBytes;

    } finally {
      if (output != null) {
        try {
          output.close();
        } catch (IOException e) {
          logger.error("Error when closing outputstream", e);
        }
      }
      if (input != null) {
        try {
          input.close();
        } catch (IOException e) {
          logger.error("Error when closing inputstream ", e);
        }
      }
    }
  }

}
