/* ***** BEGIN LICENSE BLOCK *****
 * 
 * Copyright (c) 2008, Mozilla Corporation
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the Mozilla Corporation nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * Contributor(s):
 *   Josh Aas <josh@mozilla.com>
 * 
 * ***** END LICENSE BLOCK ***** */

#include "nptest_platform.h"
#include "nsAlgorithm.h"
#include <CoreServices/CoreServices.h>

using namespace std;

bool
pluginSupportsWindowMode()
{
  return false;
}

bool
pluginSupportsWindowlessMode()
{
  return true;
}

NPError
pluginInstanceInit(InstanceData* instanceData)
{
  NPP npp = instanceData->npp;

  NPBool supportsCoreGraphics = false;
  if ((NPN_GetValue(npp, NPNVsupportsCoreGraphicsBool, &supportsCoreGraphics) == NPERR_NO_ERROR) &&
      supportsCoreGraphics) {
    NPN_SetValue(npp, NPPVpluginDrawingModel, (void*)NPDrawingModelCoreGraphics);
  } else {
    printf("CoreGraphics drawing model not supported, can't create a plugin instance.\n");
    return NPERR_INCOMPATIBLE_VERSION_ERROR;
  }

#ifndef NP_NO_CARBON
  // The test plugin will test using Carbon NPAPI if it is available. This
  // is simply because we want to test Gecko's Carbon NPAPI support. You can
  // override this behavior with an environment variable.
  if (!getenv("TEST_COCOA_NPAPI")) {
    NPBool supportsCarbonEvents = false;
    if ((NPN_GetValue(npp, NPNVsupportsCarbonBool, &supportsCarbonEvents) == NPERR_NO_ERROR) &&
        supportsCarbonEvents) {
      instanceData->eventModel = NPEventModelCarbon;
      return NPERR_NO_ERROR;
    }
  }
#endif

#if 0 // USE_COCOA_NPAPI
  NPBool supportsCocoaEvents = false;
  if ((NPN_GetValue(npp, NPNVsupportsCocoaBool, &supportsCocoaEvents) == NPERR_NO_ERROR) &&
      supportsCocoaEvents) {
    NPN_SetValue(npp, NPPVpluginEventModel, (void*)NPEventModelCocoa);
    instanceData->eventModel = NPEventModelCocoa;
  } else {
    printf("Cocoa event model not supported, can't create a plugin instance.\n");
    return NPERR_INCOMPATIBLE_VERSION_ERROR;
  }
#endif

  return NPERR_NO_ERROR;

}

void
pluginInstanceShutdown(InstanceData* instanceData)
{
}

static bool
RectEquals(const NPRect& r1, const NPRect& r2)
{
  return r1.left == r2.left && r1.top == r2.top &&
         r1.right == r2.right && r1.bottom == r2.bottom;
}

void
pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow)
{
  // Ugh. Due to a terrible Gecko bug, we have to ignore position changes
  // when the clip rect doesn't change; the position can be wrong
  // when set by a path other than nsObjectFrame::FixUpPluginWindow.
  int32_t oldX = instanceData->window.x;
  int32_t oldY = instanceData->window.y;
  bool clipChanged =
    !RectEquals(instanceData->window.clipRect, newWindow->clipRect);
  instanceData->window = *newWindow;
  if (!clipChanged) {
    instanceData->window.x = oldX;
    instanceData->window.y = oldY;
  }
}

void
pluginWidgetInit(InstanceData* instanceData, void* oldWindow)
{
  // Should never be called since we don't support window mode
}

static void 
GetColorsFromRGBA(PRUint32 rgba, float* r, float* g, float* b, float* a)
{
  *b = (rgba & 0xFF) / 255.0;
  *g = ((rgba & 0xFF00) >> 8) / 255.0;
  *r = ((rgba & 0xFF0000) >> 16) / 255.0;
  *a = ((rgba & 0xFF000000) >> 24) / 255.0;
}

static void
pluginDraw(InstanceData* instanceData, NPCocoaEvent* event)
{
  if (!instanceData)
    return;

  notifyDidPaint(instanceData);

  NPP npp = instanceData->npp;
  if (!npp)
    return;
  
  const char* uaString = NPN_UserAgent(npp);
  if (!uaString)
    return;

  NPWindow window = instanceData->window;

  CGContextRef cgContext = NULL;
#ifndef NP_NO_CARBON
  if (instanceData->eventModel == NPEventModelCocoa) {
    cgContext = event->data.draw.context;
  } else {
    cgContext = ((NP_CGContext*)(window.window))->context;
  }
#else
  cgContext = event->data.draw.context;
#endif

  float windowWidth = window.width;
  float windowHeight = window.height;

  switch(instanceData->scriptableObject->drawMode) {
  case DM_DEFAULT: {
    CFStringRef uaCFString = CFStringCreateWithCString(kCFAllocatorDefault, uaString, kCFStringEncodingASCII);
    // save the cgcontext gstate
    CGContextSaveGState(cgContext);

    // we get a flipped context
    CGContextTranslateCTM(cgContext, 0.0, windowHeight);
    CGContextScaleCTM(cgContext, 1.0, -1.0);

    // draw a gray background for the plugin
    CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));
    CGContextSetGrayFillColor(cgContext, 0.5, 1.0);
    CGContextDrawPath(cgContext, kCGPathFill);

    // draw a black frame around the plugin
    CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));
    CGContextSetGrayStrokeColor(cgContext, 0.0, 1.0);
    CGContextSetLineWidth(cgContext, 6.0);
    CGContextStrokePath(cgContext);

#if(0) // USE_COCOA_NPAPI
// from Fx 3.6

    // draw the UA string using Core Text
    CGContextSetTextMatrix(cgContext, CGAffineTransformIdentity);

    // Initialize a rectangular path.
    CGMutablePathRef path = CGPathCreateMutable();
    CGRect bounds = CGRectMake(10.0, 10.0, NS_MAX(0.0, windowWidth - 20.0),
                               NS_MAX(0.0, windowHeight - 20.0));
    CGPathAddRect(path, NULL, bounds);

    // Initialize an attributed string.
    CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
    CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), uaCFString);

    // Create a color and add it as an attribute to the string.
    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    CGFloat components[] = { 0.0, 0.0, 0.0, 1.0 };
    CGColorRef red = CGColorCreate(rgbColorSpace, components);    
    CGColorSpaceRelease(rgbColorSpace);
    CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 50), kCTForegroundColorAttributeName, red);

    // Create the framesetter with the attributed string.
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
    CFRelease(attrString);

    // Create the frame and draw it into the graphics context
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
    CFRelease(framesetter);
    if (frame) {
      CTFrameDraw(frame, cgContext);
      CFRelease(frame);
    }
#else
    CGContextSetGrayFillColor(cgContext, 0.0, 1.0);
    ATSUStyle atsuStyle;
    ATSUCreateStyle(&atsuStyle);
    CFIndex stringLength = CFStringGetLength(uaCFString);
    UniChar* unicharBuffer = (UniChar*)malloc((stringLength + 1) * sizeof(UniChar));
    CFStringGetCharacters(uaCFString, CFRangeMake(0, stringLength), unicharBuffer);
    UniCharCount runLengths = kATSUToTextEnd;
    ATSUTextLayout atsuLayout;
    ATSUCreateTextLayoutWithTextPtr(unicharBuffer,
                                    kATSUFromTextBeginning,
                                    kATSUToTextEnd,
                                    stringLength,
                                    1,
                                    &runLengths,
                                    &atsuStyle,
                                    &atsuLayout);
    ATSUAttributeTag contextTag = kATSUCGContextTag;
    ByteCount byteSize = sizeof(CGContextRef);
    ATSUAttributeValuePtr contextATSUPtr = &cgContext;
    ATSUSetLayoutControls(atsuLayout, 1, &contextTag, &byteSize, &contextATSUPtr);
    ATSUTextMeasurement lineAscent, lineDescent;
    ATSUGetLineControl(atsuLayout,
                       kATSUFromTextBeginning,
                       kATSULineAscentTag,
                       sizeof(ATSUTextMeasurement),
                       &lineAscent,
                       &byteSize);
    ATSUGetLineControl(atsuLayout,
                       kATSUFromTextBeginning,
                       kATSULineDescentTag,
                       sizeof(ATSUTextMeasurement),
                       &lineDescent,
                       &byteSize);
    float lineHeight = FixedToFloat(lineAscent) + FixedToFloat(lineDescent);
    ItemCount softBreakCount;
    ATSUBatchBreakLines(atsuLayout,
                        kATSUFromTextBeginning,
                        stringLength,
                        FloatToFixed(windowWidth - 10.0),
                        &softBreakCount);
    ATSUGetSoftLineBreaks(atsuLayout,
                          kATSUFromTextBeginning,
                          kATSUToTextEnd,
                          0, NULL, &softBreakCount);
    UniCharArrayOffset* softBreaks = (UniCharArrayOffset*)malloc(softBreakCount * sizeof(UniCharArrayOffset));
    ATSUGetSoftLineBreaks(atsuLayout,
                          kATSUFromTextBeginning,
                          kATSUToTextEnd,
                          softBreakCount, softBreaks, &softBreakCount);
    UniCharArrayOffset currentDrawOffset = kATSUFromTextBeginning;
    unsigned int i = 0;
    while (i < softBreakCount) {
      ATSUDrawText(atsuLayout, currentDrawOffset, softBreaks[i], FloatToFixed(5.0),
                   FloatToFixed(windowHeight - 5.0 - (lineHeight * (i + 1.0))));
      currentDrawOffset = softBreaks[i];
      i++;
    }
    ATSUDrawText(atsuLayout, currentDrawOffset, kATSUToTextEnd, FloatToFixed(5.0),
                 FloatToFixed(windowHeight - 5.0 - (lineHeight * (i + 1.0))));
    free(unicharBuffer);
    free(softBreaks);
#endif

    // restore the cgcontext gstate
    CGContextRestoreGState(cgContext);
    break;
  }
  case DM_SOLID_COLOR: {
    // save the cgcontext gstate
    CGContextSaveGState(cgContext);

    // we get a flipped context
    CGContextTranslateCTM(cgContext, 0.0, windowHeight);
    CGContextScaleCTM(cgContext, 1.0, -1.0);

    // draw a solid background for the plugin
    CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));

    float r,g,b,a;
    GetColorsFromRGBA(instanceData->scriptableObject->drawColor, &r, &g, &b, &a);
    CGContextSetRGBFillColor(cgContext, r, g, b, a);
    CGContextDrawPath(cgContext, kCGPathFill);

    // restore the cgcontext gstate
    CGContextRestoreGState(cgContext);
    break;
  }
  }
}

int16_t
pluginHandleEvent(InstanceData* instanceData, void* event)
{
#ifndef NP_NO_CARBON
  if (instanceData->eventModel == NPEventModelCarbon) {
    EventRecord* carbonEvent = (EventRecord*)event;
    if (!carbonEvent)
      return kNPEventNotHandled;

    NPWindow* w = &instanceData->window;
    switch (carbonEvent->what) {
      case updateEvt:
        pluginDraw(instanceData, NULL);
        break;
      case mouseDown:
      case mouseUp:
      case osEvt:
      {
        Rect globalBounds = {0};
        WindowRef nativeWindow = static_cast<WindowRef>(static_cast<NP_CGContext*>(w->window)->window);
        if (nativeWindow)
          ::GetWindowBounds(nativeWindow, kWindowStructureRgn, &globalBounds);
        instanceData->lastMouseX = carbonEvent->where.h - w->x - globalBounds.left;
        instanceData->lastMouseY = carbonEvent->where.v - w->y - globalBounds.top;
        break;
      }
      default:
        return kNPEventNotHandled;
    }

    return kNPEventHandled;
  }
#endif

  NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)event;
  if (!cocoaEvent)
    return kNPEventNotHandled;

  switch (cocoaEvent->type) {
    case NPCocoaEventDrawRect:
      pluginDraw(instanceData, cocoaEvent);
      break;
    case NPCocoaEventMouseDown:
    case NPCocoaEventMouseUp:
    case NPCocoaEventMouseMoved:
      instanceData->lastMouseX = (int32_t)cocoaEvent->data.mouse.pluginX;
      instanceData->lastMouseY = (int32_t)cocoaEvent->data.mouse.pluginY;
      break;
    case NPCocoaEventWindowFocusChanged:
      instanceData->topLevelWindowActivationState = cocoaEvent->data.focus.hasFocus ?
        ACTIVATION_STATE_ACTIVATED : ACTIVATION_STATE_DEACTIVATED;
      instanceData->topLevelWindowActivationEventCount = instanceData->topLevelWindowActivationEventCount + 1;
      break;
    case NPCocoaEventFocusChanged:
      instanceData->focusState = cocoaEvent->data.focus.hasFocus ?
      ACTIVATION_STATE_ACTIVATED : ACTIVATION_STATE_DEACTIVATED;
      instanceData->focusEventCount = instanceData->focusEventCount + 1;
      break;
    default:
      return kNPEventNotHandled;
  }

  return kNPEventHandled;
}

int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge)
{
  NPWindow* w = &instanceData->window;
  switch (edge) {
  case EDGE_LEFT:
    return w->x;
  case EDGE_TOP:
    return w->y;
  case EDGE_RIGHT:
    return w->x + w->width;
  case EDGE_BOTTOM:
    return w->y + w->height;
  }
  return NPTEST_INT32_ERROR;
}

int32_t pluginGetClipRegionRectCount(InstanceData* instanceData)
{
  return 1;
}

int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, 
    int32_t rectIndex, RectEdge edge)
{
  if (rectIndex != 0)
    return NPTEST_INT32_ERROR;

  // We have to add the Cocoa titlebar height here since the clip rect
  // is being returned relative to that
  static const int COCOA_TITLEBAR_HEIGHT = 22;

  NPWindow* w = &instanceData->window;
  switch (edge) {
  case EDGE_LEFT:
    return w->clipRect.left;
  case EDGE_TOP:
    return w->clipRect.top + COCOA_TITLEBAR_HEIGHT;
  case EDGE_RIGHT:
    return w->clipRect.right;
  case EDGE_BOTTOM:
    return w->clipRect.bottom + COCOA_TITLEBAR_HEIGHT;
  }
  return NPTEST_INT32_ERROR;
}

void pluginDoInternalConsistencyCheck(InstanceData* instanceData, string& error)
{
}
