/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "RecordedEventImpl.h"

#include "PathRecording.h"
#include "RecordingTypes.h"
#include "Tools.h"
#include "Filters.h"
#include "Logging.h"
#include "ScaledFontBase.h"
#include "SFNTData.h"
#include "InlineTranslator.h"

namespace mozilla {
namespace gfx {

/* static */
bool RecordedEvent::DoWithEventFromStream(
    EventStream& aStream, EventType aType,
    const std::function<bool(RecordedEvent*)>& aAction) {
  return DoWithEvent(aStream, aType, aAction);
}

/* static */
bool RecordedEvent::DoWithEventFromReader(
    MemReader& aReader, EventType aType,
    const std::function<bool(RecordedEvent*)>& aAction) {
  return DoWithEvent(aReader, aType, aAction);
}

std::string RecordedEvent::GetEventName(EventType aType) {
  switch (aType) {
    case DRAWTARGETCREATION:
      return "DrawTarget Creation";
    case DRAWTARGETDESTRUCTION:
      return "DrawTarget Destruction";
    case FILLRECT:
      return "FillRect";
    case STROKERECT:
      return "StrokeRect";
    case STROKELINE:
      return "StrokeLine";
    case CLEARRECT:
      return "ClearRect";
    case COPYSURFACE:
      return "CopySurface";
    case SETPERMITSUBPIXELAA:
      return "SetPermitSubpixelAA";
    case SETTRANSFORM:
      return "SetTransform";
    case PUSHCLIP:
      return "PushClip";
    case PUSHCLIPRECT:
      return "PushClipRect";
    case POPCLIP:
      return "PopClip";
    case REMOVEALLCLIPS:
      return "RemoveAllClips";
    case FILL:
      return "Fill";
    case FILLGLYPHS:
      return "FillGlyphs";
    case STROKEGLYPHS:
      return "StrokeGlyphs";
    case MASK:
      return "Mask";
    case STROKE:
      return "Stroke";
    case DRAWSURFACE:
      return "DrawSurface";
    case DRAWSURFACEDESCRIPTOR:
      return "DrawSurfaceDescriptor";
    case DRAWDEPENDENTSURFACE:
      return "DrawDependentSurface";
    case DRAWSURFACEWITHSHADOW:
      return "DrawSurfaceWithShadow";
    case DRAWFILTER:
      return "DrawFilter";
    case PATHCREATION:
      return "PathCreation";
    case PATHDESTRUCTION:
      return "PathDestruction";
    case SOURCESURFACECREATION:
      return "SourceSurfaceCreation";
    case SOURCESURFACEDESTRUCTION:
      return "SourceSurfaceDestruction";
    case FILTERNODECREATION:
      return "FilterNodeCreation";
    case FILTERNODEDESTRUCTION:
      return "FilterNodeDestruction";
    case GRADIENTSTOPSCREATION:
      return "GradientStopsCreation";
    case GRADIENTSTOPSDESTRUCTION:
      return "GradientStopsDestruction";
    case SNAPSHOT:
      return "Snapshot";
    case SCALEDFONTCREATION:
      return "ScaledFontCreation";
    case SCALEDFONTDESTRUCTION:
      return "ScaledFontDestruction";
    case MASKSURFACE:
      return "MaskSurface";
    case FILTERNODESETATTRIBUTE:
      return "SetAttribute";
    case FILTERNODESETINPUT:
      return "SetInput";
    case CREATESIMILARDRAWTARGET:
      return "CreateSimilarDrawTarget";
    case FONTDATA:
      return "FontData";
    case FONTDESC:
      return "FontDescriptor";
    case PUSHLAYER:
      return "PushLayer";
    case POPLAYER:
      return "PopLayer";
    case UNSCALEDFONTCREATION:
      return "UnscaledFontCreation";
    case UNSCALEDFONTDESTRUCTION:
      return "UnscaledFontDestruction";
    case EXTERNALSURFACECREATION:
      return "ExternalSourceSurfaceCreation";
    case LINK:
      return "Link";
    case DESTINATION:
      return "Destination";
    default:
      return "Unknown";
  }
}

template <class S>
void RecordedEvent::RecordUnscaledFontImpl(UnscaledFont* aUnscaledFont,
                                           S& aOutput) {
  RecordedFontData fontData(aUnscaledFont);
  RecordedFontDetails fontDetails;
  if (fontData.GetFontDetails(fontDetails)) {
    // Try to serialise the whole font, just in case this is a web font that
    // is not present on the system.
    WriteElement(aOutput, fontData.mType);
    fontData.RecordToStream(aOutput);

    auto r = RecordedUnscaledFontCreation(aUnscaledFont, fontDetails);
    WriteElement(aOutput, r.mType);
    r.RecordToStream(aOutput);
  } else {
    // If that fails, record just the font description and try to load it from
    // the system on the other side.
    RecordedFontDescriptor fontDesc(aUnscaledFont);
    if (fontDesc.IsValid()) {
      WriteElement(aOutput, fontDesc.RecordedEvent::mType);
      fontDesc.RecordToStream(aOutput);
    } else {
      gfxWarning()
          << "DrawTargetRecording::FillGlyphs failed to serialise UnscaledFont";
    }
  }
}

void RecordedEvent::RecordUnscaledFont(UnscaledFont* aUnscaledFont,
                                       std::ostream* aOutput) {
  RecordUnscaledFontImpl(aUnscaledFont, *aOutput);
}

void RecordedEvent::RecordUnscaledFont(UnscaledFont* aUnscaledFont,
                                       MemStream& aOutput) {
  RecordUnscaledFontImpl(aUnscaledFont, aOutput);
}

already_AddRefed<DrawTarget> Translator::CreateDrawTarget(
    ReferencePtr aRefPtr, const IntSize& aSize, SurfaceFormat aFormat) {
  RefPtr<DrawTarget> newDT =
      GetReferenceDrawTarget()->CreateSimilarDrawTarget(aSize, aFormat);
  AddDrawTarget(aRefPtr, newDT);
  return newDT.forget();
}

void Translator::DrawDependentSurface(uint64_t aKey, const Rect& aRect) {
  if (!mDependentSurfaces || !mCurrentDT) {
    return;
  }

  RefPtr<RecordedDependentSurface> recordedSurface =
      mDependentSurfaces->Get(aKey);
  if (!recordedSurface) {
    return;
  }

  // Construct a new translator, so we can recurse into translating this
  // sub-recording into the same DT. Set an initial transform for the
  // translator, so that all commands get moved into the rect we want to draw.
  //
  // Because the recording may have filtered out SetTransform calls with the
  // same value, we need to call SetTransform here to ensure it gets called at
  // least once with the translated matrix.
  const Matrix oldTransform = mCurrentDT->GetTransform();

  Matrix dependentTransform = oldTransform;
  dependentTransform.PreTranslate(aRect.TopLeft());

  mCurrentDT->PushClipRect(aRect);
  mCurrentDT->SetTransform(dependentTransform);

  {
    InlineTranslator translator(mCurrentDT, nullptr);
    translator.SetReferenceDrawTargetTransform(dependentTransform);
    translator.SetDependentSurfaces(mDependentSurfaces);
    translator.TranslateRecording((char*)recordedSurface->mRecording.mData,
                                  recordedSurface->mRecording.mLen);
  }

  mCurrentDT->SetTransform(oldTransform);
  mCurrentDT->PopClip();
}

}  // namespace gfx
}  // namespace mozilla
