// hgt.hxx -- SRTM "hgt" data management class
//
// Written by Curtis Olson, started February 2003.
//
// Copyright (C) 2003  Curtis L. Olson  - http://www.flightgear.org/~curt
//
// 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 2 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, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// $Id: hgt.cxx,v 1.7 2005-12-19 16:06:45 curt Exp $


#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <simgear/compiler.h>

#include <stdlib.h>   // atof()

#ifdef SG_HAVE_STD_INCLUDES
#  include <cerrno>
#else
#  include <errno.h>
#endif

#include <simgear/constants.h>
#include <simgear/io/lowlevel.hxx>

#ifdef _MSC_VER
#  include <win32/mkdir.hpp>
#endif

#include "hgt.hxx"

SG_USING_STD(cout);
SG_USING_STD(endl);


TGHgt::TGHgt( int _res ) {
    hgt_resolution = _res;

    data = new short int[MAX_HGT_SIZE][MAX_HGT_SIZE];
    output_data = new short int[MAX_HGT_SIZE][MAX_HGT_SIZE];

    remove_tmp_file = false;
}


TGHgt::TGHgt( int _res, const SGPath &file ) {
    hgt_resolution = _res;

    data = new short int[MAX_HGT_SIZE][MAX_HGT_SIZE];
    output_data = new short int[MAX_HGT_SIZE][MAX_HGT_SIZE];

    remove_tmp_file = false;

    TGHgt::open( file );
}


// open an HGT file
bool
TGHgt::open ( const SGPath &f ) {
    SGPath file_name = f;

    // open input file (or read from stdin)
    if ( file_name.str() ==  "-" ) {
	cout << "Loading HGT data file: stdin" << endl;
        if ( (fd = gzdopen(STDIN_FILENO, "r")) == NULL ) {
            cout << "ERROR: opening stdin" << endl;
            return false;
        }
    } else {
        if ( file_name.extension() == "zip" ) {
            // extract the .zip file to /tmp and point the file name
            // to the extracted file
            cout << "Extracting " << file_name.str() << " to /tmp" << endl;
            string command = "unzip -d /tmp " + file_name.base();
            system( command.c_str() );

            SGPath full_name = file_name.base();
            file_name.set( "/tmp" );
            if ( full_name.file().empty() ) {
                file_name.append( full_name.str() );
            } else {
                file_name.append( full_name.file() );
            }
            remove_tmp_file = true;
            remove_file_name = file_name.str();

            cout << "Proceeding with " << file_name.str() << endl;
        }
        
        cout << "Loading HGT data file: " << file_name.str() << endl;
        if ( (fd = gzopen( file_name.c_str(), "rb" )) == NULL ) {
            SGPath file_name_gz = file_name;
            file_name_gz.append( ".gz" );
            if ( (fd = gzopen( file_name_gz.c_str(), "rb" )) == NULL ) {
                cout << "ERROR: opening " << file_name.str() << " or "
                     << file_name_gz.str() << "for reading!" << endl;
                return false;
            }
        }
    }

    // Determine originx/originy from file name
    string name = file_name.file();
    cout << "  Name = " << name << endl;
    originy = atof( name.substr(1, 2).c_str() ) * 3600.0;
    if ( name.substr(0, 1) == "S" ) {
        originy = -originy;
    }
    originx = atof( name.substr(4, 3).c_str() ) * 3600.0;
    if ( name.substr(3, 1) == "W" ) {
        originx = -originx;
    }
    cout << "  Origin = " << originx << ", " << originy << endl;

    return true;
}


// close an HGT file
bool
TGHgt::close () {
    gzclose(fd);

    if ( remove_tmp_file ) {
        string command = "/bin/rm " + remove_file_name;
        system( command.c_str() );
    }

    return true;
}


// load an hgt file
bool
TGHgt::load( ) {
    int size;
    if ( hgt_resolution == 1 ) {
        cols = rows = size = 3601;
        col_step = row_step = 1;
    } else if ( hgt_resolution == 3 ) {
        cols = rows = size = 1201;
        col_step = row_step = 3;
    } else {
        cout << "Unknown HGT resolution, only 1 and 3 arcsec formats" << endl;
        cout << " are supported!" << endl;
        return false;
    }

    short int *var;
    for ( int row = size - 1; row >= 0; --row ) {
        for ( int col = 0; col < size; ++col ) {
            var = &data[col][row];
            if ( gzread ( fd, var, sizeof(short) ) != sizeof(short) ) {
                return false;
            }
            if ( sgIsLittleEndian() ) {
                sgEndianSwap( (unsigned short int*)var);
            }
        }
    }

    return true;
}


// write out the area of data covered by the specified bucket.  Data
// is written out column by column starting at the lower left hand
// corner.
bool
TGHgt::write_area( const string& root, SGBucket& b ) {
    // calculate some boundaries
    double min_x = ( b.get_center_lon() - 0.5 * b.get_width() ) * 3600.0;
    double max_x = ( b.get_center_lon() + 0.5 * b.get_width() ) * 3600.0;

    double min_y = ( b.get_center_lat() - 0.5 * b.get_height() ) * 3600.0;
    double max_y = ( b.get_center_lat() + 0.5 * b.get_height() ) * 3600.0;

    cout << b << endl;
    cout << "width = " << b.get_width() << " height = " << b.get_height() 
	 << endl;
    cout << "min = " << min_x << "," << min_y
         << "  max = " << max_x << "," << max_y << endl;
    int start_x = (int)((min_x - originx) / col_step);
    int span_x = (int)(b.get_width() * 3600.0 / col_step);

    int start_y = (int)((min_y - originy) / row_step);
    int span_y = (int)(b.get_height() * 3600.0 / row_step);

    cout << "start_x = " << start_x << "  span_x = " << span_x << endl;
    cout << "start_y = " << start_y << "  span_y = " << span_y << endl;

    // Do a simple sanity checking.  But, please, please be nice to
    // this write_area() routine and feed it buckets that coincide
    // well with the underlying grid structure and spacing.

    if ( ( min_x < originx )
	 || ( max_x > originx + cols * col_step )
	 || ( min_y < originy )
	 || ( max_y > originy + rows * row_step ) ) {
	cout << "  ERROR: bucket at least partially outside HGT data range!" <<
	    endl;
	return false;
    }

    // If the area is all ocean, skip it.
    if ( !has_non_zero_elev(start_x, span_x, start_y, span_y) ) {
        cout << "Tile is all zero elevation: skipping" << endl;
        return false;
    }

    // generate output file name
    string base = b.gen_base_path();
    string path = root + "/" + base;
#ifdef _MSC_VER
    fg_mkdir( path.c_str() );
#else
    string command = "mkdir -p " + path;
    system( command.c_str() );
#endif

    string array_file = path + "/" + b.gen_index_str() + ".arr.gz";
    cout << "array_file = " << array_file << endl;

    // write the file
    gzFile fp;
    if ( (fp = gzopen( array_file.c_str(), "wb9" )) == NULL ) {
	cout << "ERROR:  cannot open " << array_file << " for writing!" << endl;
	exit(-1);
    }

    gzprintf( fp, "%d %d\n", (int)min_x, (int)min_y );
    gzprintf( fp, "%d %d %d %d\n", span_x + 1, (int)col_step, 
              span_y + 1, (int)row_step );
    for ( int i = start_x; i <= start_x + span_x; ++i ) {
	for ( int j = start_y; j <= start_y + span_y; ++j ) {
	    gzprintf( fp, "%d ", (int)data[i][j] );
	}
	gzprintf( fp, "\n" );
    }
    gzclose(fp);

    return true;
}


// write the entire area out in a simple ascii format
bool TGHgt::write_whole_ascii( const string& file ) {
    cout << "writing to " << file << endl;
    // write the file
    gzFile fp;
    if ( (fp = gzopen( file.c_str(), "wb9" )) == NULL ) {
	cout << "ERROR:  cannot open " << file << " for writing!" << endl;
	exit(-1);
    }

    gzprintf( fp, "%d\n%d\n", rows, cols );
    for ( int row = rows - 1; row >= 0; row-- ) {
        for ( int col = 0; col < cols; col++ ) {
            gzprintf( fp, "%d\n", (int)data[col][row] );
        }
    }
    gzclose(fp);

    return true;
}



TGHgt::~TGHgt() {
    // printf("class TGHgt DEstructor called.\n");
    delete [] data;
    delete [] output_data;
}


bool
TGHgt::has_non_zero_elev (int start_x, int span_x,
                          int start_y, int span_y) const
{
    for ( int row = start_y; row < start_y + span_y; row++ ) {
        for ( int col = start_x; col < start_x + span_x; col++ ) {
            if ( data[col][row] != 0 )
                return true;
        }
    }

    return false;
}