/**********************************************************************
 *
 * Name:     mitab_datfile.cpp
 * Project:  MapInfo TAB Read/Write library
 * Language: C++
 * Purpose:  Implementation of the MIDDATAFile class used to handle
 *           reading/writing of the MID/MIF files
 * Author:   Stephane Villeneuve, stephane.v@videotron.ca
 *
 **********************************************************************
 * Copyright (c) 1999, 2000, Stephane Villeneuve
 *
 * 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.
 **********************************************************************/

#include "cpl_port.h"
#include "mitab.h"

#include <cstdarg>
#include <cstddef>

#include "cpl_conv.h"
#include "cpl_csv.h"
#include "cpl_error.h"
#include "cpl_string.h"
#include "cpl_vsi.h"
#include "mitab_priv.h"


/*=====================================================================
 *                      class MIDDATAFile
 *
 *====================================================================*/

MIDDATAFile::MIDDATAFile( const char* pszEncoding ) :
    m_fp(nullptr),
    m_pszDelimiter("\t"),  // Encom 2003 (was NULL).
    m_pszFname(nullptr),
    m_eAccessMode(TABRead),
    m_dfXMultiplier(1.0),
    m_dfYMultiplier(1.0),
    m_dfXDisplacement(0.0),
    m_dfYDisplacement(0.0),
    m_bEof(FALSE),
    m_osEncoding(pszEncoding)
{
}

MIDDATAFile::~MIDDATAFile() { Close(); }

void MIDDATAFile::SaveLine(const char *pszLine)
{
    if(pszLine == nullptr)
    {
        m_osSavedLine.clear();
    }
    else
    {
        m_osSavedLine = pszLine;
    }
}

const char *MIDDATAFile::GetSavedLine() { return m_osSavedLine.c_str(); }

int MIDDATAFile::Open(const char *pszFname, const char *pszAccess)
{
    if(m_fp)
    {
        return -1;
    }

    // Validate access mode and make sure we use Text access.
    if(STARTS_WITH_CI(pszAccess, "r"))
    {
        m_eAccessMode = TABRead;
        pszAccess = "rt";
    }
    else if(STARTS_WITH_CI(pszAccess, "w"))
    {
        m_eAccessMode = TABWrite;
        pszAccess = "wt";
    }
    else
    {
        return -1;
    }

    // Open file for reading.
    m_pszFname = CPLStrdup(pszFname);
    m_fp = VSIFOpenL(m_pszFname, pszAccess);

    if(m_fp == nullptr)
    {
        CPLFree(m_pszFname);
        m_pszFname = nullptr;
        return -1;
    }

    SetEof(FALSE);
    return 0;
}

int MIDDATAFile::Rewind()
{
    if(m_fp == nullptr || m_eAccessMode == TABWrite)
        return -1;

    else
    {
        VSIRewindL(m_fp);
        SetEof(FALSE);
    }
    return 0;
}

int MIDDATAFile::Close()
{
    if(m_fp == nullptr)
        return 0;

    // Close file
    VSIFCloseL(m_fp);
    m_fp = nullptr;

    // clear readline buffer.
    CPLReadLineL(nullptr);

    CPLFree(m_pszFname);
    m_pszFname = nullptr;

    return 0;
}

const char *MIDDATAFile::GetLine()
{
    if(m_eAccessMode != TABRead)
    {
        CPLAssert(false);
        return nullptr;
    }

    static const int nMaxLineLength = atoi(
        CPLGetConfigOption("MITAB_MAX_LINE_LENGTH", "1000000"));
    const char *pszLine = CPLReadLine2L(m_fp, nMaxLineLength, nullptr);

    if(pszLine == nullptr)
    {
        if( strstr(CPLGetLastErrorMsg(), "Maximum number of characters allowed reached") )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Maximum number of characters allowed reached. "
                     "You can set the MITAB_MAX_LINE_LENGTH configuration option "
                     "to the desired number of bytes (or -1 for unlimited)");
        }
        SetEof(TRUE);
        m_osLastRead.clear();
    }
    else
    {
        // Skip leading spaces and tabs except if the delimiter is tab.
        while(*pszLine == ' ' ||
                          (*m_pszDelimiter != '\t' && *pszLine == '\t'))
            pszLine++;

        m_osLastRead = pszLine;
    }

#if DEBUG_VERBOSE
    if(pszLine)
        CPLDebug("MITAB", "pszLine: %s", pszLine);
#endif

    return pszLine;
}

const char *MIDDATAFile::GetLastLine()
{
    // Return NULL if EOF.
    if(GetEof())
    {
        return nullptr;
    }
    if(m_eAccessMode == TABRead)
    {
#if DEBUG_VERBOSE
        CPLDebug("MITAB", "m_osLastRead: %s", m_osLastRead.c_str());
#endif
        return m_osLastRead.c_str();
    }

    // We should never get here.  Read/Write mode not implemented.
    CPLAssert(false);
    return nullptr;
}

char** MIDDATAFile::GetTokenizedNextLine()
{
    static const int nMaxLineLength = atoi(
        CPLGetConfigOption("MITAB_MAX_LINE_LENGTH", "1000000"));
    char** papszTokens = CSVReadParseLine3L( m_fp,
                                             nMaxLineLength,
                                             m_pszDelimiter,
                                             true, // bHonourStrings
                                             false, // bKeepLeadingAndClosingQuotes
                                             false, // bMergeDelimiter
                                             false // bSkipBOM
                                            );
    if( papszTokens == nullptr )
    {
        if( strstr(CPLGetLastErrorMsg(), "Maximum number of characters allowed reached") )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Maximum number of characters allowed reached. "
                     "You can set the MITAB_MAX_LINE_LENGTH configuration option "
                     "to the desired number of bytes (or -1 for unlimited)");
        }
        SetEof(TRUE);
    }
    return papszTokens;
}

void MIDDATAFile::WriteLine(const char *pszFormat, ...)
{
    va_list args;

    if(m_eAccessMode == TABWrite && m_fp)
    {
        va_start(args, pszFormat);
        CPLString osStr;
        osStr.vPrintf(pszFormat, args);
        VSIFWriteL(osStr.c_str(), 1, osStr.size(), m_fp);
        va_end(args);
    }
    else
    {
        CPLAssert(false);
    }
}

void MIDDATAFile::SetTranslation( double dfXMul,double dfYMul,
                                  double dfXTran, double dfYTran )
{
    m_dfXMultiplier = dfXMul;
    m_dfYMultiplier = dfYMul;
    m_dfXDisplacement = dfXTran;
    m_dfYDisplacement = dfYTran;
}

double MIDDATAFile::GetXTrans(double dfX)
{
    return (dfX * m_dfXMultiplier) + m_dfXDisplacement;
}

double MIDDATAFile::GetYTrans(double dfY)
{
    return (dfY * m_dfYMultiplier) + m_dfYDisplacement;
}

GBool MIDDATAFile::IsValidFeature(const char *pszString)
{
    char **papszToken = CSLTokenizeString(pszString);

    if(CSLCount(papszToken) == 0)
    {
        CSLDestroy(papszToken);
        return FALSE;
    }

    if(EQUAL(papszToken[0], "NONE") || EQUAL(papszToken[0], "POINT") ||
       EQUAL(papszToken[0], "LINE") || EQUAL(papszToken[0], "PLINE") ||
       EQUAL(papszToken[0], "REGION") || EQUAL(papszToken[0], "ARC") ||
       EQUAL(papszToken[0], "TEXT") || EQUAL(papszToken[0], "RECT") ||
       EQUAL(papszToken[0], "ROUNDRECT") || EQUAL(papszToken[0], "ELLIPSE") ||
       EQUAL(papszToken[0], "MULTIPOINT") || EQUAL(papszToken[0], "COLLECTION"))
    {
        CSLDestroy(papszToken);
        return TRUE;
    }

    CSLDestroy(papszToken);
    return FALSE;
}

GBool MIDDATAFile::GetEof() { return m_bEof; }

const CPLString& MIDDATAFile::GetEncoding() const
{
    return m_osEncoding;
}

void MIDDATAFile::SetEncoding( const CPLString& osEncoding )
{
    m_osEncoding = osEncoding;
}

void MIDDATAFile::SetEof(GBool bEof) { m_bEof = bEof; }
