#ifndef BA_GRAPH_IO_PREGRAPH_HPP
#define BA_GRAPH_IO_PREGRAPH_HPP

#include <io/print_nice.hpp>

#include <fstream>
#include <iostream>
#include <sstream>
#include <stdio.h>
#include <string.h>
#include <string>
#include <vector>

namespace ba_graph {

namespace internal {

Graph semiEdgeDumpToManyDumps(Graph &g, Factory *f = &static_factory) {
  Graph h(createG(*f));
  for (int i = 0; i < g.order() - 1; i++) {
    addV(h, i);
  }
  int j = h.order();
  for (auto &rotation : g) {
    for (auto &incidence : rotation) {
      if (incidence.n1() < incidence.n2()) {
        if (incidence.n1() == g.order() - 1 ||
            incidence.n2() == g.order() - 1) {
          addV(h, j);
          addE(h, Location(incidence.n1(), j));
          j++;
        } else {
          addE(h, Location(incidence.n1(), incidence.n2()));
        }
      }
    }
  }
  return std::move(h);
}

Graph manySemiedgeDumpsToOne(Graph &g, Factory *f = &static_factory) {
  Graph h(createG(*f));

  // find first vertex with degree of one
  int dumpSemiEdge = 1;
  for (auto &rotation : g) {
    if (rotation.degree() == 1) {
      break;
    }
    dumpSemiEdge++;
  }

  for (int i = 0; i <= dumpSemiEdge; i++) {
    addV(h, i);
  }
  for (auto &rotation : g) {
    for (auto &incidence : rotation) {
      if (incidence.n1() < incidence.n2()) {
        if (incidence.n2() >= dumpSemiEdge) {
          addE(h, Location(incidence.n1(), dumpSemiEdge));
        } else {
          addE(h, Location(incidence.n1(), incidence.n2()));
        }
      }
    }
  }

  return std::move(h);
}

int readHeader(std::istream &in) {
  int endian = LITTLE_ENDIAN;
  // First we read the header and determine the file format
  char buffer[15];
  in.read(buffer, 15);
  std::string header = ">>pregraph_code";
  if (buffer != header) {
    throw std::runtime_error("File is in wrong format");
  }
  // next we read the endian of the file
  char en[2] = "";
  in.read(buffer, 1);
  if (buffer[0] != ' ') {
    in.putback(1);
  }
  in.read(buffer, 2);
  strncat(en, buffer, 2);
  std::string be = "be";
  std::string le = "le";
  std::string arrows = "<<";
  if (en == be) {
    endian = BIG_ENDIAN;
    in.read(buffer, 2);
  } else if (en == le) {
    in.read(buffer, 2);
  } else if (en == arrows) {
  } else {
    throw std::runtime_error("File is in wrong format");
  }
  return endian;
}

unsigned short read_2byte_number(std::istream &in, int endian) {
  char c[2];
  unsigned short res;
  if (in.read(c, 2).eof()) {
    throw std::runtime_error("File is in wrong format");
  }
  if (endian == BIG_ENDIAN) {
    res = c[0] * 256 + c[1];
  } else {
    res = c[1] * 256 + c[0];
  }
  return res;
}

unsigned short readNextNumber(std::istream &in, bool bignum, int endian) {
  if (bignum) {
    return read_2byte_number(in, endian);
  }
  char k;
  in.get(k);
  return (unsigned short)k;
}

Graph readOnePregraph(std::istream &in, int endian,
                      Factory *f = &static_factory) {
  int signum, number;
  signum = readNextNumber(in, false, endian);

  // if the code starts with a zero, all the entries are two bytes
  // else the number we just read was the order of the graph
  if (signum == 0) {
    number = readNextNumber(in, true, endian);
  } else {
    number = signum;
  }
  int n = number;
  if (number == 0) {
    throw std::runtime_error("graph of unsupported order");
  }
  Graph g(createG(*f));
  for (int i = 0; i < n + 1; i++) { // +1 to store semiedges
    addV(g, i);
  }
  int i = 0;
  while (i <= n - 1) {
    number = readNextNumber(in, signum == 0, endian);
    number--;
    if (number == n - 1) { // is semi-edge
      addE(g, Location(i, n - 1));
    } else if (number != -1) {
      addE(g, Location(i, number));
    } else {
      i++; // next vertex
    }
  }
  return std::move(semiEdgeDumpToManyDumps(g));
}

} // namespace internal

std::vector<Graph> readPregraphsFromFile(const std::string &fileName,
                                         Factory *f = &static_factory) {
  std::ifstream in(fileName);
  if (!in)
    throw std::runtime_error("cannot read file " + fileName);
  auto endian = internal::readHeader(in);
  std::vector<Graph> graphs;
  while (!in.eof()) {
    if ((short)in.peek() == -1)
      break;
    graphs.push_back(std::move(internal::readOnePregraph(in, endian, f)));
  }
  return graphs;
}

template <typename P>
void read_pregraph_stream(std::istream &in,
                          std::function<void(Graph &, Factory &, P *)> callback,
                          P *callbackParam, Factory *f = &static_factory) {
  auto endian = internal::readHeader(in);
  while (!in.eof()) {
    if ((short)in.peek() == -1)
      break;
    Graph g = std::move(internal::readOnePregraph(in, endian, f));
    callback(g, *f, callbackParam);
  }
}

template <typename P>
void read_pregraph_file(const std::string &fileName,
                        std::function<void(Graph &, Factory &, P *)> callback,
                        P *callbackParam, Factory *f = &static_factory) {
  std::ifstream fin(fileName);
  if (!fin)
    throw std::runtime_error("cannot read file " + fileName);
  read_pregraph_stream<P>(fin, callback, callbackParam, f);
}

// assumes g has single semiedge dump
Graph copyWithoutSemiedges(Graph &g) {
  Graph h(createG());

  int order = g.order();
  for (int i = 1; i < order; i++) {
    addV(h, i);
  }
  for (auto &rotation : g) {
    for (auto &incidence : rotation) {
      if (incidence.n1() < incidence.n2()) {
        if (incidence.n2() < order) {
          addE(h, Location(incidence.n1(), incidence.n2()));
        }
      }
    }
  }

  return std::move(h);
}

char writePregraph(FILE *f, Graph &g) {
  fprintf(f, "%c", (unsigned char)g.order());

  for (auto &rotation : g) {
    for (auto &incidence : rotation) {
      if (incidence.n1() < incidence.n2()) {
        fprintf(f, "%c", (unsigned char)incidence.n2().to_int());
      }
    }
    fprintf(f, "%c", (unsigned char)0);
  }
  return 0;
}

char writePregraphCode(FILE *f, std::vector<Graph> &graphs) {
  // write header
  std::cout << "writing graphs: " << graphs.size() << std::endl;
  fprintf(f, ">>pregraph_code %s<<", "le");

  for (auto &g : graphs) {
    writePregraph(f, g);
  }

  return (ferror(f) ? 2 : 1);
}

} // namespace ba_graph

#endif
