//
/// \file transferbuffer.cpp
/// Safe transfer buffers
//
// Copyright � 2010 Vidar Hasfjord
// Distributed under the OWLNext License (see http://owlnext.sourceforge.net).
//
 
#include <owl/pch.h>
 
#include <owl/transferbuffer.h>
#include <owl/validate.h>
#include <map>
#include <algorithm>
 
namespace owl {
 
namespace // Local implementation utilities
{
 
  //
  /// Stores information about a bound field in a window's transfer buffer.
  //
  struct TFieldInfo
  {
    size_t Offset;
    size_t Size;
  };
 
  //
  /// Type of collection of bound controls
  /// Public so that the class implementation can use free functions.
  //
  typedef std::map<TWindow*, TFieldInfo, std::less<TWindow*> > TFieldDetails; // NB! Default arg is for BC++ 5.0x compatibility.
 
  //
  /// Functor for find_if with TFieldDetails
  //
  struct TOffsetEq
  {
    size_t Offset;
    TOffsetEq(size_t ofs) : Offset(ofs) {}
 
    bool operator()(const TFieldDetails::value_type& v)
    {return v.second.Offset == Offset;}
  };
 
  //
  /// Functor for find_if with TFieldDetails
  //
  struct TIdEq
  {
    int Id;
    TIdEq(int id) : Id(id) {}
 
    bool operator()(const TFieldDetails::value_type& v)
    {return v.first->GetId() == Id;}
  };
 
  //
  /// Parameter type for TransferChild.
  //
  struct TTransferIterInfo
  {
    TFieldDetails* Details;
    void* Buffer;
    TTransferDirection Direction;
  };
 
  //
  /// Calculates the field address and dispatches to the given child.
  /// Checks that all participating children are bound to a field; throws if not.
  /// Checks that the transferred size matches the bound field size; throws if not.
  /// Forbids validator meddling; throws if a validator wants to override data transfer.
  //
  // TODO: Replace TXWindow (IDS_TRANSFERBUFFERSIZEMISMATCH) with a local exception class.
  //
  void TransferChild(TWindow& child, const TTransferIterInfo& t)
  {
    PRECONDITION(child);
    if (!child.IsFlagSet(wfTransfer)) return; // disabled
    if (TEdit* edit = TYPESAFE_DOWNCAST(&child, TEdit))
      if (TValidator* v = edit->GetValidator())
        if (v->HasOption(voTransfer)) throw TTransferBufferWindowBase::TXMeddlingValidator(*edit);
 
    CHECK(t.Details);
    TFieldDetails::const_iterator i = t.Details->find(&child);
    if (i == t.Details->end()) throw TTransferBufferWindowBase::TXUnboundControl(child);
    const TFieldInfo& f = (*i).second;
    uint size = child.Transfer(nullptr, tdSizeData);
    if (size != f.Size) TXWindow::Raise(&child, IDS_TRANSFERBUFFERSIZEMISMATCH);
    if (t.Direction != tdSizeData)
    {
      void* field = static_cast<char*>(t.Buffer) + f.Offset;
      uint transferred_size = child.Transfer(field, t.Direction);
      if (transferred_size != size) TXWindow::Raise(&child, IDS_TRANSFERBUFFERSIZEMISMATCH);
    }
  }
 
  //
  /// See TXPolygamousControl.
  //
  tstring MakeMessageForTXPolygamousControl(TWindow& c)
  {
    tostringstream s;
    TWindow* p = c.GetParentO();
    s << _T("Control #") << c.GetId() << _T(" (") << GetFullTypeName(&c) << _T(") ")
      << _T(" in window ") << GetFullTypeName(p) << _T(" ")
      << _T(" is trying to bind to more than one field.\n\n")
      << _T("Ensure that every control is bound to a separate field in the transfer buffer, ")
        _T("and that TTransferBufferWindow::Bind is not called more than once for the same control.\n");
    return s.str();
  }
 
  //
  /// See TXFieldConflict.
  //
  tstring MakeMessageForTXFieldConflict(TWindow& c)
  {
    tostringstream s;
    TWindow* p = c.GetParentO();
    s << _T("Control #") << c.GetId() << _T(" (") << GetFullTypeName(&c) << _T(") ")
      << _T(" in window ") << GetFullTypeName(p) << _T(" ")
      << _T(" is trying to bind to an already bound field.\n\n")
      << _T("Ensure that every control is bound to a separate field in the transfer buffer, ")
        _T("and that TTransferBufferWindow::Bind is not called more than once for the same control.\n");
    return s.str();
  }
 
  //
  /// See TXUnboundControl.
  //
  tstring MakeMessageForTXUnboundControl(TWindow& c)
  {
    tostringstream s;
    TWindow* p = c.GetParentO();
    s << _T("Transfer requested by unbound control #")
      << c.GetId() << _T(" (") << GetFullTypeName(&c) << _T(") ")
      << _T(" in window ") << GetFullTypeName(p) << _T(".\n\n")
      << _T("Use TTransferBufferWindow::Bind to bind the control to a field in the window's transfer buffer, ")
        _T("or disable transfer for this control using TWindow::DisableTransfer.\n");
    return s.str();
  }
 
  //
  /// See TXMeddlingValidator.
  //
  tstring MakeMessageForTXMeddlingValidator(TEdit& c)
  {
    tostringstream s;
    TWindow* p = c.GetParentO();
    s << _T("Validator ") << GetFullTypeName(c.GetValidator())
      << _T(" is meddling with the transfer of control #")
      << c.GetId() << _T(" (") << GetFullTypeName(&c) << _T(") ")
      << _T(" in window ") << GetFullTypeName(p) << _T(".\n\n")
      << _T("Validators are not allowed to take part in transfers in a TTransferBufferWindow. ")
        _T("Turn off transfer meddling for the validator by calling TValidator::UnsetOption(voTransfer). ")
        _T("You must use a custom control if you need to transfer numeric buffer fields.\n");
    return s.str();
  }
 
} // namespace
 
//
/// Private implementation class for TTransferBufferWindowBase
/// Stores the buffer field associations.
//
class TTransferBufferWindowBase::TImpl
{
public:
  TFieldDetails FieldDetails;
};
 
TTransferBufferWindowBase::TTransferBufferWindowBase()
: pimpl(new TImpl())
{}
 
TTransferBufferWindowBase::~TTransferBufferWindowBase()
{
  delete pimpl;
}
 
void TTransferBufferWindowBase::TransferData(TTransferDirection direction)
{
  Transfer(GetTransferBuffer(), direction);
}
 
uint TTransferBufferWindowBase::Transfer(void* buffer, TTransferDirection direction)
{
  if (!buffer && direction != tdSizeData) return 0;
  const auto info = TTransferIterInfo{&pimpl->FieldDetails, buffer, direction};
  for (auto& w : GetChildren())
    TransferChild(w, info);
  return GetTransferBufferSize();
}
 
void TTransferBufferWindowBase::AssignField(TWindow& c, size_t offset, size_t size)
{
  PRECONDITION(size > 0);
 
  TFieldDetails& f = pimpl->FieldDetails;
  if (std::find_if(f.begin(), f.end(), TOffsetEq(offset)) != f.end())
    throw TXFieldConflict(c);
  if (std::find_if(f.begin(), f.end(), TIdEq(c.GetId())) != f.end())
    throw TXPolygamousControl(c);
 
  TFieldInfo info = {offset, size};
  typedef std::pair<TFieldDetails::iterator, bool> TInsertResult;
  TInsertResult r = f.insert(TFieldDetails::value_type(&c, info));
  if (!r.second) throw TXPolygamousControl(c);
 
  c.EnableTransfer();
}
 
TTransferBufferWindowBase::TXPolygamousControl::TXPolygamousControl(TWindow& c)
  : TXOwl(MakeMessageForTXPolygamousControl(c))
{}
 
TTransferBufferWindowBase::TXFieldConflict::TXFieldConflict(TWindow& c)
  : TXOwl(MakeMessageForTXFieldConflict(c))
{}
 
TTransferBufferWindowBase::TXUnboundControl::TXUnboundControl(TWindow& c)
  : TXOwl(MakeMessageForTXUnboundControl(c))
{}
 
TTransferBufferWindowBase::TXMeddlingValidator::TXMeddlingValidator(TEdit& c)
  : TXOwl(MakeMessageForTXMeddlingValidator(c))
{}
 
} // OWL namespace

V1004 The 't.Details' pointer was used unsafely after it was verified against nullptr. Check lines: 86, 87.