/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  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) 2019-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 "cyclicAMIgpuFvPatch.H"
#include "addToRunTimeSelectionTable.H"
#include "gpufvMesh.H"
#include "Time.H"
#include "transform.H"
#include "surfacegpuFields.H"

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

namespace Foam
{
    defineTypeNameAndDebug(cyclicAMIgpuFvPatch, 0);
    addToRunTimeSelectionTable(gpufvPatch, cyclicAMIgpuFvPatch, polyPatch);
    addNamedToRunTimeSelectionTable
    (
        gpufvPatch,
        cyclicAMIgpuFvPatch,
        polyPatch,
        cyclicPeriodicAMI
    );

    struct cyclicAMIFvPatchMakeWeightsFunctor
    {
        __host__ __device__
        scalar operator () (const scalar& deltas, const scalar& nbrDeltas)
        {
            return nbrDeltas/(deltas + nbrDeltas);
        }
    };
}


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

// void Foam::cyclicAMIFvPatch::newInternalProcFaces
// (
//     label& newFaces,
//     label& newProcFaces
// ) const
// {
//     const labelListList& addSourceFaces = AMI().srcAddress();
//
//     // Add new faces as many weights for AMI
//     forAll (addSourceFaces, faceI)
//     {
//         const labelList& nbrFaceIs = addSourceFaces[faceI];
//
//         forAll (nbrFaceIs, j)
//         {
//             label nbrFaceI = nbrFaceIs[j];
//
//             if (nbrFaceI < neighbPatch().size())
//             {
//                 // local faces
//                 newFaces++;
//             }
//             else
//             {
//                 // Proc faces
//                 newProcFaces++;
//             }
//         }
//     }
// }


bool Foam::cyclicAMIgpuFvPatch::coupled() const
{
    return
        Pstream::parRun()
     || !this->boundaryMesh().mesh().hostmesh().time().processorCase();
}


void Foam::cyclicAMIgpuFvPatch::makeWeights(scalargpuField& w) const
{
    if (coupled())
    {
        const cyclicAMIgpuFvPatch& nbrPatch = neighbFvPatch();

        const scalargpuField deltas(nf() & coupledgpuFvPatch::delta());

        tmp<scalargpuField> tnbrDeltas;
        if (applyLowWeightCorrection())
        {
            tnbrDeltas =
                interpolate
                (
                    nbrPatch.nf() & nbrPatch.coupledgpuFvPatch::delta(),
                    scalargpuField(this->size(), 1.0)
                );
        }
        else
        {
            tnbrDeltas =
                interpolate(nbrPatch.nf() & nbrPatch.coupledgpuFvPatch::delta());
        }

        const scalargpuField& nbrDeltas = tnbrDeltas();

        thrust::transform
        (
            deltas.begin(),
            deltas.end(),
            nbrDeltas.begin(),
            w.begin(),
            cyclicAMIFvPatchMakeWeightsFunctor()
        );
    }
    else
    {
        // Behave as uncoupled patch
        gpufvPatch::makeWeights(w);
    }
}


void Foam::cyclicAMIgpuFvPatch::makeDeltaCoeffs(scalargpuField& coeffs) const
{
    // Apply correction to default coeffs
}


void Foam::cyclicAMIgpuFvPatch::makeNonOrthoDeltaCoeffs(scalargpuField& coeffs) const
{
    // Apply correction to default coeffs
    //coeffs = Zero;
}


void Foam::cyclicAMIgpuFvPatch::makeNonOrthoCorrVectors(vectorgpuField& vecs) const
{
    // Apply correction to default vectors
    //vecs = Zero;
}


Foam::tmp<Foam::vectorgpuField> Foam::cyclicAMIgpuFvPatch::delta() const
{
    const cyclicAMIgpuFvPatch& nbrPatch = neighbFvPatch();

    if (coupled())
    {
        const vectorgpuField patchD(coupledgpuFvPatch::delta());

        tmp<vectorgpuField> tnbrPatchD;
        if (applyLowWeightCorrection())
        {
            tnbrPatchD =
                interpolate
                (
                    nbrPatch.coupledgpuFvPatch::delta(),
                    vectorgpuField(this->size(), Zero)
                );
        }
        else
        {
            tnbrPatchD = interpolate(nbrPatch.coupledgpuFvPatch::delta());
        }

        const vectorgpuField& nbrPatchD = tnbrPatchD();

        auto tpdv = tmp<vectorgpuField>::New(patchD.size());
        vectorgpuField& pdv = tpdv.ref();

        // do the transformation if necessary
        if (parallel())
        {
            thrust::transform
            (
                patchD.begin(),
                patchD.end(),
                nbrPatchD.begin(),
                pdv.begin(),
                subtractOperatorFunctor<vector,vector,vector>()
            );
        }
        else
        {
            tensor t = gpuForwardT().first();
			
            thrust::transform
            (
                patchD.begin(),
                patchD.end(),
                thrust::make_transform_iterator
                (
                    nbrPatchD.begin(),
                    transformBinaryFunctionSFFunctor<tensor,vector,vector>(t)
                ),
                pdv.begin(),
                subtractOperatorFunctor<vector,vector,vector>()
            );
        }

        return tpdv;
    }
    else
    {
        return coupledgpuFvPatch::delta();
    }
}


Foam::tmp<Foam::labelgpuField> Foam::cyclicAMIgpuFvPatch::interfaceInternalField
(
    const labelgpuList& internalData
) const
{
    return patchInternalField(internalData);
}


Foam::tmp<Foam::labelgpuField> Foam::cyclicAMIgpuFvPatch::interfaceInternalField
(
    const labelgpuList& internalData,
    const labelgpuList& faceCells
) const
{
    return patchInternalField(internalData, faceCells);
}


Foam::tmp<Foam::labelgpuField> Foam::cyclicAMIgpuFvPatch::internalFieldTransfer
(
    const Pstream::commsTypes commsType,
    const labelgpuList& iF
) const
{
    return neighbFvPatch().patchInternalField(iF);
}


void Foam::cyclicAMIgpuFvPatch::movePoints()
{

    if (!owner() || !cyclicAMIPolyPatch_.createAMIFaces())
    {
        // Only manipulating patch face areas and mesh motion flux if the AMI
        // creates additional faces
        return;
    }

    // Update face data based on values set by the AMI manipulations
    const vectorField& sf_tmp = boundaryMesh().mesh().hostmesh().Sf().boundaryField()[index()];
    const vectorField& cf_tmp = boundaryMesh().mesh().hostmesh().Cf().boundaryField()[index()];
    const scalarField& magsf_tmp = boundaryMesh().mesh().hostmesh().magSf().boundaryField()[index()];
    const_cast<vectorField&>(sf_tmp) = cyclicAMIPolyPatch_.faceAreas();
    const_cast<vectorField&>(cf_tmp) = cyclicAMIPolyPatch_.faceCentres();
    const_cast<scalarField&>(magsf_tmp) = mag(sf_tmp);

    const cyclicAMIgpuFvPatch& nbr = neighbPatch();
    const vectorField& nbr_sf_tmp = nbr.boundaryMesh().mesh().hostmesh().Sf().boundaryField()[index()];
    const vectorField& nbr_cf_tmp = nbr.boundaryMesh().mesh().hostmesh().Cf().boundaryField()[index()];
    const scalarField& nbr_magsf_tmp = nbr.boundaryMesh().mesh().hostmesh().magSf().boundaryField()[index()];
    const_cast<vectorField&>(nbr_sf_tmp) = nbr.cyclicAMIPatch().faceAreas();
    const_cast<vectorField&>(nbr_cf_tmp) = nbr.cyclicAMIPatch().faceCentres();
    const_cast<scalarField&>(nbr_magsf_tmp) = mag(nbr_sf_tmp);


    // Set consitent mesh motion flux
    // TODO: currently maps src mesh flux to tgt - update to
    // src = src + mapped(tgt) and tgt = tgt + mapped(src)?

    const fvMesh& mesh = boundaryMesh().mesh().hostmesh();
    surfaceScalarField& meshPhi = const_cast<fvMesh&>(mesh).setPhi();
    surfaceScalarField::Boundary& meshPhiBf = meshPhi.boundaryFieldRef();

    if (cyclicAMIPolyPatch_.owner())
    {
        scalarField& phip = meshPhiBf[patch().index()];
        forAll(phip, facei)
        {
            const face& f = cyclicAMIPolyPatch_.localFaces()[facei];

            // Note: using raw point locations to calculate the geometric
            // area - faces areas are currently scaled by the AMI weights
            // (decoupled from mesh points)
            const scalar geomArea = f.mag(cyclicAMIPolyPatch_.localPoints());

            //const scalar scaledArea = magSf()[facei]; // TODO magsf_tmp
            const scalar scaledArea = magsf_tmp[facei];
            phip[facei] *= scaledArea/geomArea;
        }

        scalarField srcMeshPhi(phip);
        if (AMI().distributed())
        {
            AMI().srcMap().distribute(srcMeshPhi);
        }

        const labelListList& tgtToSrcAddr = AMI().tgtAddress();
        scalarField& nbrPhip = meshPhiBf[nbr.index()];

        forAll(tgtToSrcAddr, tgtFacei)
        {
            // Note: now have 1-to-1 mapping so tgtToSrcAddr[tgtFacei] is size 1
            const label srcFacei = tgtToSrcAddr[tgtFacei][0];
            nbrPhip[tgtFacei] = -srcMeshPhi[srcFacei];
        }

        DebugInfo
            << "patch:" << patch().name()
            << " sum(area):" << gSum(magSf())
            << " min(mag(faceAreas):" << gMin(magSf())
            << " sum(meshPhi):" << gSum(phip) << nl
            << " sum(nbrMeshPhi):" << gSum(nbrPhip) << nl
            << endl;
    }

}

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