﻿// TortoiseSVN - a Windows shell extension for easy version control

// Copyright (C) 2003-2010, 2014 - TortoiseSVN

// This program 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 2
// of the License, or (at your option) any later version.

// This program 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 this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "stdafx.h"
#include "resource.h"
#include "StandardLayoutNodeList.h"
#include "VisibleGraphNode.h"
#include "CachedLogInfo.h"
#include "SVN.h"
#include "UnicodeUtils.h"

/// utilities

index_t CStandardLayoutNodeList::GetStyle
    (const CVisibleGraphNode* node) const
{
    CNodeClassification classification = node->GetClassification();

    if (classification.Is (CNodeClassification::IS_ADDED))
        return ILayoutNodeList::SNode::STYLE_ADDED;
    else if (classification.Is (CNodeClassification::IS_DELETED))
        return ILayoutNodeList::SNode::STYLE_DELETED;
    else if (classification.Is (CNodeClassification::IS_RENAMED))
        return ILayoutNodeList::SNode::STYLE_RENAMED;
    else if (classification.Is (CNodeClassification::IS_LAST))
        return ILayoutNodeList::SNode::STYLE_LAST;
    else if (classification.Is (CNodeClassification::IS_MODIFIED))
        return ILayoutNodeList::SNode::STYLE_MODIFIED;
    else
        return ILayoutNodeList::SNode::STYLE_DEFAULT;
}

DWORD CStandardLayoutNodeList::GetStyleFlags
    (const CVisibleGraphNode* /*node*/) const
{
    return 0;
}

// construction

CStandardLayoutNodeList::CStandardLayoutNodeList
    ( const std::vector<CStandardLayoutNodeInfo>& nodes
    , const CCachedLogInfo* cache
    , const CFullHistory::SWCInfo& wcInfo)
    : cache (cache)
    , nodes (nodes)
    , wcInfo (wcInfo)
{
}

// implement ILayoutItemList

index_t CStandardLayoutNodeList::GetCount() const
{
    return static_cast<index_t>(nodes.size());
}

namespace
{
    void AddString (CString& target, int id)
    {
        CString toAdd;
        toAdd.Format (id);
        target = target.IsEmpty()
               ? toAdd
               : target + L", " + toAdd;
    }
}

CString CStandardLayoutNodeList::GetToolTip (index_t index) const
{
    CString strTipText;

    const CRevisionIndex& revisions = cache->GetRevisions();
    const CRevisionInfoContainer& revisionInfo = cache->GetLogInfo();

    const CVisibleGraphNode* node = nodes[index].node;

    // node properties

    CNodeClassification classification = node->GetClassification();
    revision_t revision = node->GetRevision();

    CString path
        = CUnicodeUtils::StdGetUnicode
            (node->GetPath().GetPath()).c_str();

    // special case: the "workspace is modified" node

    if (classification.Is (CNodeClassification::IS_MODIFIED_WC))
    {
        strTipText.FormatMessage ( IDS_REVGRAPH_BOXTOOLTIP_WC
                                  , revision-1
                                  , (LPCWSTR)path);
        return strTipText;
    }

    // find the revision in our cache.
    // Might not be present if this is the WC / HEAD revision
    // (but should not happen).

    index_t revisionIndex = revisions [revision];
    assert (revisionIndex != NO_INDEX);
    if (revisionIndex == NO_INDEX)
        return strTipText;

    // get standard revprops

    wchar_t    date[SVN_DATE_BUFFER] = {0};
    apr_time_t timeStamp = revisionInfo.GetTimeStamp (revisionIndex);
    SVN::formatDate(date, timeStamp);

    CString author
        = CUnicodeUtils::StdGetUnicode
            (revisionInfo.GetAuthor (revisionIndex)).c_str();
    CString comment
        = CUnicodeUtils::StdGetUnicode
            (revisionInfo.GetComment (revisionIndex)).c_str();

    // description of the operation represented by this node

    CString revisionDescription;
    if (classification.Is (CNodeClassification::IS_COPY_TARGET))
    {
        if (classification.Is (CNodeClassification::IS_TAG))
            revisionDescription.LoadString (IDS_REVGRAPH_NODEIS_TAG);
        else if (classification.Is (CNodeClassification::IS_BRANCH))
            revisionDescription.LoadString (IDS_REVGRAPH_NODEIS_BRANCH);
        else
            revisionDescription.LoadString (IDS_REVGRAPH_NODEIS_COPY);
    }
    else if (classification.Is (CNodeClassification::IS_ADDED))
        revisionDescription.LoadString (IDS_REVGRAPH_NODEIS_ADDED);
    else if (classification.Is (CNodeClassification::IS_DELETED))
        revisionDescription.LoadString (IDS_REVGRAPH_NODEIS_DELETE);
    else if (classification.Is (CNodeClassification::IS_RENAMED))
        revisionDescription.LoadString (IDS_REVGRAPH_NODEIS_RENAME);
    else if (classification.Is (CNodeClassification::IS_LAST))
        revisionDescription.LoadString (IDS_REVGRAPH_NODEIS_HEAD);
    else if (classification.Is (CNodeClassification::IS_MODIFIED))
        revisionDescription.LoadString (IDS_REVGRAPH_NODEIS_MODIFIED);
    else if (classification.Is (CNodeClassification::IS_COPY_SOURCE))
        revisionDescription.LoadString (IDS_REVGRAPH_NODEIS_COPYSOURCE);
    else
        revisionDescription.LoadString (IDS_REVGRAPH_NODEIS_WORKINGCOPY);

    // working copy info

    CString workingCopyLine;
    if (classification.Is (CNodeClassification::IS_WORKINGCOPY))
    {
        CString status;

        // "w/c at rev"

        if (node->GetRevision() == wcInfo.maxAtRev)
        {
            AddString (status, node->GetRevision() == wcInfo.minAtRev
                             ? IDS_REVGRAPH_NODEWC_AT
                             : IDS_REVGRAPH_NODEWC_MAX_AT);
        }
        else if (node->GetRevision() == wcInfo.minAtRev)
        {
            AddString (status, IDS_REVGRAPH_NODEWC_MIN_AT);
        }

        // "last commit rev"

        if (node->GetRevision() == wcInfo.maxCommit)
        {
            AddString (status, node->GetRevision() == wcInfo.minCommit
                             ? IDS_REVGRAPH_NODEWC_COMMITTED
                             : IDS_REVGRAPH_NODEWC_MAX_COMMITTED);
        }
        else if (node->GetRevision() == wcInfo.minCommit)
        {
            AddString (status, IDS_REVGRAPH_NODEWC_MIN_COMMITTED);
        }

        // complete line

        workingCopyLine.Format (IDS_REVGRAPH_NODEWC_LINE, (LPCWSTR)status);
    }

    // copy-from info, if available

    CString copyFromLine;
    const CFullGraphNode* copySource = node->GetBase()->GetCopySource();
    if (copySource != NULL)
    {
        CString copyFromPath
            = CUnicodeUtils::StdGetUnicode
                (copySource->GetPath().GetPath()).c_str();
        revision_t copyFromRevision = copySource->GetRevision();

        copyFromLine.FormatMessage ( IDS_REVGRAPH_BOXTOOLTIP_COPYSOURCE
                                    , (LPCWSTR)copyFromPath
                                    , copyFromRevision);
    }

    // construct the tooltip

    CString additionalInfo = workingCopyLine + copyFromLine;
    if (node->GetFirstTag() == NULL)
    {
        strTipText.FormatMessage ( IDS_REVGRAPH_BOXTOOLTIP
                                  , revision, (LPCWSTR)revisionDescription
                                  , (LPCWSTR)path, (LPCWSTR)additionalInfo
                                  , (LPCWSTR)author, date, (LPCWSTR)comment);
    }
    else
    {
        CString tags;
        int tagCount = 0;
        for ( const CVisibleGraphNode::CFoldedTag* tag = node->GetFirstTag()
            ; tag != NULL
            ; tag = tag->GetNext())
        {
            ++tagCount;

            CString attributes;
            if (tag->IsModified())
                attributes.LoadString (IDS_REVGRAPH_TAGMODIFIED);

            if (tag->IsDeleted())
            {
                CString attribute;
                attribute.LoadString (IDS_REVGRAPH_TAGDELETED);
                if (attributes.IsEmpty())
                    attributes = attribute;
                else
                    attributes += L", " + attribute;
            }

            CString tagInfo;
            std::string tagPath = tag->GetTag()->GetPath().GetPath();

            if (attributes.IsEmpty())
            {
                tagInfo.Format (   tag->IsAlias()
                                 ? IDS_REVGRAPH_TAGALIAS
                                 : IDS_REVGRAPH_TAG
                               , CUnicodeUtils::StdGetUnicode (tagPath).c_str());
            }
            else
            {
                tagInfo.FormatMessage (   tag->IsAlias()
                                        ? IDS_REVGRAPH_TAGALIASATTRIBUTED
                                        : IDS_REVGRAPH_TAGATTRIBUTED
                                        , (LPCWSTR)attributes
                                        , CUnicodeUtils::StdGetUnicode (tagPath).c_str());
            }

            tags +=   L"\r\n"
                    + CString (' ', (int)tag->GetDepth() * 6)
                    + tagInfo;
        }

        strTipText.FormatMessage ( IDS_REVGRAPH_BOXTOOLTIP_TAGGED
                                  , revision, (LPCWSTR)revisionDescription
                                  , (LPCWSTR)path, (LPCWSTR)additionalInfo
                                  , (LPCWSTR)author, date, tagCount, (LPCWSTR)tags
                                  , (LPCWSTR)comment);
    }

    // ready

    return strTipText;
}

index_t CStandardLayoutNodeList::GetFirstVisible (const CRect& viewRect) const
{
    return GetNextVisible (static_cast<index_t>(-1), viewRect);
}

index_t CStandardLayoutNodeList::GetNextVisible ( index_t prev
                                                , const CRect& viewRect) const
{
    for (size_t i = prev+1, count = nodes.size(); i < count; ++i)
        if (FALSE != CRect().IntersectRect (nodes[i].rect, viewRect))
            return static_cast<index_t>(i);

    return static_cast<index_t>(NO_INDEX);
}

index_t CStandardLayoutNodeList::GetAt (const CPoint& point, CSize delta) const
{
    for (size_t i = 0, count = nodes.size(); i < count; ++i)
    {
        const CRect& rect = nodes[i].rect;
        if (   (rect.top - point.y <= delta.cy)
            && (rect.left - point.x <= delta.cx)
            && (point.y - rect.bottom <= delta.cy)
            && (point.x - rect.right <= delta.cx))
        {
            return static_cast<index_t>(i);
        }
    }

    return static_cast<index_t>(NO_INDEX);
}

// implement ILayoutNodeList

CStandardLayoutNodeList::SNode
CStandardLayoutNodeList::GetNode (index_t index) const
{
    SNode result;

    const CVisibleGraphNode* node = nodes[index].node;

    result.rect = nodes[index].rect;
    result.node = node;
    result.style = GetStyle (node);
    result.styleFlags = GetStyleFlags (node);

    return result;
}
