#!/usr/bin/python
# -*- coding: utf-8 -*-
# *****************************************************************************
# $Id: gdalpythonserver.py 7464f4b11b93bb2d1098d1b962907228932bf8c1 2018-05-03 19:56:49 +1000 Ben Elliston $
#
# Project:  GDAL
# Purpose:  GDAL API_PROXY server written in Python
# Author:   Even Rouault, <even dot rouault at mines-paris dot org>
#
# *****************************************************************************
# Copyright (c) 2013, Even Rouault <even dot rouault at mines-paris dot org>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
# ***************************************************************************/

# WARNING: only Python 2 compatible for now

import os
import struct
import sys

from osgeo import gdal


class GDALPythonServerRasterBand(object):

    def __init__(self, gdal_band):
        self.gdal_band = gdal_band
        self.XSize = gdal_band.XSize
        self.YSize = gdal_band.YSize
        self.Band = gdal_band.GetBand()
        (self.BlockXSize, self.BlockYSize) = gdal_band.GetBlockSize()
        self.DataType = gdal_band.DataType
        self.mask_band = None
        self.ovr_bands = None

    def FlushCache(self):
        return self.gdal_band.FlushCache()

    def GetColorInterpretation(self):
        return self.gdal_band.GetColorInterpretation()

    def GetNoDataValue(self):
        return self.gdal_band.GetNoDataValue()

    def GetMinimum(self):
        return self.gdal_band.GetMinimum()

    def GetMaximum(self):
        return self.gdal_band.GetMaximum()

    def GetOffset(self):
        return self.gdal_band.GetOffset()

    def GetScale(self):
        return self.gdal_band.GetScale()

    def HasArbitraryOverviews(self):
        return self.gdal_band.HasArbitraryOverviews()

    def GetOverviewCount(self):
        return self.gdal_band.GetOverviewCount()

    def GetMaskFlags(self):
        return self.gdal_band.GetMaskFlags()

    def GetMaskBand(self):
        if self.mask_band is None:
            gdal_mask_band = self.gdal_band.GetMaskBand()
            if gdal_mask_band is not None:
                self.mask_band = GDALPythonServerRasterBand(gdal_mask_band)
        return self.mask_band

    def GetOverview(self, iovr):
        if self.ovr_bands is None:
            self.ovr_bands = [None] * self.GetOverviewCount()
        if self.ovr_bands[iovr] is None:
            gdal_ovr_band = self.gdal_band.GetOverview(iovr)
            if gdal_ovr_band is not None:
                self.ovr_bands[iovr] = GDALPythonServerRasterBand(gdal_ovr_band)
        return self.ovr_bands[iovr]

    def GetMetadata(self, domain):
        return self.gdal_band.GetMetadata(domain)

    def GetMetadataItem(self, key, domain):
        return self.gdal_band.GetMetadataItem(key, domain)

    def IReadBlock(self, nXBlockOff, nYBlockOff):
        return self.gdal_band.ReadBlock(nXBlockOff, nYBlockOff)

    def IRasterIO_Read(self, nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize, nBufType):
        return self.gdal_band.ReadRaster(nXOff, nYOff, nXSize, nYSize, buf_xsize=nBufXSize, buf_ysize=nBufYSize, buf_type=nBufType)

    def GetUnitType(self):
        return self.gdal_band.GetUnitType()

    def GetStatistics(self, approx_ok, force):
        return self.gdal_band.GetStatistics(approx_ok, force)

    def ComputeRasterMinMax(self, approx_ok):
        return self.gdal_band.ComputeRasterMinMax(approx_ok)

    def GetColorTable(self):
        return self.gdal_band.GetColorTable()

    def GetHistogram(self, dfMin, dfMax, nBuckets, bIncludeOutOfRange, bApproxOK):
        return self.gdal_band.GetHistogram(dfMin, dfMax, nBuckets, include_out_of_range=bIncludeOutOfRange, approx_ok=bApproxOK)


class GDALPythonServerDataset(object):

    def __init__(self, filename, access=gdal.GA_ReadOnly, open_options=None):
        nFlags = 0
        if access == gdal.GA_Update:
            nFlags |= gdal.OF_UPDATE
        if open_options is None:
            open_options = []
        self.gdal_ds = gdal.OpenEx(filename, nFlags, open_options=open_options)
        if self.gdal_ds is None:
            raise Exception(gdal.GetLastErrorMsg())
        self.RasterXSize = self.gdal_ds.RasterXSize
        self.RasterYSize = self.gdal_ds.RasterYSize
        self.RasterCount = self.gdal_ds.RasterCount
        self.bands = []
        for i in range(self.RasterCount):
            gdal_band = self.gdal_ds.GetRasterBand(i + 1)
            self.bands.append(GDALPythonServerRasterBand(gdal_band))

    def __del__(self):
        self.gdal_ds = None

    def GetDriver(self):
        return self.gdal_ds.GetDriver()

    def GetRasterBand(self, i):
        return self.bands[i - 1]

    def GetDescription(self):
        return self.gdal_ds.GetDescription()

    def GetGeoTransform(self):
        return self.gdal_ds.GetGeoTransform()

    def GetProjectionRef(self):
        return self.gdal_ds.GetProjectionRef()

    def GetGCPCount(self):
        return self.gdal_ds.GetGCPCount()

    def GetFileList(self):
        return self.gdal_ds.GetFileList()

    def GetMetadata(self, domain):
        return self.gdal_ds.GetMetadata(domain)

    def GetMetadataItem(self, key, domain):
        return self.gdal_ds.GetMetadataItem(key, domain)

    def FlushCache(self):
        self.gdal_ds.FlushCache()

    def IRasterIO_Read(self, nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize,
                       nBufType, panBandMap, nPixelSpace, nLineSpace, nBandSpace):
        return self.gdal_ds.ReadRaster(nXOff, nYOff, nXSize, nYSize,
                                       buf_xsize=nBufXSize, buf_ysize=nBufYSize,
                                       buf_type=nBufType, band_list=panBandMap,
                                       buf_pixel_space=nPixelSpace, buf_line_space=nLineSpace, buf_band_space=nBandSpace)


INSTR_GetGDALVersion = 1
INSTR_EXIT = 2
INSTR_EXIT_FAIL = 3
INSTR_SetConfigOption = 4
# INSTR_Progress = 5
INSTR_Reset = 6
INSTR_Open = 7
INSTR_Identify = 8
INSTR_Create = 9
INSTR_CreateCopy = 10
INSTR_QuietDelete = 11
# INSTR_AddBand = 12
INSTR_GetGeoTransform = 13
# INSTR_SetGeoTransform = 14
INSTR_GetProjectionRef = 15
# INSTR_SetProjection = 16
INSTR_GetGCPCount = 17
# INSTR_GetGCPProjection = 18
# INSTR_GetGCPs = 19
# INSTR_SetGCPs = 20
INSTR_GetFileList = 21
INSTR_FlushCache = 22
# INSTR_SetDescription = 23
INSTR_GetMetadata = 24
INSTR_GetMetadataItem = 25
# INSTR_SetMetadata = 26
# INSTR_SetMetadataItem = 27
INSTR_IRasterIO_Read = 28
# INSTR_IRasterIO_Write = 29
# INSTR_IBuildOverviews = 30
# INSTR_AdviseRead = 31
# INSTR_CreateMaskBand = 32
INSTR_Band_First = 33
INSTR_Band_FlushCache = 34
INSTR_Band_GetCategoryNames = 35
# INSTR_Band_SetCategoryNames = 36
# INSTR_Band_SetDescription = 37
INSTR_Band_GetMetadata = 38
INSTR_Band_GetMetadataItem = 39
INSTR_Band_SetMetadata = 40
INSTR_Band_SetMetadataItem = 41
INSTR_Band_GetColorInterpretation = 42
# INSTR_Band_SetColorInterpretation = 43
INSTR_Band_GetNoDataValue = 44
INSTR_Band_GetMinimum = 45
INSTR_Band_GetMaximum = 46
INSTR_Band_GetOffset = 47
INSTR_Band_GetScale = 48
# INSTR_Band_SetNoDataValue = 49
# INSTR_Band_SetOffset = 50
# INSTR_Band_SetScale = 51
INSTR_Band_IReadBlock = 52
# INSTR_Band_IWriteBlock = 53
INSTR_Band_IRasterIO_Read = 54
# INSTR_Band_IRasterIO_Write = 55
INSTR_Band_GetStatistics = 56
# INSTR_Band_ComputeStatistics = 57
# INSTR_Band_SetStatistics = 58
INSTR_Band_ComputeRasterMinMax = 59
INSTR_Band_GetHistogram = 60
INSTR_Band_GetDefaultHistogram = 61
# INSTR_Band_SetDefaultHistogram = 62
INSTR_Band_HasArbitraryOverviews = 63
INSTR_Band_GetOverviewCount = 64
INSTR_Band_GetOverview = 65
INSTR_Band_GetMaskBand = 66
INSTR_Band_GetMaskFlags = 67
# INSTR_Band_CreateMaskBand = 68
# INSTR_Band_Fill = 69
INSTR_Band_GetColorTable = 70
# INSTR_Band_SetColorTable = 71
INSTR_Band_GetUnitType = 72
# INSTR_Band_SetUnitType = 73
# INSTR_Band_BuildOverviews = 74
INSTR_Band_GetDefaultRAT = 75
# INSTR_Band_SetDefaultRAT = 76
# INSTR_Band_AdviseRead = 77
# INSTR_Band_DeleteNoDataValue=78
INSTR_Band_End = 79
# INSTR_END = 80

caps_list = [
    INSTR_GetGDALVersion,
    INSTR_EXIT,
    INSTR_EXIT_FAIL,
    INSTR_SetConfigOption,
    # INSTR_Progress,
    INSTR_Reset,
    INSTR_Open,
    INSTR_Identify,
    INSTR_Create,
    INSTR_CreateCopy,
    INSTR_QuietDelete,
    # INSTR_AddBand,
    INSTR_GetGeoTransform,
    # INSTR_SetGeoTransform,
    INSTR_GetProjectionRef,
    # INSTR_SetProjection,
    INSTR_GetGCPCount,
    # INSTR_GetGCPProjection,
    # INSTR_GetGCPs,
    # INSTR_SetGCPs,
    INSTR_GetFileList,
    INSTR_FlushCache,
    # INSTR_SetDescription,
    INSTR_GetMetadata,
    INSTR_GetMetadataItem,
    # INSTR_SetMetadata,
    # INSTR_SetMetadataItem,
    INSTR_IRasterIO_Read,
    # INSTR_IRasterIO_Write,
    # INSTR_IBuildOverviews,
    # INSTR_AdviseRead,
    # INSTR_CreateMaskBand,
    # INSTR_Band_First,
    INSTR_Band_FlushCache,
    INSTR_Band_GetCategoryNames,
    # INSTR_Band_SetCategoryNames,
    # INSTR_Band_SetDescription,
    INSTR_Band_GetMetadata,
    INSTR_Band_GetMetadataItem,
    INSTR_Band_SetMetadata,
    INSTR_Band_SetMetadataItem,
    INSTR_Band_GetColorInterpretation,
    # INSTR_Band_SetColorInterpretation,
    INSTR_Band_GetNoDataValue,
    INSTR_Band_GetMinimum,
    INSTR_Band_GetMaximum,
    INSTR_Band_GetOffset,
    INSTR_Band_GetScale,
    # INSTR_Band_SetNoDataValue,
    # INSTR_Band_SetOffset,
    # INSTR_Band_SetScale,
    INSTR_Band_IReadBlock,
    # INSTR_Band_IWriteBlock,
    INSTR_Band_IRasterIO_Read,
    # INSTR_Band_IRasterIO_Write,
    INSTR_Band_GetStatistics,
    # INSTR_Band_ComputeStatistics,
    # INSTR_Band_SetStatistics,
    INSTR_Band_ComputeRasterMinMax,
    INSTR_Band_GetHistogram,
    # INSTR_Band_GetDefaultHistogram,
    # INSTR_Band_SetDefaultHistogram,
    INSTR_Band_HasArbitraryOverviews,
    INSTR_Band_GetOverviewCount,
    INSTR_Band_GetOverview,
    INSTR_Band_GetMaskBand,
    INSTR_Band_GetMaskFlags,
    # INSTR_Band_CreateMaskBand,
    # INSTR_Band_Fill,
    INSTR_Band_GetColorTable,
    # INSTR_Band_SetColorTable,
    INSTR_Band_GetUnitType,
    # INSTR_Band_SetUnitType,
    # INSTR_Band_BuildOverviews,
    # INSTR_Band_GetDefaultRAT,
    # INSTR_Band_SetDefaultRAT,
    # INSTR_Band_AdviseRead ,
    # INSTR_Band_End,
    # INSTR_END = 80
]

CE_None = 0
CE_Failure = 3

VERBOSE = 0


def read_int():
    if sys.version_info >= (3, 0, 0):
        return struct.unpack('i', sys.stdin.read(4).encode('latin1'))[0]
    return struct.unpack('i', sys.stdin.read(4))[0]


def read_bigint():
    if sys.version_info >= (3, 0, 0):
        return struct.unpack('q', sys.stdin.read(8).encode('latin1'))[0]
    return struct.unpack('q', sys.stdin.read(8))[0]


def read_double():
    if sys.version_info >= (3, 0, 0):
        return struct.unpack('d', sys.stdin.read(8).encode('latin1'))[0]
    return struct.unpack('d', sys.stdin.read(8))[0]


def read_str():
    length = read_int()
    if length <= 0:
        return None
    line = sys.stdin.read(length)
    if line and line[-1] == '\0':
        line = line[:-1]
    return str


def read_strlist():
    count = read_int()
    strlist = []
    for _ in range(count):
        strlist.append(read_str())
    return strlist


def write_int(i):
    if i is True:
        v = struct.pack('i', 1)
    elif i is False or i is None:
        v = struct.pack('i', 0)
    else:
        v = struct.pack('i', i)
    if sys.version_info >= (3, 0, 0):
        sys.stdout.write(v.decode('latin1'))
    else:
        sys.stdout.write(v)


def write_uint64(i):
    v = struct.pack('Q', i)
    if sys.version_info >= (3, 0, 0):
        sys.stdout.write(v.decode('latin1'))
    else:
        sys.stdout.write(v)


def write_double(d):
    if sys.version_info >= (3, 0, 0):
        sys.stdout.write(struct.pack('d', d).decode('latin1'))
    else:
        sys.stdout.write(struct.pack('d', d))


def write_str(s):
    if s is None:
        write_int(0)
    else:
        write_int(len(s) + 1)
        sys.stdout.write(s)
        sys.stdout.write('\x00')


def write_band(band, isrv_num):
    if band is not None:
        write_int(isrv_num)  # srv band count
        write_int(band.Band)  # band number
        write_int(0)  # access
        write_int(band.XSize)  # X
        write_int(band.YSize)  # Y
        write_int(band.DataType)  # data type
        write_int(band.BlockXSize)  # block x size
        write_int(band.BlockYSize)  # block y size
        write_str('')  # band description
    else:
        write_int(-1)


def write_ct(ct):
    if ct is None:
        write_int(-1)
    else:
        write_int(ct.GetPaletteInterpretation())
        nCount = ct.GetCount()
        write_int(nCount)
        for i in range(nCount):
            entry = ct.GetColorEntry(i)
            write_int(entry[0])
            write_int(entry[1])
            write_int(entry[2])
            write_int(entry[3])


def write_marker():
    sys.stdout.write('\xDE\xAD\xBE\xEF')


def write_zero_error():
    write_int(0)


def main_loop():

    server_ds = None
    server_bands = []
    gdal.SetConfigOption('GDAL_API_PROXY', 'NO')

    while 1:
        sys.stdout.flush()

        instr = read_int()
        if VERBOSE:
            sys.stderr.write('instr=%d\n' % instr)

        band = None
        if instr >= INSTR_Band_First and instr <= INSTR_Band_End:
            srv_band = read_int()
            band = server_bands[srv_band]

        if instr == INSTR_GetGDALVersion:
            if sys.version_info >= (3, 0, 0):
                lsb = struct.unpack('B', sys.stdin.read(1).encode('latin1'))[0]
            else:
                lsb = struct.unpack('B', sys.stdin.read(1))[0]
            ver = read_str()
            vmajor = read_int()
            vminor = read_int()
            protovmajor = read_int()
            protovminor = read_int()
            extra_bytes = read_int()
            if VERBOSE:
                sys.stderr.write('lsb=%d\n' % lsb)
                sys.stderr.write('ver=%s\n' % ver)
                sys.stderr.write('vmajor=%d\n' % vmajor)
                sys.stderr.write('vminor=%d\n' % vminor)
                sys.stderr.write('protovmajor=%d\n' % protovmajor)
                sys.stderr.write('protovminor=%d\n' % protovminor)
                sys.stderr.write('extra_bytes=%d\n' % extra_bytes)

            write_str('2.1dev')
            write_int(2)  # vmajor
            write_int(1)  # vminor
            write_int(3)  # protovmajor
            write_int(0)  # protovminor
            write_int(0)  # extra bytes
            continue
        elif instr == INSTR_EXIT:
            server_ds = None
            server_bands = []
            write_marker()
            write_int(1)
            sys.exit(0)
        elif instr == INSTR_EXIT_FAIL:
            server_ds = None
            server_bands = []
            write_marker()
            write_int(1)
            sys.exit(1)
        elif instr == INSTR_SetConfigOption:
            key = read_str()
            val = read_str()
            gdal.SetConfigOption(key, val)
            if VERBOSE:
                sys.stderr.write('key=%s\n' % key)
                sys.stderr.write('val=%s\n' % val)
            continue
        elif instr == INSTR_Reset:
            # if server_ds is not None:
            #    sys.stderr.write('Reset(%s)\n' % server_ds.GetDescription())
            server_ds = None
            server_bands = []
            write_marker()
            write_int(1)
        elif instr == INSTR_Open:
            access = read_int()
            filename = read_str()
            cwd = read_str()
            open_options = read_strlist()
            if cwd is not None:
                os.chdir(cwd)
            if VERBOSE:
                sys.stderr.write('access=%d\n' % access)
                sys.stderr.write('filename=%s\n' % filename)
                sys.stderr.write('cwd=%s\n' % cwd)
                sys.stderr.write('open_options=%s\n' % str(open_options))
            # sys.stderr.write('Open(%s)\n' % filename)
            try:
                server_ds = GDALPythonServerDataset(filename, access, open_options)
            except:
                server_ds = None

            write_marker()
            if server_ds is None:
                write_int(0)  # Failure
            else:
                write_int(1)  # Success
                write_int(16)  # caps length
                caps = [0 for i in range(16)]
                for cap in caps_list:
                    caps[int(cap / 8)] = caps[int(cap / 8)] | (1 << (cap % 8))
                for i in range(16):
                    sys.stdout.write(struct.pack('B', caps[i]))  # caps
                write_str(server_ds.GetDescription())
                drv = server_ds.GetDriver()
                if drv is not None:
                    write_str(drv.GetDescription())
                    write_int(0)  # End of driver metadata
                else:
                    write_str(None)
                write_int(server_ds.RasterXSize)  # X
                write_int(server_ds.RasterYSize)  # Y
                write_int(server_ds.RasterCount)  # Band count
                write_int(1)  # All bands are identical

                if server_ds.RasterCount > 0:
                    write_band(server_ds.GetRasterBand(1), len(server_bands))
                    for i in range(server_ds.RasterCount):
                        server_bands.append(server_ds.GetRasterBand(i + 1))
        elif instr == INSTR_Identify:
            filename = read_str()
            cwd = read_str()
            dr = gdal.IdentifyDriver(filename)
            write_marker()
            if dr is None:
                write_int(0)
            else:
                write_int(1)
        elif instr == INSTR_Create:
            filename = read_str()
            cwd = read_str()
            read_int()  # xsize =
            read_int()  # ysize =
            read_int()  # bands =
            read_int()  # datatype =
            read_strlist()  # options =
            write_marker()
            # FIXME
            write_int(0)
        elif instr == INSTR_CreateCopy:
            filename = read_str()
            read_str()  # src_description =
            cwd = read_str()
            read_int()  # strict =
            read_strlist()  # options =
            # FIXME
            write_int(0)
        elif instr == INSTR_QuietDelete:
            filename = read_str()
            cwd = read_str()
            write_marker()
            # FIXME
        elif instr == INSTR_GetGeoTransform:
            gt = server_ds.GetGeoTransform()
            write_marker()
            if gt is not None:
                write_int(CE_None)
                write_int(6 * 8)
                for i in range(6):
                    write_double(gt[i])
            else:
                write_int(CE_Failure)
        elif instr == INSTR_GetProjectionRef:
            write_marker()
            write_str(server_ds.GetProjectionRef())
        elif instr == INSTR_GetGCPCount:
            write_marker()
            write_int(server_ds.GetGCPCount())
        elif instr == INSTR_GetFileList:
            write_marker()
            fl = server_ds.GetFileList()
            write_int(len(fl))
            for f in fl:
                write_str(f)
        elif instr == INSTR_GetMetadata:
            domain = read_str()
            md = server_ds.GetMetadata(domain)
            write_marker()
            write_int(len(md))
            for key in md:
                write_str('%s=%s' % (key, md[key]))
        elif instr == INSTR_GetMetadataItem:
            key = read_str()
            domain = read_str()
            val = server_ds.GetMetadataItem(key, domain)
            write_marker()
            write_str(val)
        elif instr == INSTR_IRasterIO_Read:
            nXOff = read_int()
            nYOff = read_int()
            nXSize = read_int()
            nYSize = read_int()
            nBufXSize = read_int()
            nBufYSize = read_int()
            nBufType = read_int()
            nBandCount = read_int()
            panBandMap = []
            read_int()  # size =
            for i in range(nBandCount):
                panBandMap.append(read_int())
            nPixelSpace = read_bigint()
            nLineSpace = read_bigint()
            nBandSpace = read_bigint()
            val = server_ds.IRasterIO_Read(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize, nBufType, panBandMap, nPixelSpace, nLineSpace, nBandSpace)
            write_marker()
            if val is None:
                write_int(CE_Failure)
                write_int(0)
            else:
                write_int(CE_None)
                write_int(len(val))
                sys.stdout.write(val)
        elif instr == INSTR_FlushCache:
            if server_ds is not None:
                server_ds.FlushCache()
            write_marker()
        elif instr == INSTR_Band_FlushCache:
            val = band.FlushCache()
            write_marker()
            write_int(val)
        elif instr == INSTR_Band_GetCategoryNames:
            write_marker()
            # FIXME
            write_int(-1)
        elif instr == INSTR_Band_GetMetadata:
            domain = read_str()
            md = band.GetMetadata(domain)
            write_marker()
            write_int(len(md))
            for key in md:
                write_str('%s=%s' % (key, md[key]))
        elif instr == INSTR_Band_GetMetadataItem:
            key = read_str()
            domain = read_str()
            val = band.GetMetadataItem(key, domain)
            write_marker()
            write_str(val)
        elif instr == INSTR_Band_GetColorInterpretation:
            val = band.GetColorInterpretation()
            write_marker()
            write_int(val)
        elif instr == INSTR_Band_GetNoDataValue:
            val = band.GetNoDataValue()
            write_marker()
            if val is None:
                write_int(0)
                write_double(0)
            else:
                write_int(1)
                write_double(val)
        elif instr == INSTR_Band_GetMinimum:
            val = band.GetMinimum()
            write_marker()
            if val is None:
                write_int(0)
                write_double(0)
            else:
                write_int(1)
                write_double(val)
        elif instr == INSTR_Band_GetMaximum:
            val = band.GetMaximum()
            write_marker()
            if val is None:
                write_int(0)
                write_double(0)
            else:
                write_int(1)
                write_double(val)
        elif instr == INSTR_Band_GetOffset:
            val = band.GetOffset()
            write_marker()
            if val is None:
                write_int(0)
                write_double(0)
            else:
                write_int(1)
                write_double(val)
        elif instr == INSTR_Band_GetScale:
            val = band.GetScale()
            write_marker()
            if val is None:
                write_int(0)
                write_double(1)  # default value is 1
            else:
                write_int(1)
                write_double(val)
        elif instr == INSTR_Band_IReadBlock:
            nXBlockOff = read_int()
            nYBlockOff = read_int()
            val = band.IReadBlock(nXBlockOff, nYBlockOff)
            write_marker()
            if val is None:
                write_int(CE_Failure)
                length = band.BlockXSize * band.BlockYSize * (gdal.GetDataTypeSize(band.DataType) / 8)
                write_int(length)
                sys.stdout.write(''.join('\0' for i in range(length)))
            else:
                write_int(CE_None)
                write_int(len(val))
                sys.stdout.write(val)
        elif instr == INSTR_Band_IRasterIO_Read:
            nXOff = read_int()
            nYOff = read_int()
            nXSize = read_int()
            nYSize = read_int()
            nBufXSize = read_int()
            nBufYSize = read_int()
            nBufType = read_int()
            val = band.IRasterIO_Read(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize, nBufType)
            write_marker()
            if val is None:
                write_int(CE_Failure)
                write_int(0)
            else:
                write_int(CE_None)
                write_int(len(val))
                sys.stdout.write(val)
        elif instr == INSTR_Band_GetStatistics:
            approx_ok = read_int()
            force = read_int()
            val = band.GetStatistics(approx_ok, force)
            write_marker()
            if val is None or val[3] < 0:
                write_int(CE_Failure)
            else:
                write_int(CE_None)
                write_double(val[0])
                write_double(val[1])
                write_double(val[2])
                write_double(val[3])
        elif instr == INSTR_Band_ComputeRasterMinMax:
            approx_ok = read_int()
            val = band.ComputeRasterMinMax(approx_ok)
            write_marker()
            if val is None:
                write_int(CE_Failure)
            else:
                write_int(CE_None)
                write_double(val[0])
                write_double(val[1])
        elif instr == INSTR_Band_GetHistogram:
            dfMin = read_double()
            dfMax = read_double()
            nBuckets = read_int()
            bIncludeOutOfRange = read_int()
            bApproxOK = read_int()
            val = band.GetHistogram(dfMin, dfMax, nBuckets, bIncludeOutOfRange, bApproxOK)
            write_marker()
            if val is None:
                write_int(CE_Failure)
            else:
                write_int(CE_None)
                write_int(len(val) * 8)
                for v in val:
                    write_uint64(v)
        # elif instr == INSTR_Band_GetDefaultHistogram:
        #    bForce = read_int()
        #    write_marker()
        #    write_int(CE_Failure)
        elif instr == INSTR_Band_HasArbitraryOverviews:
            val = band.HasArbitraryOverviews()
            write_marker()
            write_int(val)
        elif instr == INSTR_Band_GetOverviewCount:
            val = band.GetOverviewCount()
            write_marker()
            write_int(val)
        elif instr == INSTR_Band_GetOverview:
            iovr = read_int()
            ovr_band = band.GetOverview(iovr)
            write_marker()
            write_band(ovr_band, len(server_bands))
            if ovr_band is not None:
                server_bands.append(ovr_band)
        elif instr == INSTR_Band_GetMaskBand:
            msk_band = band.GetMaskBand()
            write_marker()
            write_band(msk_band, len(server_bands))
            if msk_band is not None:
                server_bands.append(msk_band)
        elif instr == INSTR_Band_GetMaskFlags:
            val = band.GetMaskFlags()
            write_marker()
            write_int(val)
        elif instr == INSTR_Band_GetColorTable:
            ct = band.GetColorTable()
            write_marker()
            write_ct(ct)
        elif instr == INSTR_Band_GetUnitType:
            val = band.GetUnitType()
            write_marker()
            write_str(val)
        # elif instr == INSTR_Band_GetDefaultRAT:
        #    write_marker()
        #    # FIXME
        #    write_int(0)
        else:
            break

        write_zero_error()


main_loop()
