//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000-2018 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
//
// This program is free software.  The contents of this file are subject
// to the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
//
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please see the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
//
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
//
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
///////////////////////////////////////////////////////////////////////////////
// fspropdisplayer.cpp
//

#include "stdfs.h"

//=========================================================================
// INCLUDES
//=========================================================================

#include "fspropdisplayer.h"
#include "fspropset.h"
#include "core/fsservices.h"
#include "core/serializer.h"
#include "core/timeconvert.h"
#include "fco/fconame.h"
#include "fco/fco.h"
#include "core/twlocale.h"
#include "core/displayencoder.h"
#include "fco/fcoundefprop.h"
#include "core/errorutil.h"

//=========================================================================
// STANDARD LIBRARY INCLUDES
//=========================================================================

#include <iomanip>

//=========================================================================
// DEFINES AND MACROS
//=========================================================================

//=========================================================================
// OTHER DIRECTIVES
//=========================================================================

#if !IS_AIX
// For some stupid linker reason, this object does not get constructed under
// AIX. The cheap fix is to move this call to fs.cpp.
IMPLEMENT_TYPEDSERIALIZABLE(cFSPropDisplayer, _T("FSPropDisplayer"), 0, 1);
#endif

//=========================================================================
// GLOBALS
//=========================================================================

//=========================================================================
// UTIL FUNCTION PROTOTYES
//=========================================================================

//=========================================================================
// METHOD CODE
//=========================================================================

cFSPropDisplayer::cFSPropDisplayer() : mbLazy(false)
{
    mpvPropsWeDisplay.AddItemAndGrow(cFSPropSet::PROP_UID);
    mpvPropsWeDisplay.AddItemAndGrow(cFSPropSet::PROP_GID);
    mpvPropsWeDisplay.AddItemAndGrow(cFSPropSet::PROP_ATIME);
    mpvPropsWeDisplay.AddItemAndGrow(cFSPropSet::PROP_MTIME);
    mpvPropsWeDisplay.AddItemAndGrow(cFSPropSet::PROP_CTIME);
    mpvPropsWeDisplay.AddItemAndGrow(cFSPropSet::PROP_MODE);

    mpvPropsWeDisplay.AddItemAndGrow(cFSPropSet::PROP_BLOCK_SIZE);
    mpvPropsWeDisplay.AddItemAndGrow(cFSPropSet::PROP_BLOCKS);
    mpvPropsWeDisplay.AddItemAndGrow(cFSPropSet::PROP_SIZE);
    mpvPropsWeDisplay.AddItemAndGrow(cFSPropSet::PROP_NLINK);
}

iFCOPropDisplayer* cFSPropDisplayer::Clone() const
{
    iFCOPropDisplayer* pNew = new cFSPropDisplayer;
    pNew->Merge(this);
    return pNew;
}

bool cFSPropDisplayer::IsMultiLineProp(int propID) const
{
    // no FS props are multiline
    return false;
}

cFSPropDisplayer::~cFSPropDisplayer()
{
    cDebug d("~cFSPropDisplayer");
    d.TraceNever("Destructor called on object %x\n", (ptr_size_type)this);
}

const cFCOPropVector& cFSPropDisplayer::GetPropsConverted() const
{
    return mpvPropsWeDisplay;
}

void cFSPropDisplayer::SetLazy(const bool bLazy /* = true */)
{
    mbLazy = bLazy;
}

bool cFSPropDisplayer::GetLazy() const
{
    return mbLazy;
}

// adds all prop display info from rhs to this
void cFSPropDisplayer::Merge(const iFCOPropDisplayer* const ppd)
{
    ASSERT(ppd != 0);

    // should only merge two prop displayers of the same type
    if (ppd->GetType() != this->GetType())
        ASSERT(false);

    const cFSPropDisplayer* const pfspd = static_cast<const cFSPropDisplayer* const>(ppd);

    // merge propvectors
    mpvPropsWeDisplay |= pfspd->GetPropsConverted();


    // merge mapping info
    for (INT64_TO_STRING_MAP::const_iterator iterUID = pfspd->uidToUsername.begin();
         iterUID != pfspd->uidToUsername.end();
         ++iterUID)
    {
        AddUsernameMapping(iterUID->first, iterUID->second);
    }

    for (INT64_TO_STRING_MAP::const_iterator iterGID = pfspd->gidToGroupname.begin();
         iterGID != pfspd->gidToGroupname.end();
         ++iterGID)
    {
        AddGroupnameMapping(iterGID->first, iterGID->second);
    }

    // keep lazy flag the same
}


TSTRING& cFSPropDisplayer::GetDetailsHeader(TSTRING& strBuf, int iMargin) const
{
    static cFSPropSet set;
    TOSTRINGSTREAM    sstr;

    sstr << std::left;
    sstr << _T(" ") << std::setw(PROP_MODE_WIDTH) << set.GetPropName(cFSPropSet::PROP_MODE);
    sstr << _T(" ") << std::setw(PROP_OWNER_WIDTH) << set.GetPropName(cFSPropSet::PROP_UID);
    sstr << _T(" ") << std::setw(PROP_SIZE_WIDTH) << set.GetPropName(cFSPropSet::PROP_SIZE);
    sstr << _T(" ") << std::setw(PROP_MTIME_WIDTH) << set.GetPropName(cFSPropSet::PROP_MTIME);
    sstr << _T("\n");
    for (int i = 0; i < iMargin; i++)
        sstr << _T(" ");
    sstr << _T(" ") << std::setw(PROP_MODE_WIDTH) << _T("------");
    sstr << _T(" ") << std::setw(PROP_OWNER_WIDTH) << _T("----------");
    sstr << _T(" ") << std::setw(PROP_SIZE_WIDTH) << _T("----------");
    sstr << _T(" ") << std::setw(PROP_MTIME_WIDTH) << _T("----------");

    strBuf = sstr.str();
    return strBuf;
}


////////////////////////////////////////////////////////////////////////
// Function name    : cFSPropDisplayer::GetDetails
// Description      : takes a TSTRING and fills it with an "ls -l" representation
//                    of the object's permission bits ( e.g. "drwxr-x--x" ), UID,
//                    size, and last access time.  Does not add the name of the FSO
//
// Return type      : TSTRING& -- reference to input buffer
//
// Argument         : TSTRING& strBuf -- buffer.  GetDesription will use it to allocate space.
//
TSTRING& cFSPropDisplayer::GetDetails(const iFCO* const pfco, TSTRING& strBuf) const
{
    TOSTRINGSTREAM       sstr;
    const cFCOPropVector pv = pfco->GetPropSet()->GetValidVector();

    sstr << std::left;

    sstr << _T(" ") << std::setw(PROP_MODE_WIDTH);
    if (pv.ContainsItem(cFSPropSet::PROP_MODE))
        sstr << PropAsString(pfco, cFSPropSet::PROP_MODE).c_str();
    else
        sstr << _T("XXXXXXXXX");

    sstr << _T(" ") << std::setw(PROP_OWNER_WIDTH);
    if (pv.ContainsItem(cFSPropSet::PROP_UID))
        sstr << PropAsString(pfco, cFSPropSet::PROP_UID).c_str();
    else
        sstr << _T("XXXXXXXXX");

    sstr << _T(" ") << std::setw(PROP_SIZE_WIDTH);
    if (pv.ContainsItem(cFSPropSet::PROP_SIZE))
        sstr << PropAsString(pfco, cFSPropSet::PROP_SIZE).c_str();
    else
        sstr << _T("XXX");

    sstr << _T(" ") << std::setw(PROP_MTIME_WIDTH);
    if (pv.ContainsItem(cFSPropSet::PROP_MTIME))
        sstr << PropAsString(pfco, cFSPropSet::PROP_MTIME).c_str();
    else
        sstr << _T("XXXXXXXXXXXXXXXXX");

    strBuf = sstr.str();
    return strBuf;
}


void cFSPropDisplayer::InitForFCO(const iFCO* const ifco)
{
    ASSERT(ifco != 0);

    cFCOPropVector v     = ifco->GetPropSet()->GetValidVector();
    int            vSize = v.GetSize();
    for (int i = 0; i < vSize; i++)
    {
        if (v.ContainsItem(i))
        {
            InitForProp(ifco, i);
        }
    }
}


void cFSPropDisplayer::InitForProp(const iFCO* const pFCO, const int propIdx)
{
    ASSERT(pFCO != 0);

    switch (propIdx)
    {
    case cFSPropSet::PROP_UID:
    {
        const int64& i64UID =
            static_cast<const cFCOPropInt64* const>(pFCO->GetPropSet()->GetPropAt(cFSPropSet::PROP_UID))->GetValue();

        // check if prop is in table.  if it is, then don't hit the FS
        TSTRING tstrDummy;
        if (!GetUsername(i64UID, tstrDummy))
        {
            TSTRING tstrUsername;
            if (iFSServices::GetInstance()->GetUserName(i64UID, tstrUsername))
                AddUsernameMapping(i64UID, tstrUsername);
            else
                AddUsernameMapping(
                    i64UID,
                    _T("")); // on failure, enter error value into mapping so we don't search for this value again.
        }
    }
    break;
    case cFSPropSet::PROP_GID:
    {
        const int64& i64GID =
            static_cast<const cFCOPropInt64* const>(pFCO->GetPropSet()->GetPropAt(cFSPropSet::PROP_GID))->GetValue();

        // check if prop is in table.  if it is, then don't hit the FS
        TSTRING tstrDummy;
        if (!GetGroupname(i64GID, tstrDummy))
        {
            TSTRING tstrGroupname;
            if (iFSServices::GetInstance()->GetGroupName(i64GID, tstrGroupname))
                AddGroupnameMapping(i64GID, tstrGroupname);
            else
                AddGroupnameMapping(
                    i64GID,
                    _T("")); // on failure, enter error value into mapping so we don't search for this value again.
        }
    }
    break;
    default:
        // right now nothing else needs to be done for the other props
        break;
    }
}

TSTRING cFSPropDisplayer::PropAsString(const iFCO* const pFCO, const int propIdx, int iOffset, int iWidth)
{
    if (mpvPropsWeDisplay.ContainsItem(propIdx) && mbLazy)
    {
        InitForProp(pFCO, propIdx);
    }
    // run the const version of PropAsString
    return ((const cFSPropDisplayer*)this)->PropAsString(pFCO, propIdx, iOffset, iWidth);
}

TSTRING cFSPropDisplayer::PropAsString(const iFCO* const pFCO, const int propIdx, int iOffset, int iWidth) const
{
    TSTRING strProp = _T("");

    ASSERT(pFCO != 0);

    // get prop pointer
    const iFCOProp* pProp = pFCO->GetPropSet()->GetPropAt(propIdx);

    // if we know how to display prop
    if (pProp->GetType() != cFCOUndefinedProp::GetInstance()->GetType() && mpvPropsWeDisplay.ContainsItem(propIdx))
    {
        switch (propIdx)
        {
        case cFSPropSet::PROP_BLOCK_SIZE:
        case cFSPropSet::PROP_BLOCKS:
        case cFSPropSet::PROP_SIZE:
        case cFSPropSet::PROP_NLINK:
        {
            const cFCOPropInt64* const pTypedProp = static_cast<const cFCOPropInt64* const>(pProp);
            cTWLocale::FormatNumber(pTypedProp->GetValue(), strProp);
        }
        break;
        case cFSPropSet::PROP_ATIME:
        case cFSPropSet::PROP_MTIME:
        case cFSPropSet::PROP_CTIME:
        {
            const cFCOPropInt64* const pTypedProp = static_cast<const cFCOPropInt64* const>(pProp);
            int64                      i64        = pTypedProp->GetValue();
            cTWLocale::FormatTime(i64, strProp);
        }
        break;
        case cFSPropSet::PROP_MODE:
        {
            const cFCOPropUint64* const pTypedProp = static_cast<const cFCOPropUint64* const>(pProp);
            ASSERT(pTypedProp != 0);

            iFSServices::GetInstance()->ConvertModeToString(pTypedProp->GetValue(), strProp);
        }
        break;
        case cFSPropSet::PROP_UID:
        {
            const cFCOPropInt64* const pTypedProp = static_cast<const cFCOPropInt64* const>(pProp);
            ASSERT(pTypedProp != 0);
            if (GetUsername(pTypedProp->GetValue(), strProp))
            {
                TSTRINGSTREAM ostr;
                ostr << strProp << _T(" (") << (int32)pTypedProp->GetValue() << _T(")");
                strProp = ostr.str();
            }
            else
                strProp = pProp->AsString();
        }
        break;
        case cFSPropSet::PROP_GID:
        {
            const cFCOPropInt64* const pTypedProp = static_cast<const cFCOPropInt64* const>(pProp);
            ASSERT(pTypedProp != 0);
            if (GetGroupname(pTypedProp->GetValue(), strProp))
            {
                TSTRINGSTREAM ostr;
                ostr << strProp << _T(" (") << (int32)pTypedProp->GetValue() << _T(")");
                strProp = ostr.str();
            }
            else
                strProp = pProp->AsString();
        }
        break;
        default:
            ASSERT(false); // mpvPropsWeDisplay should contain one of the above
            break;
        }
    }
    else // we don't know how to display prop, so let prop display itself
    {
        strProp = pProp->AsString();
    }

    return (strProp);
}

void cFSPropDisplayer::Write(iSerializer* pSerializer) const
{
    mpvPropsWeDisplay.Write(pSerializer);
    pSerializer->WriteInt32(mbLazy ? 1 : 0);


    //  stuff
    {
        // write UID mapping
        pSerializer->WriteInt32(uidToUsername.size());
        for (INT64_TO_STRING_MAP::const_iterator iterUid = uidToUsername.begin(); iterUid != uidToUsername.end();
             ++iterUid)
        {
            pSerializer->WriteInt64(iterUid->first);
            pSerializer->WriteString(iterUid->second);
        }

        // write GID mapping
        pSerializer->WriteInt32(gidToGroupname.size());
        for (INT64_TO_STRING_MAP::const_iterator iterGid = gidToGroupname.begin(); iterGid != gidToGroupname.end();
             ++iterGid)
        {
            pSerializer->WriteInt64(iterGid->first);
            pSerializer->WriteString(iterGid->second);
        }
    }
}

void cFSPropDisplayer::Read(iSerializer* pSerializer, int32 version)
{
    if (version > Version())
        ThrowAndAssert(eSerializerVersionMismatch(_T("Property Displayer Read")));

    mpvPropsWeDisplay.Read(pSerializer);

    int32 iDummy;
    pSerializer->ReadInt32(iDummy);
    mbLazy = iDummy == 0 ? false : true;

    //  stuff
    {
        int32   nValues;
        int64   key;
        TSTRING val;
        int     i;

        // read UID hashtable
        pSerializer->ReadInt32(nValues);
        for (i = 0; i < nValues; i++)
        {
            pSerializer->ReadInt64(key);
            pSerializer->ReadString(val);
            AddUsernameMapping(key, val);
        }

        // read GID hashtable
        pSerializer->ReadInt32(nValues);
        for (i = 0; i < nValues; i++)
        {
            pSerializer->ReadInt64(key);
            pSerializer->ReadString(val);
            AddGroupnameMapping(key, val);
        }
    }
}


//////////////////////////////////////////////
// Lookup functions
//////////////////////////////////////////////

bool cFSPropDisplayer::GetUsername(const int64& i64uid, TSTRING& tstrUsername) const
{
    bool fFound = false;

    INT64_TO_STRING_MAP::const_iterator iter = uidToUsername.find(i64uid);

    if (iter != uidToUsername.end())
    {
        fFound       = true;
        tstrUsername = iter->second;
    }

    return (fFound && !tstrUsername.empty());
}

bool cFSPropDisplayer::GetGroupname(const int64& i64gid, TSTRING& tstrGroupname) const
{
    bool fFound = false;

    INT64_TO_STRING_MAP::const_iterator iter = gidToGroupname.find(i64gid);

    if (iter != gidToGroupname.end())
    {
        fFound        = true;
        tstrGroupname = iter->second;
    }

    return (fFound && !tstrGroupname.empty());
}

//////////////////////////////////////////////
// Addition functions
//////////////////////////////////////////////

bool cFSPropDisplayer::AddUsernameMapping(const int64& i64uid, const TSTRING& tstrUsername)
{
    std::pair<INT64_TO_STRING_MAP::iterator, bool> ret =
        uidToUsername.insert(INT64_TO_STRING_MAP::value_type(i64uid, tstrUsername));
    return (ret.second = false); // returns true if key didn't exist before
}

bool cFSPropDisplayer::AddGroupnameMapping(const int64& i64gid, const TSTRING& tstrGroupname)
{
    std::pair<INT64_TO_STRING_MAP::iterator, bool> ret =
        gidToGroupname.insert(INT64_TO_STRING_MAP::value_type(i64gid, tstrGroupname));
    return (ret.second = false); // returns true if key didn't exist before
}

bool cFSPropDisplayer::operator==(const cFSPropDisplayer& rhs) const
{
    return (mpvPropsWeDisplay == rhs.mpvPropsWeDisplay && uidToUsername == rhs.uidToUsername &&
            gidToGroupname == rhs.gidToGroupname);
}
