// #include "ap_config.h"

#ifdef _WIN32
#include "windows.h"
#endif

#include <cstdio>
#include <cstdlib>
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>

#include "parse.h"
#include "xml_util.h"
#include "db_table.h"
#include "ap_schema.h"
// #include "astropulse.h"
#include "util.h"
#include "base64.h"
#include "ap_raw_data_db.h"
#undef read

#include <cstring>
#include <cassert>
#include <cstdlib>
#include <cstdio>
#include <cerrno>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

namespace Astropulse {

/* make_raw_data_string
 *
 * arguments:
 * std::string wufilename	should name a workunit file (ap_something.wu)
 * long peak_bin		The raw data segment will center on the peak bin
 * 				of an ap_signal, so we can see all around it.
 *
 * return value: 		16384 bytes of raw data, written in base 64 to form
 * 				a string of length 4/3 that many characters.  (The final
 * 				byte of raw data is omitted b/c divisibility by 3.)
 * 				(where "16384" should be replaced by NUM_SAMPLES_TO_COPY / 4)
 */
std::string make_raw_data_string(std::string wufilename, long peak_bin) {
  std::string outstring;
  FILE * wufile = fopen(wufilename.c_str(), "r");
  if(wufile == NULL) return std::string("");
  get_wu_header(wufile); // We could store the header here, but we choose not to.
  outstring = get_wu_raw_data(wufile, peak_bin);
  fclose(wufile);
  printf("outstring size is %d\n", outstring.size());
  return outstring; 
}

/* ap_signal_to_miniwufile
 *
 * Take an ap_signal with base64 raw data, turn it into bytes (base 256),
 * and output to a mini wu file (.mwu)
 * It does this by calling signal.print_xml(), which prints the header, as well
 * as the raw data in base64, then backing up and re-printing the data in bytes.
 *
 * arguments:
 * signal			an ap_signal object, from the science
 * 				database.
 * wufilename			this is not the name of the output file,
 * 				rather it will be used to generate
 * 				the name of the output file
 * no_peak_bin_in_name		1: don't attach peak bin to name
 * 				0: do attach peak bin to name
 * dir_prefix			attach prefix directory to wufilename
 *
 * return value:
 * 0 on success, nonzero on failure.
 */
int ap_signal_to_miniwufile(ap_signal& signal, std::string& wufilename, int no_peak_bin_in_name, std::string dir_prefix) {
  std::string outfilename;
  int peak_bin = signal.peak_bin;
  if(no_peak_bin_in_name) peak_bin = 0;
  outfilename = get_outfilename(wufilename, peak_bin);
  if(dir_prefix != "") outfilename = dir_prefix + "/" + outfilename;
  FILE * miniwufile = 
      fopen(outfilename.c_str(), "w");
  if(!miniwufile) {
    fprintf(stderr, "Can't open file %s\n", outfilename.c_str());
    return 1;
  }
  fprintf(miniwufile, "<wufilename>%s</wufilename>\n", wufilename.c_str());
  fprintf(miniwufile, "%s", signal.print_xml().c_str());
  // Now we need to back up until we find the raw_data tag, so we can 
  // re-print the data
  fclose(miniwufile);
  miniwufile = fopen(outfilename.c_str(), "r+");
    
  int type;
  std::string tag;
  std::string content;
  std::string whole_xml;

  bool found_raw_data = false;
  while(!get_xml(miniwufile, type, tag, content, whole_xml)) {
      if(tag == "raw_data") { // We'll leave the file pointer at the end of the <raw_data> tag
          found_raw_data = true;
          break;
      }
  }
  if(!found_raw_data) {
    printf("Error in ap_raw_data_db.cpp : ap_signal_to_miniwufile : didn't find raw data, this signal might not have any?<br>\n");
    return 1;
  }

  fprintf(stderr, "First 16 hex characters of raw_data:\n");
  for(int i = 0; i < 8; i++) {
    fprintf(stderr, "%x", (unsigned char) signal.raw_data.at(i) / 16);
    fprintf(stderr, "%x", (unsigned char) signal.raw_data.at(i) % 16);
    fprintf(stderr, " ");
  }
  printf("\n");

  std::string raw_data_string(signal.raw_data.begin(), signal.raw_data.end());
  // note: can use size()
  
  fprintf(stderr, "First 16 hex characters of raw_data_string:\n");
  for(int i = 0; i < 8; i++) {
    fprintf(stderr, "%x", (unsigned char) raw_data_string.at(i) / 16);
    fprintf(stderr, "%x", (unsigned char) raw_data_string.at(i) % 16);
    fprintf(stderr, " ");
  }
  printf("\n");

  fwrite(raw_data_string.c_str(), 1, raw_data_string.size(), miniwufile);

  fprintf(miniwufile, "</raw_data>\n</ap_signal>\n"); // end with \n?  nul 0?
  long trunclen = ftell(miniwufile);
  fclose(miniwufile);
  truncate(outfilename.c_str(), trunclen);
  return 0;
}

/* interpreting the return value of this function:
 * Anything other than a 0 is an error, and will result in an error string
 * being printed to stderr.
 */
int update_ap_signal_db_with_raw_data(long signalid, std::string raw_data) {
  // Do I have to set up a new connection, using 
  // DB_CONN::open from boinc/db/db_base.h? 
  std::stringstream id_stream;
  id_stream << signalid;
  std::string query = "update ap_signal set raw_data=\"" +
		    	raw_data + "\" where id = " + 
			id_stream.str() + ";";

  // Where does db come from? -> return db->do_query(query.c_str());
}

/* How do I fetch a row?
std::string fetch_raw_data_from_ap_signal(long signalid) {
  std::stringstream id_stream;
  id_stream << signalid;
  std::string query = "select raw_data from ap_signal where id = " +
			id_stream.str() + ";";
}
*/

/* get_outfilename
 *
 * Generates a name for a "mini workunit" .mwu file to store
 * a raw_data lvarchar that has been extracted from the database.
 */
std::string get_outfilename(std::string wufilename, int peak_bin) {
  std::stringstream peak_bin_stream;
  peak_bin_stream << peak_bin;
  return wufilename + "_" + peak_bin_stream.str() + ".mwu";
}

std::string get_wu_header(FILE * wufile) {
  fseek(wufile, 0, SEEK_SET);
  int type;
  std::string outstring = "";
  std::string tag;
  std::string content;
  std::string whole_xml;

  // do we need to add "\n" after each whole_xml?
  while(!get_xml(wufile, type, tag, content, whole_xml)) {
    if(tag == "data") // We'll leave the file pointer at the end of the <data> tag
      return outstring; 
    outstring += whole_xml;
  }

  fprintf(stderr, "Error in ap_raw_data_db.cpp : cp_header : didn't find data.\n");
  printf("Error in ap_raw_data_db.cpp : cp_header : didn't find data.\n");
  exit(-1);
}


std::string get_wu_raw_data(FILE * wufile, long peak_bin) {
  // Assume the file pos of wufile is exactly at the start of the data
  long start_byte = std::max(0L, (peak_bin - NUM_SAMPLES_TO_COPY / 2) / 4);
  // We don't want to start where we'll go off the end of the file.
  // The minus 8 is so I don't have to think about 
  // off-by-1 effects.
  long max_start = 8L * 1024L * 1024L - NUM_SAMPLES_TO_COPY/4 - 8;
  start_byte = std::min(start_byte, max_start);
  char ch;
  std::string outstring = "";
  // each sample is 2 bits, each character is 8 bits, so divide by 4
  outstring.reserve(NUM_SAMPLES_TO_COPY / 4 + 1);

  // use SEEK_CUR -- we have just read in the header
  fseek(wufile, start_byte, SEEK_CUR);
 
  // num bytes to copy = NUM_SAMPLES_TO_COPY / 4
  for(long i = 0; i < NUM_SAMPLES_TO_COPY / 4; i++) {
    ch = getc_err_on_EOF(wufile);
    if(i < 12) {
      printf("char %d: %x%x\n", i, (unsigned char) ch / 16, 
				   (unsigned char) ch % 16);
    }
    outstring += ch;
  }
  return outstring;
}

#if 0
This code was used to get an encoded string in base64.
Now, we no longer encode strings, so a different version of this function is used.
// returns a string of length (NUM_SAMPLES_TO_COPY / 4 / 3) * 4
std::string get_wu_raw_data(FILE * wufile, long peak_bin) {
  // Assume the file pos of wufile is exactly at the start of the data
  long start_byte = std::max(0L, (peak_bin - NUM_SAMPLES_TO_COPY / 2) / 4);
  // We don't want to start where we'll go off the end of the file.
  // The minus 8 is so I don't have to think about 
  // off-by-1 effects.
  long max_start = 8 * 1024 * 1024 - NUM_SAMPLES_TO_COPY/4 - 8;
  start_byte = std::min(start_byte, max_start);
  char ch;
  char from[3];
  std::string outstring = "";
  // each sample is 2 bits, each base64 character is 6 bits, so divide by 3
  outstring.reserve(NUM_SAMPLES_TO_COPY / 3 + 1);

  // use SEEK_CUR -- we have just read in the header
  fseek(wufile, start_byte, SEEK_CUR);
  
  // num bytes to copy = NUM_SAMPLES_TO_COPY / 4
  // num 3-byte base64 groups to copy = num bytes to copy / 3
  for(long i = 0; i < NUM_SAMPLES_TO_COPY / 4; i++) {
    ch = getc_err_on_EOF(wufile);
    if(i < 12) {
      printf("char %d: %x%x\n", i, (unsigned char) ch / 16, 
				   (unsigned char) ch % 16);
    }
    from[i % 3] = ch;
    // encode a 4-byte output string ever 3 bytes.
    // The final two bytes of the 65536 bytes will be omitted, 
    // as they have i % 3 = 0 and 1.
    if(i % 3 == 2) {
      outstring += r_base64_encode(from, 3);
      if(i < 12)
	printf("outstring: %s\n", outstring.c_str());
    }
  }

  return outstring;
}
#endif

/* get_xml
 *
 * purpose of this function is to read an xml tag, or content between two tags,
 * and advance the filepos so we can read the next tag, but no farther.
 *
 * arguments:
 * FILE * file : we read an xml tag or content from this file
 * int& type : returns the type of object we just read.
 *		0 = start tag
 *		1 = end tag
 *		2 = content
 * std::string& tag : the tag name. <data type="text"> produces 'data'
 * 		 "NO_TAG" means content only, or something like <>
 * std::string& content : the content. <data type="text"> would produce
 * 		'type="text"'.  If we're reading the content between
 * 		tags, it is returned here.  "NO_CONTENT" means a tag only.
 * std::string& whole_xml : all the xml, including tag, angle brackets, and content.
 *
 * return value = true if we read EOF immediately (no more tags)
 * 		  false otherwise
 */
bool get_xml(FILE * file, int& type, std::string& tag, std::string& content, std::string& whole_xml) {
  bool empty_tag = true;
  bool empty_content = true;
  int ch_int;
  char first_ch, second_ch, ch;
  bool end_of_file = false;
  tag = "";
  content = "";
  whole_xml = "";

  /*  No way to check if a file is open!  Oh well.
  if(!isopen(file)) { 
    fprintf(stderr, "Error in ap_raw_data_db.cpp : get_xml : file not open\n");
    exit(-1);
  }
  */

  ch_int = getc(file);
  if(ch_int == EOF) return true;
  first_ch = (char) ch_int;

  if(first_ch == '<') {  // reading a tag
    whole_xml.push_back(first_ch);
    second_ch = getc_err_on_EOF(file);
    whole_xml.push_back(second_ch);
    if(second_ch == '/') {  // reading an end tag
      type = 1;
      ch = getc_err_on_EOF(file);
      whole_xml.push_back(ch);
      // At this point, we have read and pushed back "</d" of "</data>"
      // and ch = 'd'
    } else {  // reading a start tag
      type = 0;
      ch = second_ch;
      // At this point, we have read and pushed back "<d" of "<data>"
      // and ch = 'd'
    }
    while(ch != '>' && ch != ' ') { // reading tag name
      if(empty_tag) empty_tag = false;
      tag.push_back(ch);  // push back a character known to be good
      ch = getc_err_on_EOF(file); // get next character
      whole_xml.push_back(ch); // whole_xml gets all characters
    }
    if(ch == ' ') { // reading content within tag
      ch = getc_err_on_EOF(file);
      whole_xml.push_back(ch);  // whole_xml gets all characters
      while(ch != '>') {
        if(empty_content) empty_content = false;
        content.push_back(ch);  // push back a character known to be good
        ch = (char) getc(file);
        whole_xml.push_back(ch);
      }
    }
  } else {  // we're reading content between tags
    type=2;
    ch = first_ch;
    while(ch != '<') {
      if(empty_content) empty_content = false;
      content.push_back(ch);
      whole_xml.push_back(ch);
      ch_int = getc(file);
      ch = (char) ch_int;
      if(ch_int == EOF) {
        fprintf(stderr, "In ap_raw_data_db.cpp : reached EOF in the middle of a tag\n");
        printf("In ap_raw_data_db.cpp : reached EOF in the middle of a tag\n");
        //  printf("<br>Whole xml so far: %s<br>", whole_xml.c_str());
        //  exit(1);
        end_of_file = true;
        break;
      }
    }
    fseek(file, -1, SEEK_CUR);  // move filepos back one, get ready to read '<'
  }
  if(empty_tag) tag = "NO_TAG";
  if(empty_content) content = "NO_CONTENT";
  // printf("%s<br>", whole_xml.c_str());
  return end_of_file;
}

char getc_err_on_EOF(FILE* file) {      
  int ch_int = getc(file);
  char ch;
  if(ch_int == EOF) {
      fprintf(stderr, "Error in ap_raw_data_db.cpp : getc_err_on_EOF : unexpected EOF\n");
      printf("Error in ap_raw_data_db.cpp : getc_err_on_EOF : unexpected EOF\n");
      exit(-1);
  }
  ch = (char) ch_int;
  return ch;
}

}
