#include "gdal_priv.h"
#include <iostream>
#include <cstdlib>
#include <string>

#define suicide()  _suicide(__FILE__, __LINE__)

#define DUMP(expr)  std::cout << dump::indent() << expr << std::endl
#define DUMP_PUSH(expr)  do { std::cout << dump::indent() << expr; dump::push(); } while (false)
#define DUMP_POP()  dump::pop()
namespace dump
{
  static int level = 0;
  static std::string indentation;
  static const char *indent()
  {
    return indentation.c_str();
  }
  static void increase(bool positive)
  {
    level += positive ? 1 : -1;
    indentation = "";
    for (int i = 0; i < level; ++i)
      indentation += "  ";
  }
  static void push()
  {
    std::cout << " {{{" << std::endl;
    increase(true);
  }
  static void pop()
  {
    increase(false);
    DUMP("}}}");
  }
}

static void _suicide(const char *fname, int line)
{
  std::cerr << "suicide at " << fname << ":" << line << std::endl;
  _exit(1);
}

// print all metadata in the given domain
static void print_metadata(GDALDataset *poDataset, const char *domain)
{
  char **metadata = poDataset->GetMetadata(domain);
  if (metadata) {
    DUMP_PUSH("Metadata " << domain);
    for (char **meta = metadata; *meta; ++meta)
      DUMP(*meta);
    DUMP_POP();
  }
}

// recursively dump elements, attributes and child datasets
static void doit(const char *filename)
{
  GDALDataset *poDataset;

  poDataset = (GDALDataset *)GDALOpen(filename, GA_ReadOnly);
  if (!poDataset) suicide();
  DUMP_PUSH(filename);

  // global description
  {
    DUMP("Driver " << poDataset->GetDriver()->GetDescription());
  }

  // x, y
  int xsize = poDataset->GetRasterXSize();
  int ysize = poDataset->GetRasterYSize();
  DUMP("(X, Y) " << xsize << ", " << ysize);

  // projection
  {
    const char *proj = poDataset->GetProjectionRef();
    if (proj) {
      DUMP("Projection " << proj);
    }
    const char *gcpproj = poDataset->GetGCPProjection();
    if (gcpproj) {
      DUMP("GCPProjection " << proj);
    }
    int gcpcount = poDataset->GetGCPCount();
    for (int i = 0; i < gcpcount; ++i) {
      const GDAL_GCP *gcp = poDataset->GetGCPs() + i;
      DUMP("GCP[" << i << "] " << gcp->pszId << ", " << gcp->pszInfo);
    }
  }
  {
    double geotransform[6];
    if (poDataset->GetGeoTransform(geotransform) == CE_None) {
      DUMP("GeoTransform " << geotransform[0] << ", " << geotransform[1] << ", " << geotransform[2]);
      DUMP("GeoTransform " << geotransform[3] << ", " << geotransform[4] << ", " << geotransform[5]);
    }
  }

  // elements
  {
    int count = poDataset->GetRasterCount();
    for (int i = 0; i < count; ++i) {
      GDALRasterBand *rb = poDataset->GetRasterBand(i + 1);
      int blockx, blocky;
      rb->GetBlockSize(&blockx, &blocky);
      GDALDataType dtype = rb->GetRasterDataType();
      const char *desc = rb->GetDescription();
      DUMP_PUSH("RasterBand[" << i << "] (" << blockx << ", " << blocky << ") " << GDALGetDataTypeName(dtype) << " : " << desc);
      int hasfill = false;
      double fill = rb->GetNoDataValue(&hasfill);
      if (hasfill) DUMP("Fill " << fill);
      float *buffer = new float[xsize * ysize];
      rb->RasterIO(GF_Read, 0, 0, xsize, ysize, buffer, xsize, ysize, GDT_Float32, 0, 0);
      for (int j = 0; j < ysize; ++j) {
	for (int k = 0; k < xsize; ++k) {
	  std::cout << buffer[j * xsize + k] << " ";
	}
	std::cout << std::endl;
      }
      delete [] buffer;
      DUMP_POP();
    }
  }

  // metadata
  print_metadata(poDataset, "");
  print_metadata(poDataset, "GEOLOCATION");
  print_metadata(poDataset, "IMAGE_STRUCTURE");
  print_metadata(poDataset, "SUBDATASETS");

  // recursively dump all child objects
  {
    char **metadata = poDataset->GetMetadata("SUBDATASETS");
    if (metadata) {
      // need to skip SUBDATASET_?_DESC;
      // we only need SUBDATASET_?_NAME here
      for (char **meta = metadata; *meta; meta += 2) {
	const char *name = strstr(*meta, "=");
	if (!name) suicide();
	doit(name + 1);
      }
    }
  }

  GDALClose(poDataset);
  DUMP_POP();
}

int main(int argc, char **argv)
{
  if (argc != 2) return 1;

  GDALAllRegister();
  doit(argv[1]);
  return 0;
}

// vim:ts=8:sw=2:sts=2

