/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/

#include "StdAfx.h"
#include "ImageASC.h"
#include "CryFile.h"

//---------------------------------------------------------------------------

bool CImageASC::Save(const QString& fileName, const CFloatImage& image)
{
    // There are two types of ARCGrid file formats - binary (ADF) and ASCII (ASC).
    // See here:  https://en.wikipedia.org/wiki/Esri_grid

    uint32 width = image.GetWidth();
    uint32 height = image.GetHeight();
    float* pixels = image.GetData();

    string fileHeader;
    fileHeader.Format(
        // Number of columns and rows in the data
        "ncols %d\n"
        "nrows %d\n"

        // The coordinates of the bottom-left corner.
        // These numbers represent coordinates on a globe, so this choice of values is arbitrary.
        "xllcorner 0.0\n"
        "yllcorner 0.0\n"

        // The size of each grid square.
        // The problem is that cellsize represents the size of a square on a grid being projected onto a globe.
        // This number can be used to convert size to degrees, based on where on the globe it appears.
        // We don't have a real-world location associated with our data, so this size choice is arbitrary.
        "cellsize 0.0003\n"

        // The value used for missing data.  Since we shouldn't have any missing data, we'll choose a value that can't appear below.
        "nodata_value -1\n"
        , width, height);

    FILE* file = nullptr;
    azfopen(&file, fileName.toUtf8().data(), "wt");
    if (!file)
    {
        return false;
    }

    // First print the file header
    fprintf(file, fileHeader.c_str());

    // Then print all the pixels.
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            fprintf(file, "%.7f ", pixels[x + y * width]);
        }
        fprintf(file, "\n");
    }

    fclose(file);
    return true;
}

//---------------------------------------------------------------------------

bool CImageASC::Load(const QString& fileName, CFloatImage& image)
{
    FILE* file = nullptr;
    azfopen(&file, fileName.toUtf8().data(), "rt");
    if (!file)
    {
        return false;
    }

    const char seps[] = " \r\n\t";
    char* token;

    int32 width = 0;
    int32 height = 0;
    float nodataValue = 0.0f;

    bool validData = true;

    // Read the file into memory

    fseek(file, 0, SEEK_END);
    int fileSize = ftell(file);
    fseek(file, 0, SEEK_SET);

    char* str = new char[fileSize];
    fread(str, fileSize, 1, file);

    // Break all of the values in the file apart into tokens.

    char* nextToken = nullptr;
    token = azstrtok(str, 0, seps, &nextToken);

    // ncols = grid width
    validData = validData && (azstricmp(token, "ncols") == 0);
    token = azstrtok(NULL, 0, seps, &nextToken);
    width = atoi(token);

    // nrows = grid height
    token = azstrtok(NULL, 0, seps, &nextToken);
    validData = validData && (azstricmp(token, "nrows") == 0);
    token = azstrtok(NULL, 0, seps, &nextToken);
    height = atoi(token);

    // xllcorner = leftmost coordinate.  (Skip, we don't care about it)
    token = azstrtok(NULL, 0, seps, &nextToken);
    validData = validData && (azstricmp(token, "xllcorner") == 0);
    token = azstrtok(NULL, 0, seps, &nextToken);

    // yllcorner = bottommost coordinate.  (Skip, we don't care about it)
    token = azstrtok(NULL, 0, seps, &nextToken);
    validData = validData && (azstricmp(token, "yllcorner") == 0);
    token = azstrtok(NULL, 0, seps, &nextToken);

    // cellsize = size of each grid cell.  (Skip, we don't care about it)
    token = azstrtok(NULL, 0, seps, &nextToken);
    validData = validData && (azstricmp(token, "cellsize") == 0);
    token = azstrtok(NULL, 0, seps, &nextToken);

    // nodata_value = the value used for missing data.  We'll replace these with 0 height.
    token = azstrtok(NULL, 0, seps, &nextToken);
    validData = validData && (azstricmp(token, "nodata_value") == 0);
    token = azstrtok(NULL, 0, seps, &nextToken);
    nodataValue = atof(token);

    if (!validData)
    {
        // Bad file. not supported asc.
        delete[]str;
        fclose(file);
        return false;
    }

    image.Allocate(width, height);

    // Read in the pixel data

    float* p = image.GetData();
    int size = width * height;
    int i = 0;
    float pixelValue;
    float maxPixel = 0.0f;
    while (token != NULL && i < size)
    {
        token = azstrtok(NULL, 0, seps, &nextToken);
        if (token != NULL)
        {
            // Negative heights aren't supported, clamp to 0.
            pixelValue = max(0.0, atof(token));

            // If this is a location we specifically don't have data for, set it to 0.
            if (pixelValue == nodataValue)
            {
                pixelValue = 0.0f;
            }

            *p++ = pixelValue;
            maxPixel = max(maxPixel, pixelValue);
            i++;
        }
    }

    if (maxPixel > 0.0f)
    {
        // Scale our range down to 0 - 1
        float* p = image.GetData();
        for (i = 0; i < size; i++)
        {
            p[i] = clamp_tpl(p[i] / maxPixel, 0.0f, 1.0f);
        }
    }

    delete[]str;

    fclose(file);

    return true;
}