/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
    \\  /    A nd           | www.openfoam.com
     \\/     M anipulation  |
-------------------------------------------------------------------------------
    Copyright (C) 2011-2015 OpenFOAM Foundation
    Copyright (C) 2017-2023 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
    This file is part of OpenFOAM.

    OpenFOAM 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 3 of the License, or
    (at your option) any later version.

    OpenFOAM 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 OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.

\*---------------------------------------------------------------------------*/

#include "error.H"
#include "ITstream.H"
#include "SpanStream.H"
#include <algorithm>
#include <memory>

// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //

static std::unique_ptr<Foam::ITstream> emptyStreamPtr_;


// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //

namespace Foam
{

// Convert input sequence into a list of tokens.
// Return the number of tokens in the resulting list.
static label parseStream(ISstream& is, tokenList& tokens)
{
    tokens.clear();

    label count = 0;
    token tok;
    while (!is.read(tok).bad() && tok.good())
    {
        if (count >= tokens.size())
        {
            // Increase capacity (doubling) with min-size [64]
            tokens.resize(max(label(64), 2*tokens.size()));
        }

        tokens[count] = std::move(tok);
        ++count;
    }

    tokens.resize(count);

    return count;
}

} // End namespace Foam


// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //

Foam::ITstream& Foam::ITstream::empty_stream()
{
    if (emptyStreamPtr_)
    {
        emptyStreamPtr_->ITstream::clear();  // Ensure it really is empty
        emptyStreamPtr_->ITstream::seek(0);  // rewind(), but bypasss virtual
    }
    else
    {
        emptyStreamPtr_.reset(new ITstream(Foam::zero{}, "empty-stream"));
    }

    // Set stream as bad to indicate that this is an invald stream
    emptyStreamPtr_->setBad();

    return *emptyStreamPtr_;
}


Foam::tokenList Foam::ITstream::parse
(
    const UList<char>& input,
    IOstreamOption streamOpt
)
{
    ISpanStream is(input, streamOpt);

    tokenList tokens;
    parseStream(is, tokens);
    return tokens;
}


Foam::tokenList Foam::ITstream::parse
(
    const std::string& input,
    IOstreamOption streamOpt
)
{
    ISpanStream is(input, streamOpt);

    tokenList tokens;
    parseStream(is, tokens);
    return tokens;
}


Foam::tokenList Foam::ITstream::parse
(
    const char* input,
    IOstreamOption streamOpt
)
{
    ISpanStream is(input, strlen(input), streamOpt);

    tokenList tokens;
    parseStream(is, tokens);
    return tokens;
}


// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //

void Foam::ITstream::reserveCapacity(const label newCapacity)
{
    // Reserve - leave excess capacity for further appends

    label len = tokenList::size();

    if (len < newCapacity)
    {
        // Min-size (16) when starting from zero
        if (!len) len = 8;

        // Increase capacity. Strict doubling
        do
        {
            len *= 2;
        }
        while (len < newCapacity);

        tokenList::resize(len);
    }
}


// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //

Foam::ITstream::ITstream(const ITstream& is)
:
    Istream(static_cast<IOstreamOption>(is)),
    tokenList(is),
    name_(is.name_),
    tokenIndex_(0)
{
    setOpened();
    setGood();
}


Foam::ITstream::ITstream(ITstream&& is)
:
    Istream(static_cast<IOstreamOption>(is)),
    tokenList(std::move(static_cast<tokenList&>(is))),
    name_(std::move(is.name_)),
    tokenIndex_(0)
{
    setOpened();
    setGood();
}


Foam::ITstream::ITstream
(
    IOstreamOption streamOpt,
    const string& name
)
:
    Istream(IOstreamOption(streamOpt.format(), streamOpt.version())),
    tokenList(),
    name_(name),
    tokenIndex_(0)
{
    setOpened();
    setGood();
}


Foam::ITstream::ITstream
(
    const Foam::zero,
    const string& name,
    IOstreamOption streamOpt
)
:
    ITstream(streamOpt, name)
{}


Foam::ITstream::ITstream
(
    const UList<token>& tokens,
    IOstreamOption streamOpt,
    const string& name
)
:
    Istream(IOstreamOption(streamOpt.format(), streamOpt.version())),
    tokenList(tokens),
    name_(name),
    tokenIndex_(0)
{
    setOpened();
    setGood();
}


Foam::ITstream::ITstream
(
    List<token>&& tokens,
    IOstreamOption streamOpt,
    const string& name
)
:
    Istream(IOstreamOption(streamOpt.format(), streamOpt.version())),
    tokenList(std::move(tokens)),
    name_(name),
    tokenIndex_(0)
{
    setOpened();
    setGood();
}


Foam::ITstream::ITstream
(
    const UList<char>& input,
    IOstreamOption streamOpt,
    const string& name
)
:
    ITstream(streamOpt, name)
{
    ISpanStream is(input, streamOpt);

    parseStream(is, static_cast<tokenList&>(*this));
    ITstream::seek(0);  // rewind(), but bypasss virtual
}


Foam::ITstream::ITstream
(
    const std::string& input,
    IOstreamOption streamOpt,
    const string& name
)
:
    ITstream(streamOpt, name)
{
    ISpanStream is(input, streamOpt);

    parseStream(is, static_cast<tokenList&>(*this));
    ITstream::seek(0);  // rewind(), but bypasss virtual
}


Foam::ITstream::ITstream
(
    const char* input,
    IOstreamOption streamOpt,
    const string& name
)
:
    ITstream(streamOpt, name)
{
    ISpanStream is(input, strlen(input), streamOpt);

    parseStream(is, static_cast<tokenList&>(*this));
    ITstream::seek(0);  // rewind(), but bypasss virtual
}


// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //

void Foam::ITstream::print(Ostream& os) const
{
    os  << "ITstream : " << name_.c_str() << ", line ";

    if (tokenList::empty())
    {
        os  << lineNumber();
    }
    else
    {
        const tokenList& toks = *this;

        os  << toks.front().lineNumber();

        if (toks.front().lineNumber() < toks.back().lineNumber())
        {
            os  << '-' << toks.back().lineNumber();
        }
    }
    os  << ", ";

    IOstream::print(os);
}


std::string Foam::ITstream::toString() const
{
    if (tokenList::empty())
    {
        return std::string();
    }
    else if (tokenList::size() == 1 && tokenList::front().isStringType())
    {
        // Already a string-type (WORD, STRING, ...). Just copy.
        return tokenList::front().stringToken();
    }

    // Stringify
    OCharStream buf;
    buf.precision(16);      // Some reasonably high precision

    auto iter = tokenList::cbegin();
    const auto last = tokenList::cend();

    // Note: could also just use the buffer token-wise

    // Contents - space separated
    if (iter != last)
    {
        buf << *iter;

        for (++iter; (iter != last); (void)++iter)
        {
            buf << token::SPACE << *iter;
        }
    }

    const auto view = buf.view();

    return std::string(view.data(), view.size());
}


const Foam::token& Foam::ITstream::peek() const
{
    // Use putback token if it exists
    if (Istream::hasPutback())
    {
        return Istream::peekBack();
    }

    return peekNoFail(tokenIndex_);
}


Foam::token& Foam::ITstream::currentToken()
{
    if (tokenIndex_ < 0 || tokenIndex_ >= tokenList::size())
    {
        FatalIOErrorInFunction(*this)
            << "Token index " << tokenIndex_ << " out of range [0,"
            << tokenList::size() << "]\n"
            << abort(FatalIOError);
    }

    return tokenList::operator[](tokenIndex_);
}


void Foam::ITstream::seek(label pos)
{
    lineNumber_ = 0;
    const tokenList& toks = *this;
    const label nToks = toks.size();

    if (!pos)
    {
        // Seek begin (rewind)
        tokenIndex_ = 0;

        if (nToks)
        {
            lineNumber_ = toks.front().lineNumber();
        }

        setOpened();
        setGood();
    }
    else if (pos < 0 || pos >= nToks)
    {
        // Seek end (-1) or seek is out of range
        tokenIndex_ = nToks;

        if (nToks)
        {
            lineNumber_ = toks.back().lineNumber();
        }

        setEof();
    }
    else
    {
        // Seek middle (from the beginning)
        tokenIndex_ = pos;

        if (nToks)
        {
            lineNumber_ = toks[tokenIndex_].lineNumber();
        }

        setOpened();
        setGood();
    }
}


bool Foam::ITstream::skip(label n)
{
    if (!n)
    {
        // No movement - just check the current range
        return (tokenIndex_ >= 0 && tokenIndex_ < tokenList::size());
    }

    tokenIndex_ += n;  // Move forward (+ve) or backwards (-ve)

    bool noError = true;

    if (tokenIndex_ < 0)
    {
        // Underflow range
        noError = false;
        tokenIndex_ = 0;
    }
    else if (tokenIndex_ >= tokenList::size())
    {
        // Overflow range
        noError = false;
        tokenIndex_ = tokenList::size();

        if (!tokenList::empty())
        {
            // The closest reference lineNumber
            lineNumber_ = tokenList::back().lineNumber();
        }
    }

    // Update stream information
    if (tokenIndex_ < tokenList::size())
    {
        lineNumber_ = tokenList::operator[](tokenIndex_).lineNumber();
        setOpened();
        setGood();
    }
    else
    {
        setEof();
    }

    return noError;
}


Foam::Istream& Foam::ITstream::read(token& tok)
{
    // Use putback token if it exists
    if (Istream::getBack(tok))
    {
        lineNumber_ = tok.lineNumber();
        return *this;
    }

    tokenList& toks = *this;
    const label nToks = toks.size();

    if (tokenIndex_ < nToks)
    {
        tok = toks[tokenIndex_++];
        lineNumber_ = tok.lineNumber();

        if (tokenIndex_ == nToks)
        {
            setEof();
        }
    }
    else
    {
        if (eof())
        {
            FatalIOErrorInFunction(*this)
                << "attempt to read beyond EOF"
                << exit(FatalIOError);
            setBad();
        }
        else
        {
            setEof();
        }

        tok.reset();

        if (nToks)
        {
            tok.lineNumber(toks.back().lineNumber());
        }
        else
        {
            tok.lineNumber(this->lineNumber());
        }
    }

    return *this;
}


Foam::labelRange Foam::ITstream::find
(
    const token::punctuationToken delimOpen,
    const token::punctuationToken delimClose,
    label pos
) const
{
    if (pos < 0)
    {
        pos = tokenIndex_;
    }

    labelRange slice;

    for (label depth = 0; pos < tokenList::size(); ++pos)
    {
        const token& tok = tokenList::operator[](pos);

        if (tok.isPunctuation())
        {
            if (tok.isPunctuation(delimOpen))
            {
                if (!depth)
                {
                    // Initial open delimiter
                    slice.start() = pos;
                }

                ++depth;
            }
            else if (tok.isPunctuation(delimClose))
            {
                --depth;

                if (depth < 0)
                {
                    // A closing delimiter without an open!
                    // Raise error?
                    break;
                }
                if (!depth)
                {
                    // The end - include delimiter into the count
                    slice.size() = (pos - slice.start()) + 1;
                    break;
                }
            }
        }
    }

    return slice;
}


Foam::ITstream Foam::ITstream::extract(const labelRange& range)
{
    ITstream result
    (
        static_cast<IOstreamOption>(*this),
        this->name()
    );
    result.setLabelByteSize(this->labelByteSize());
    result.setScalarByteSize(this->scalarByteSize());

    // Validate the slice range of list
    const labelRange slice(range.subset0(tokenList::size()));

    if (!slice.good())
    {
        // No-op
        return result;
    }

    auto first = tokenList::begin(slice.begin_value());
    auto last = tokenList::begin(slice.end_value());

    result.resize(label(last - first));

    // Move tokens into result list
    std::move(first, last, result.begin());
    result.seek(0);  // rewind

    (void) remove(slice);  // Adjust the original list

    return result;
}


Foam::label Foam::ITstream::remove(const labelRange& range)
{
    // Validate the slice range of list
    const labelRange slice(range.subset0(tokenList::size()));

    if (!slice.good())
    {
        // No-op
        return 0;
    }

    if (slice.end_value() >= tokenList::size())
    {
        // Remove entire tail
        tokenList::resize(slice.begin_value());
    }
    else
    {
        // Attempt to adjust the current token index to something sensible...
        if (slice.contains(tokenIndex_))
        {
            // Within the removed slice - reposition tokenIndex before it
            seek(slice.begin_value());
            skip(-1);
        }
        else if (tokenIndex_ >= slice.end_value())
        {
            // After the removed slice - reposition tokenIndex relatively
            skip(-slice.size());
        }

        // Move tokens down in the list
        std::move
        (
            tokenList::begin(slice.end_value()),
            tokenList::end(),
            tokenList::begin(slice.begin_value())
        );

        // Truncate
        tokenList::resize(tokenList::size() - slice.size());
    }

    if (tokenIndex_ >= tokenList::size())
    {
        tokenIndex_ = tokenList::size();
        setEof();
    }
    else if (tokenIndex_ >= 0 && tokenIndex_ < tokenList::size())
    {
        lineNumber_ = tokenList::operator[](tokenIndex_).lineNumber();
    }

    return slice.size();
}


// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //

Foam::Istream& Foam::ITstream::read(char&)
{
    NotImplemented;
    return *this;
}


Foam::Istream& Foam::ITstream::read(word&)
{
    NotImplemented;
    return *this;
}


Foam::Istream& Foam::ITstream::read(string&)
{
    NotImplemented;
    return *this;
}


Foam::Istream& Foam::ITstream::read(label&)
{
    NotImplemented;
    return *this;
}


Foam::Istream& Foam::ITstream::read(float&)
{
    NotImplemented;
    return *this;
}


Foam::Istream& Foam::ITstream::read(double&)
{
    NotImplemented;
    return *this;
}


Foam::Istream& Foam::ITstream::readRaw(char*, std::streamsize)
{
    NotImplemented;
    return *this;
}


Foam::Istream& Foam::ITstream::read(char*, std::streamsize)
{
    NotImplemented;
    return *this;
}


void Foam::ITstream::rewind()
{
    ITstream::seek(0);
}


void Foam::ITstream::add_tokens(const token& tok)
{
    reserveCapacity(tokenIndex_ + 1);

    tokenList::operator[](tokenIndex_) = tok;
    ++tokenIndex_;
}


void Foam::ITstream::add_tokens(token&& tok)
{
    reserveCapacity(tokenIndex_ + 1);

    tokenList::operator[](tokenIndex_) = std::move(tok);
    ++tokenIndex_;
}


void Foam::ITstream::add_tokens(const UList<token>& toks)
{
    const label len = toks.size();
    reserveCapacity(tokenIndex_ + len);

    std::copy_n(toks.begin(), len, tokenList::begin(tokenIndex_));
    tokenIndex_ += len;
}


void Foam::ITstream::add_tokens(List<token>&& toks)
{
    const label len = toks.size();
    reserveCapacity(tokenIndex_ + len);

    std::move(toks.begin(), toks.end(), tokenList::begin(tokenIndex_));
    tokenIndex_ += len;
    toks.clear();
}


// * * * * * * * * * * * * * * * Member Operators  * * * * * * * * * * * * * //

void Foam::ITstream::operator=(const ITstream& is)
{
    // Self-assignment is a no-op
    if (this != &is)
    {
        Istream::operator=(is);
        tokenList::operator=(is);
        name_ = is.name_;
        ITstream::seek(0);  // rewind(), but bypasss virtual
    }
}


void Foam::ITstream::operator=(const UList<token>& toks)
{
    tokenList::operator=(toks);
    ITstream::seek(0);  // rewind(), but bypasss virtual
}


void Foam::ITstream::operator=(List<token>&& toks)
{
    tokenList::operator=(std::move(toks));
    ITstream::seek(0);  // rewind(), but bypasss virtual
}


// ************************************************************************* //
