/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
    \\  /    A nd           | www.openfoam.com
     \\/     M anipulation  |
-------------------------------------------------------------------------------
    Copyright (C) 2011-2016 OpenFOAM Foundation
    Copyright (C) 2015-2020 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 "DimensionedgpuField.H"
#include "dimensionedType.H"
#include "Time.H"

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

// Check mesh for two fields
#define checkField(df1, df2, op)                                    \
if (&(df1).mesh() != &(df2).mesh())                                 \
{                                                                   \
    FatalErrorInFunction                                            \
        << "different mesh for fields "                             \
        << (df1).name() << " and " << (df2).name()                  \
        << " during operation " <<  op                              \
        << abort(FatalError);                                       \
}

#define checkhostField(df1, df2, op)                                    \
if (&(df1).mesh().hostmesh() != &(df2).mesh())                                 \
{                                                                   \
    FatalErrorInFunction                                            \
        << "different mesh for host fields "                             \
        << (df1).name() << " and " << (df2).name()                  \
        << " during operation " <<  op                              \
        << abort(FatalError);                                       \
}

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

template<class Type, class GeoMesh>
void Foam::DimensionedgpuField<Type, GeoMesh>::checkFieldSize() const
{
    const label fieldSize = this->size();
    if (fieldSize)
    {
        const label meshSize = GeoMesh::size(this->mesh_);
        if (fieldSize != meshSize)
        {
            FatalErrorInFunction
                << "size of field = " << fieldSize
                << " is not the same as the size of mesh = "
                << meshSize
                << abort(FatalError);
        }
    }
}


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

template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
	const IOobject& io,
    const Mesh& mesh,
    const dimensionSet& dims,
    const Field<Type>& field
)
:
    regIOobject(io),
    gpuField<Type>(field),
    mesh_(mesh),
    dimensions_(dims),
    oriented_()
{
    checkFieldSize();
}

template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
	const IOobject& io,
    const Mesh& mesh,
    const dimensionSet& dims,
    const gpuField<Type>& field
)
:
	regIOobject(io),
    gpuField<Type>(field),
    mesh_(mesh),
    dimensions_(dims),
    oriented_()
{
    checkFieldSize();
}

template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
	const IOobject& io,
    const Mesh& mesh,
    const dimensionSet& dims,
    Field<Type>&& field
)
:
	regIOobject(io),
    gpuField<Type>(std::move(field)),
    mesh_(mesh),
    dimensions_(dims)
{
    //Info<<"Move construct dimensioned for " << io.name() << nl;
    checkFieldSize();
}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
	const IOobject& io,
    const Mesh& mesh,
    const dimensionSet& dims,
    List<Type>&& field
)
:
	regIOobject(io),
    gpuField<Type>(std::move(field)),
    mesh_(mesh),
    dimensions_(dims)
{
    //Info<<"Move construct dimensioned for " << io.name() << nl;
    checkFieldSize();
}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
	const IOobject& io,
    const Mesh& mesh,
    const dimensionSet& dims,
    const tmp<Field<Type>>& tfield
)
:
	regIOobject(io),
    gpuField<Type>(tfield()),
    mesh_(mesh),
    dimensions_(dims),
    oriented_()
{
    tfield.clear();
    checkFieldSize();
}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
	const IOobject& io,
    const Mesh& mesh,
    const dimensionSet& dims,
    const tmp<gpuField<Type>>& tfield
)
:
	regIOobject(io),
    gpuField<Type>(tfield.constCast(), tfield.movable()),
    mesh_(mesh),
    dimensions_(dims),
    oriented_()
{
    tfield.clear();
    checkFieldSize();
}

template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
	const IOobject& io,
    const Mesh& mesh,
    const dimensionSet& dims,
    const bool checkIOFlags
)
:
	regIOobject(io),
    gpuField<Type>(GeoMesh::size(mesh)),
    mesh_(mesh),
    dimensions_(dims),
    oriented_()
{
    if (checkIOFlags)
    {
        readIfPresent();
    }
}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
	const IOobject& io,
    const Mesh& mesh,
    const dimensioned<Type>& dt,
    const bool checkIOFlags
)
:
	regIOobject(io),
    gpuField<Type>(GeoMesh::size(mesh), dt.value()),
    mesh_(mesh),
    dimensions_(dt.dimensions()),
    oriented_()
{
    if (checkIOFlags)
    {
        readIfPresent();
    }
}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
    const DimensionedgpuField<Type, GeoMesh>& df
)
:
	regIOobject(df),
    gpuField<Type>(df),
    mesh_(df.mesh_),
    dimensions_(df.dimensions_),
    oriented_(df.oriented_)
{}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
    DimensionedgpuField<Type, GeoMesh>&& df
)
:
    DimensionedgpuField<Type, GeoMesh>(df, true)
{}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
    DimensionedgpuField<Type, GeoMesh>& df,
    bool reuse
)
:
	regIOobject(df, reuse),
    gpuField<Type>(df, reuse),
    mesh_(df.mesh_),
    dimensions_(df.dimensions_),
    oriented_(df.oriented_)
{}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
    const tmp<DimensionedgpuField<Type, GeoMesh>>& tdf
)
:
    DimensionedgpuField<Type, GeoMesh>(tdf.constCast(), tdf.movable())
{
    tdf.clear();
}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
    const IOobject& io,
    const DimensionedgpuField<Type, GeoMesh>& df
)
:
    regIOobject(io),
    gpuField<Type>(df),
    mesh_(df.mesh_),
    dimensions_(df.dimensions_),
    oriented_(df.oriented_)
{}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
    const IOobject& io,
    DimensionedgpuField<Type, GeoMesh>&& df
)
:
    DimensionedgpuField<Type, GeoMesh>(io, df, true)
{}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
    const IOobject& io,
    DimensionedgpuField<Type, GeoMesh>& df,
    bool reuse
)
:
    regIOobject(io, df),
    gpuField<Type>(df, reuse),
    mesh_(df.mesh_),
    dimensions_(df.dimensions_),
    oriented_(df.oriented_)
{}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
    const IOobject& io,
    const tmp<DimensionedgpuField<Type, GeoMesh>>& tdf
)
:
    DimensionedgpuField<Type, GeoMesh>(io, tdf.constCast(), tdf.movable())
{
    tdf.clear();
}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
    const word& newName,
    const DimensionedgpuField<Type, GeoMesh>& df
)
:
    regIOobject(newName, df, newName != df.name()),
    gpuField<Type>(df),
    mesh_(df.mesh_),
    dimensions_(df.dimensions_),
    oriented_(df.oriented_)
{}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
    const word& newName,
    DimensionedgpuField<Type, GeoMesh>&& df
)
:
    DimensionedgpuField<Type, GeoMesh>(newName, df, true)
{}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
    const word& newName,
    DimensionedgpuField<Type, GeoMesh>& df,
    bool reuse
)
:
    regIOobject(newName, df, true),
    gpuField<Type>(df, reuse),
    mesh_(df.mesh_),
    dimensions_(df.dimensions_),
    oriented_(df.oriented_)
{}


template<class Type, class GeoMesh>
Foam::DimensionedgpuField<Type, GeoMesh>::DimensionedgpuField
(
    const word& newName,
    const tmp<DimensionedgpuField<Type, GeoMesh>>& tdf
)
:
    DimensionedgpuField<Type, GeoMesh>(newName, tdf.constCast(), tdf.movable())
{
    tdf.clear();
}


template<class Type, class GeoMesh>
Foam::tmp<Foam::DimensionedgpuField<Type, GeoMesh>>
Foam::DimensionedgpuField<Type, GeoMesh>::clone() const
{
    return tmp<DimensionedgpuField<Type, GeoMesh>>::New(*this);
}


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

template<class Type, class GeoMesh>
Foam::tmp
<
    Foam::DimensionedgpuField
    <
        typename Foam::DimensionedgpuField<Type, GeoMesh>::cmptType, GeoMesh
    >
>
Foam::DimensionedgpuField<Type, GeoMesh>::component
(
    const direction d
) const
{
    auto tresult = tmp<DimensionedgpuField<cmptType, GeoMesh>>::New
    (
        IOobject
        (
            name() + ".component(" + ::Foam::name(d) + ')',
            instance(),
            db()
        ),
        mesh_,
        dimensions_
    );

    Foam::component(tresult.ref(), *this, d);

    return tresult;
}


template<class Type, class GeoMesh>
void Foam::DimensionedgpuField<Type, GeoMesh>::replace
(
    const direction d,
    const DimensionedgpuField
    <
        typename DimensionedgpuField<Type, GeoMesh>::cmptType, GeoMesh
    >& df
)
{
    gpuField<Type>::replace(d, df);
}


template<class Type, class GeoMesh>
void Foam::DimensionedgpuField<Type, GeoMesh>::replace
(
    const direction d,
    const tmp
    <
        DimensionedgpuField
        <
            typename DimensionedgpuField<Type, GeoMesh>::cmptType, GeoMesh
        >
    >& tdf
)
{
    replace(d, tdf());
    tdf.clear();
}


template<class Type, class GeoMesh>
Foam::tmp<Foam::DimensionedgpuField<Type, GeoMesh>>
Foam::DimensionedgpuField<Type, GeoMesh>::T() const
{
    auto tresult = tmp<DimensionedgpuField<Type, GeoMesh>>::New
    (
        IOobject
        (
            name() + ".T()",
            instance(),
            db()
        ),
        mesh_,
        dimensions_
    );

    Foam::T(tresult.ref(), *this);

    return tresult;
}


template<class Type, class GeoMesh>
Foam::dimensioned<Type> Foam::DimensionedgpuField<Type, GeoMesh>::average() const
{
    return
        dimensioned<Type>
        (
		    this->name() + ".average()",
            this->dimensions(),
            gAverage(field())
        );
}


template<class Type, class GeoMesh>
Foam::dimensioned<Type> Foam::DimensionedgpuField<Type, GeoMesh>::weightedAverage
(
    const DimensionedgpuField<scalar, GeoMesh>& weightField
) const
{
    return
        dimensioned<Type>
        (
            this->name() + ".weightedAverage(weights)",
            this->dimensions(),
            gSum(weightField*field())/gSum(weightField)
        );
}


template<class Type, class GeoMesh>
Foam::dimensioned<Type> Foam::DimensionedgpuField<Type, GeoMesh>::weightedAverage
(
    const tmp<DimensionedgpuField<scalar, GeoMesh>>& tweightField
) const
{
    dimensioned<Type> result = weightedAverage(tweightField());
    tweightField.clear();
    return result;
}


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

template<class Type, class GeoMesh>
void Foam::DimensionedgpuField<Type, GeoMesh>::operator=
(
    const DimensionedgpuField<Type, GeoMesh>& df
)
{
    if (this == &df)
    {
        return;  // Self-assignment is a no-op
    }

    checkField(*this, df, "=");

    dimensions_ = df.dimensions();
    oriented_ = df.oriented();
    gpuField<Type>::operator=(df);
}


template<class Type, class GeoMesh>
void Foam::DimensionedgpuField<Type, GeoMesh>::operator=
(
    const tmp<DimensionedgpuField<Type, GeoMesh>>& tdf
)
{
    auto& df = tdf.constCast();

    if (this == &df)
    {
        return;  // Self-assignment is a no-op
    }

    checkField(*this, df, "=");

    dimensions_ = df.dimensions();
    oriented_ = df.oriented();
    this->transfer(df);
    tdf.clear();
}

template<class Type, class GeoMesh>
void Foam::DimensionedgpuField<Type, GeoMesh>::operator=
(
    const DimField& df
)
{
    checkhostField(*this, df, "=");
    dimensions_ = df.dimensions();
    oriented_ = df.oriented();
    this->field() = df.field();
}


template<class Type, class GeoMesh>
void Foam::DimensionedgpuField<Type, GeoMesh>::operator=
(
    const tmp<DimField>& tdf
)
{
    auto& df = tdf.constCast();
    checkhostField(*this, df, "=");

    dimensions_ = df.dimensions();
    oriented_ = df.oriented();
    this->field() = df.field();
	tdf.clear();
}


template<class Type, class GeoMesh>
void Foam::DimensionedgpuField<Type, GeoMesh>::operator=
(
    const dimensioned<Type>& dt
)
{
    dimensions_ = dt.dimensions();
    gpuField<Type>::operator=(dt.value());
}


#define COMPUTED_ASSIGNMENT(TYPE, op)                                          \
                                                                               \
template<class Type, class GeoMesh>                                            \
void Foam::DimensionedgpuField<Type, GeoMesh>::operator op                        \
(                                                                              \
    const DimensionedgpuField<TYPE, GeoMesh>& df                                  \
)                                                                              \
{                                                                              \
    checkField(*this, df, #op);                                                \
                                                                               \
    dimensions_ op df.dimensions();                                            \
    oriented_ op df.oriented();                                                \
    gpuField<Type>::operator op(df);                                              \
}                                                                              \
                                                                               \
template<class Type, class GeoMesh>                                            \
void Foam::DimensionedgpuField<Type, GeoMesh>::operator op                        \
(                                                                              \
    const tmp<DimensionedgpuField<TYPE, GeoMesh>>& tdf                            \
)                                                                              \
{                                                                              \
    operator op(tdf());                                                        \
    tdf.clear();                                                               \
}                                                                              \
                                                                               \
template<class Type, class GeoMesh>                                            \
void Foam::DimensionedgpuField<Type, GeoMesh>::operator op                        \
(                                                                              \
    const dimensioned<TYPE>& dt                                                \
)                                                                              \
{                                                                              \
    dimensions_ op dt.dimensions();                                            \
    gpuField<Type>::operator op(dt.value());                                      \
}

COMPUTED_ASSIGNMENT(Type, +=)
COMPUTED_ASSIGNMENT(Type, -=)
COMPUTED_ASSIGNMENT(scalar, *=)
COMPUTED_ASSIGNMENT(scalar, /=)

#undef COMPUTED_ASSIGNMENT

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

#undef checkField
#undef checkhostField

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
#include "DimensionedgpuFieldIO.C"
#include "DimensionedgpuFieldFunctions.C"

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