#ifndef _WRITER_H_
#define _WRITER_H_

/**
 *    WHAM - high-throughput sequence aligner
 *    Copyright (C) 2011  WHAM Group, University of Wisconsin
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*	$Id: writer.h 157 2012-07-25 05:58:09Z yinan $ */

#include <stdio.h>
#include <assert.h>
#include "hitset.h"
#include "sequence.h"
#include "short.h"

#define SAM_FLAG_PAIRED		0x1
#define SAM_FLAG_MAPPED_PAIRED	0x2
#define SAM_FLAG_UNMAPPED	0x4
#define SAM_FLAG_MATE_UNMAPPED	0x8
#define SAM_FLAG_QUERY_STRAND	0x10
#define SAM_FLAG_MATE_STRAND	0x20
#define SAM_FLAG_FIRST_IN_PAIR	0x40
#define SAM_FLAG_SECOND_IN_PAIR	0x80
#define SAM_FLAG_NOT_PRIMARY	0x100
#define SAM_FLAG_FAILED		0x200
#define SAM_FLAG_DUPLICATE	0x400

class Writer {
public:
  Writer(CompactSequence * _sequence, ShortRead * _reader, char * fname) {
    sequence = _sequence;
    reader1 = _reader;
    reader2 = NULL;

    length = _reader->getReadLength();

    file = fopen(fname, "w");
    assert(file != NULL);
  }

  ~Writer() {
    fflush(file);
    fclose(file);
  }

  virtual void writeHead() {
  }

  virtual void writeAlignment(int readId, HitSet * set) = 0;

protected:
  char * reverseSequence(char * str2, char * str1);

protected:
  FILE * file;
  CompactSequence * sequence;
  ShortRead * reader1;
  ShortRead * reader2;
  int length;
};

/**
 * writer class for normal format output
 */
class SimpleWriter: public Writer {
public:
  SimpleWriter(CompactSequence * _sequence, ShortRead * _reader, char * fname) :
      Writer(_sequence, _reader, fname) {
  }

  void writeAlignment(int readId, HitSet * set) {
    if (set->getNumHits() > 0)
      writeValidAlignment(readId, set);
  }
protected:
  void writeField(int64 * query, int64 * reference, strand s);
  void writeField(char * str, char * query, char * reference, strand s,
      ErrorVector error);

private:
  void writeValidAlignment(int readId, HitSet * set);
};

class SimplePairWriter: public SimpleWriter {
public:
  SimplePairWriter(CompactSequence * _sequence, ShortRead * _reader1,
      ShortRead * _reader2, char * fname) :
      SimpleWriter(_sequence, _reader1, fname) {
    reader2 = _reader2;
  }

  void writeAlignment(int readId, HitSet * set) {
    if (set->getNumHits() > 0)
      writeValidAlignment(readId, set);
  }

private:
  void writeValidAlignment(int readId, HitSet * set);
};

/**
 * writer class for single-end SAM output
 */
class SamWriter: public Writer {
public:
  SamWriter(CompactSequence * _sequence, ShortRead * _reader, char * fname) :
      Writer(_sequence, _reader, fname) {
  }

  void writeHead();

  void writeAlignment(int readId, HitSet * set) {
    if (set->getNumHits() == 0)
      writeInvalidAlignment(readId, set);
    else
      writeValidAlignment(readId, set);
  }

  void writeOptionalField(char * query, char * reference, ErrorVector error);
  void getMDfield(char * str, char * query, char * reference,
      ErrorVector error);
  void getCIGAR(char * str, char * query, char * reference, ErrorVector error);

private:
  void writeValidAlignment(int readId, HitSet * set);
  void writeInvalidAlignment(int readId, HitSet * set);
};

/**
 * writer class for paired-end SAM output
 */
class SamPairWriter: public SamWriter {
public:
  SamPairWriter(CompactSequence * _sequence, ShortRead * _reader1,
      ShortRead * _reader2, char * fname) :
      SamWriter(_sequence, _reader1, fname) {
    reader2 = _reader2;
  }

  void writeAlignment(int readId, HitSet * set) {
    if (set->getNumHits() == 0)
      writeInvalidAlignment(readId, set);
    else
      writeValidAlignment(readId, set);
  }

private:
  void writeValidAlignment(int readId, HitSet * set);
  void writeInvalidAlignment(int readId, HitSet * set);
};

/**
 * writer class for raw format. Raw format is used for 
 * storeing the intermediate results in pipeline mode.
 */
class RawWriter: public Writer {
public:
  RawWriter(CompactSequence * _sequence, ShortRead * _reader, char * fname) :
      Writer(_sequence, _reader, fname) {
  }

  void writeAlignment(int readId, HitSet * set) {
    /* In RAW formate, we have to output all hits even the number of
     * hits exceeds -m <int>. Otherwise (discard all hits), the merged
     * results may be wrong.
     * we use getNumAllHits() instead of getNumHIts().
     */
    if (set->getNumAllHits() > 0)
      writeValidAlignment(readId, set);
  }

private:
  void writeValidAlignment(int readId, HitSet * set);
};

class RawReader {
public:
  RawReader() {
    file = NULL;
  }

  ~RawReader() {
    if (file != NULL
      )
      fclose(file);
  }

  bool init(char * fname, int _maxid) {
    maxid = _maxid;
    file = fopen(fname, "r");
    if (file == NULL
      )
      return false;
    return true;
  }

  void next(Hit * hit) {
    int ret = fread(hit, sizeof(Hit), 1, file);
    if (ret != 1)
      hit->id = maxid;
  }

private:
  FILE * file;
  int maxid;
};

#endif
