#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2000, Atlantis Scientific Inc. (www.atlsci.com)
# Copyright (C) 2005  Gabriel Ebner <ge@gabrielebner.at>
# Copyright (c) 2009, Even Rouault <even dot rouault at spatialys.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
#
# modified output of geotransform to retain same numerical
# precision as the gdal 'C' utilities
# Norman Vine  nhv@cape.com  03-Oct-2005 6:23:45 am

import sys

from osgeo import gdal

# =============================================================================


def names_to_fileinfos(names):
    """
    Translate a list of GDAL filenames, into file_info objects.

    names -- list of valid GDAL dataset names.

    Returns a list of file_info objects.  There may be less file_info objects
    than names if some of the names could not be opened as GDAL files.
    """

    file_infos = []
    for name in names:
        fi = file_info()
        if fi.init_from_name(name):
            file_infos.append(fi)
        else:
            print('Can not open dataset "%s", skipped' % name)

    return file_infos

# *****************************************************************************


class file_info(object):
    """A class holding information about a GDAL file."""

    def __init__(self):
        self.band_types = [None]
        self.block_sizes = [None]
        self.nodata = [None]
        self.cts = [None]
        self.color_interps = [None]

        self.filename = None
        self.bands = None
        self.xsize = None
        self.ysize = None
        self.projection = None
        self.geotransform = None
        self.ulx = None
        self.uly = None
        self.lrx = None
        self.lry = None

    def init_from_name(self, filename):
        """
        Initialize file_info from filename

        filename -- Name of file to read.

        Returns 1 on success or 0 if the file can't be opened.
        """
        fh = gdal.Open(filename)
        if fh is None:
            return False

        self.filename = filename
        self.bands = fh.RasterCount
        self.xsize = fh.RasterXSize
        self.ysize = fh.RasterYSize
        self.projection = fh.GetProjection()
        self.geotransform = fh.GetGeoTransform()
        self.ulx = self.geotransform[0]
        self.uly = self.geotransform[3]
        self.lrx = self.ulx + self.geotransform[1] * self.xsize
        self.lry = self.uly + self.geotransform[5] * self.ysize

        self.band_types = [None]
        self.block_sizes = [None]
        self.nodata = [None]
        self.cts = [None]
        self.color_interps = [None]
        for i in range(1, fh.RasterCount + 1):
            band = fh.GetRasterBand(i)
            self.band_types.append(band.DataType)
            self.block_sizes.append(band.GetBlockSize())
            if band.GetNoDataValue() is not None:
                self.nodata.append(band.GetNoDataValue())
            self.color_interps.append(band.GetRasterColorInterpretation())
            ct = band.GetRasterColorTable()
            if ct is not None:
                self.cts.append(ct.Clone())
            else:
                self.cts.append(None)

        return True

    def write_source(self, t_fh, t_geotransform, xsize, ysize, s_band):
        t_ulx = t_geotransform[0]
        t_uly = t_geotransform[3]
        t_lrx = t_geotransform[0] + xsize * t_geotransform[1]
        t_lry = t_geotransform[3] + ysize * t_geotransform[5]

        # figure out intersection region
        tgw_ulx = max(t_ulx, self.ulx)
        tgw_lrx = min(t_lrx, self.lrx)
        if t_geotransform[5] < 0:
            tgw_uly = min(t_uly, self.uly)
            tgw_lry = max(t_lry, self.lry)
        else:
            tgw_uly = max(t_uly, self.uly)
            tgw_lry = min(t_lry, self.lry)

        # do they even intersect?
        if tgw_ulx >= tgw_lrx:
            return 1
        if t_geotransform[5] < 0 and tgw_uly <= tgw_lry:
            return 1
        if t_geotransform[5] > 0 and tgw_uly >= tgw_lry:
            return 1

        # compute target window in pixel coordinates.
        tw_xoff = int((tgw_ulx - t_geotransform[0]) / t_geotransform[1] + 0.1)
        tw_yoff = int((tgw_uly - t_geotransform[3]) / t_geotransform[5] + 0.1)
        tw_xsize = int((tgw_lrx - t_geotransform[0]) / t_geotransform[1] + 0.5) \
            - tw_xoff
        tw_ysize = int((tgw_lry - t_geotransform[3]) / t_geotransform[5] + 0.5) \
            - tw_yoff

        if tw_xsize < 1 or tw_ysize < 1:
            return 1

        # Compute source window in pixel coordinates.
        sw_xoff = int((tgw_ulx - self.geotransform[0]) / self.geotransform[1])
        sw_yoff = int((tgw_uly - self.geotransform[3]) / self.geotransform[5])
        sw_xsize = int((tgw_lrx - self.geotransform[0]) /
                       self.geotransform[1] + 0.5) - sw_xoff
        sw_ysize = int((tgw_lry - self.geotransform[3]) /
                       self.geotransform[5] + 0.5) - sw_yoff

        if sw_xsize < 1 or sw_ysize < 1:
            return 1

        t_fh.write('\t\t<SimpleSource>\n')
        t_fh.write(('\t\t\t<SourceFilename relativeToVRT="1">%s' +
                    '</SourceFilename>\n') % self.filename)
        t_fh.write('\t\t\t<SourceBand>%i</SourceBand>\n' % s_band)
        data_type_name = gdal.GetDataTypeName(self.band_types[s_band])
        block_size_x, block_size_y = self.block_sizes[s_band]
        t_fh.write('\t\t\t<SourceProperties RasterXSize="%i" RasterYSize="%i"'
                   ' DataType="%s" BlockXSize="%i" BlockYSize="%i"/>\n'
                   % (self.xsize, self.ysize, data_type_name, block_size_x, block_size_y))
        t_fh.write('\t\t\t<SrcRect xOff="%i" yOff="%i" xSize="%i" ySize="%i"/>\n'
                   % (sw_xoff, sw_yoff, sw_xsize, sw_ysize))
        t_fh.write('\t\t\t<DstRect xOff="%i" yOff="%i" xSize="%i" ySize="%i"/>\n'
                   % (tw_xoff, tw_yoff, tw_xsize, tw_ysize))
        t_fh.write('\t\t</SimpleSource>\n')

# =============================================================================


def Usage():
    print('Usage: gdal_vrtmerge.py [-o out_filename] [-separate] [-pct]')
    print('           [-ul_lr ulx uly lrx lry] [-ot datatype] [-i input_file_list')
    print('           | input_files]')

# =============================================================================
#
# Program mainline.
#


if __name__ == '__main__':

    names = []
    out_file = 'out.vrt'

    ulx = None
    psize_x = None
    separate = False
    pre_init = None

    argv = gdal.GeneralCmdLineProcessor(sys.argv)
    if argv is None:
        sys.exit(0)

    # Parse command line arguments.
    i = 1
    while i < len(argv):
        arg = argv[i]

        if arg == '-o':
            i = i + 1
            out_file = argv[i]

        elif arg == '-i':
            i = i + 1
            in_file_list = open(argv[i])
            names.extend(in_file_list.read().split())

        elif arg == '-separate':
            separate = True

        elif arg == '-ul_lr':
            ulx = float(argv[i + 1])
            uly = float(argv[i + 2])
            lrx = float(argv[i + 3])
            lry = float(argv[i + 4])
            i = i + 4

        elif arg[:1] == '-':
            print('Unrecognized command option: ', arg)
            Usage()
            sys.exit(1)

        else:
            names.append(arg)

        i = i + 1

    if not names:
        print('No input files selected.')
        Usage()
        sys.exit(1)

    # Collect information on all the source files.
    file_infos = names_to_fileinfos(names)
    if not file_infos:
        print('Nothing to process, exiting.')
        sys.exit(1)

    if ulx is None:
        ulx = file_infos[0].ulx
        uly = file_infos[0].uly
        lrx = file_infos[0].lrx
        lry = file_infos[0].lry

        for fi in file_infos:
            ulx = min(ulx, fi.ulx)
            uly = max(uly, fi.uly)
            lrx = max(lrx, fi.lrx)
            lry = min(lry, fi.lry)

    if psize_x is None:
        psize_x = file_infos[0].geotransform[1]
        psize_y = file_infos[0].geotransform[5]

    projection = file_infos[0].projection

    for fi in file_infos:
        if fi.geotransform[1] != psize_x or fi.geotransform[5] != psize_y:
            print("All files must have the same scale; %s does not"
                  % fi.filename)
            sys.exit(1)

        if fi.geotransform[2] != 0 or fi.geotransform[4] != 0:
            print("No file must be rotated; %s is" % fi.filename)
            sys.exit(1)

        if fi.projection != projection:
            print("All files must be in the same projection; %s is not"
                  % fi.filename)
            sys.exit(1)

    geotransform = (ulx, psize_x, 0.0, uly, 0.0, psize_y)

    xsize = int(((lrx - ulx) / geotransform[1]) + 0.5)
    ysize = int(((lry - uly) / geotransform[5]) + 0.5)

    if separate:
        bands = len(file_infos)
    else:
        bands = file_infos[0].bands

    t_fh = open(out_file, 'w')
    t_fh.write('<VRTDataset rasterXSize="%i" rasterYSize="%i">\n'
               % (xsize, ysize))
    t_fh.write('\t<GeoTransform>%24.16f, %24.16f, %24.16f, %24.16f, %24.16f, %24.16f</GeoTransform>\n'
               % geotransform)

    if projection:
        t_fh.write('\t<SRS>%s</SRS>\n' % projection)

    if separate:
        band_n = 0
        for fi in file_infos:
            band_n = band_n + 1
            if len(fi.band_types) != 2:
                print('File %s has %d bands. Only first band will be taken '
                      'into account' % (fi.filename, len(fi.band_types) - 1))
            dataType = gdal.GetDataTypeName(fi.band_types[1])

            t_fh.write('\t<VRTRasterBand dataType="%s" band="%i">\n'
                       % (dataType, band_n))
            t_fh.write('\t\t<ColorInterp>%s</ColorInterp>\n' %
                       gdal.GetColorInterpretationName(fi.color_interps[1]))
            fi.write_source(t_fh, geotransform, xsize, ysize, 1)
            t_fh.write('\t</VRTRasterBand>\n')
    else:
        for band in range(1, bands + 1):
            dataType = gdal.GetDataTypeName(file_infos[0].band_types[band])

            t_fh.write('\t<VRTRasterBand dataType="%s" band="%i">\n'
                       % (dataType, band))
            if file_infos[0].nodata != [None]:
                t_fh.write('\t\t<NoDataValue>%f</NoDataValue>\n' %
                           file_infos[0].nodata[band])
            t_fh.write('\t\t<ColorInterp>%s</ColorInterp>\n' %
                       gdal.GetColorInterpretationName(
                           file_infos[0].color_interps[band]))

            ct = file_infos[0].cts[band]
            if ct is not None:
                t_fh.write('\t\t<ColorTable>\n')
                for i in range(ct.GetCount()):
                    t_fh.write(
                        '\t\t\t<Entry c1="%i" c2="%i" c3="%i" c4="%i"/>\n'
                        % ct.GetColorEntry(i))
                t_fh.write('\t\t</ColorTable>\n')

            for fi in file_infos:
                fi.write_source(t_fh, geotransform, xsize, ysize, band)

            t_fh.write('\t</VRTRasterBand>\n')

    t_fh.write('</VRTDataset>\n')
