/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkWindow.h"
#include "SkCanvas.h"
#include "SkOSMenu.h"
#include "SkSurface.h"
#include "SkSystemEventTypes.h"
#include "SkTime.h"

#define SK_EventDelayInval "\xd" "n" "\xa" "l"

SkWindow::SkWindow()
    : fSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType)
    , fFocusView(nullptr)
{
    fClicks.reset();
    fWaitingOnInval = false;
    fMatrix.reset();

    fBitmap.allocN32Pixels(0, 0);
}

SkWindow::~SkWindow() {
    fClicks.deleteAll();
    fMenus.deleteAll();
}

SkSurface* SkWindow::createSurface() {
    const SkBitmap& bm = this->getBitmap();
    return SkSurface::MakeRasterDirect(bm.info(), bm.getPixels(), bm.rowBytes(),
                                       &fSurfaceProps).release();
}

void SkWindow::setMatrix(const SkMatrix& matrix) {
    if (fMatrix != matrix) {
        fMatrix = matrix;
        this->inval(nullptr);
    }
}

void SkWindow::preConcat(const SkMatrix& matrix) {
    SkMatrix m;
    m.setConcat(fMatrix, matrix);
    this->setMatrix(m);
}

void SkWindow::postConcat(const SkMatrix& matrix) {
    SkMatrix m;
    m.setConcat(matrix, fMatrix);
    this->setMatrix(m);
}

void SkWindow::resize(const SkImageInfo& info) {
    if (fBitmap.info() != info) {
        fBitmap.allocPixels(info);
        this->inval(nullptr);
    }
    this->setSize(SkIntToScalar(fBitmap.width()), SkIntToScalar(fBitmap.height()));
}

void SkWindow::resize(int width, int height) {
    this->resize(fBitmap.info().makeWH(width, height));
}

void SkWindow::setColorType(SkColorType ct, SkColorProfileType pt) {
    const SkImageInfo& info = fBitmap.info();
    this->resize(SkImageInfo::Make(info.width(), info.height(), ct, kPremul_SkAlphaType, pt));

    // Set the global flag that enables or disables "legacy" mode, depending on our format.
    // With sRGB 32-bit or linear FP 16, we turn on gamma-correct handling of inputs:
    SkSurfaceProps props = this->getSurfaceProps();
    uint32_t flags = (props.flags() & ~SkSurfaceProps::kAllowSRGBInputs_Flag) |
        (SkColorAndProfileAreGammaCorrect(ct, pt) ? SkSurfaceProps::kAllowSRGBInputs_Flag : 0);
    this->setSurfaceProps(SkSurfaceProps(flags, props.pixelGeometry()));
}

bool SkWindow::handleInval(const SkRect* localR) {
    SkIRect ir;

    if (localR) {
        SkRect devR;
        SkMatrix inverse;
        if (!fMatrix.invert(&inverse)) {
            return false;
        }
        fMatrix.mapRect(&devR, *localR);
        devR.round(&ir);
    } else {
        ir.set(0, 0,
               SkScalarRoundToInt(this->width()),
               SkScalarRoundToInt(this->height()));
    }
    fDirtyRgn.op(ir, SkRegion::kUnion_Op);

    this->onHandleInval(ir);
    return true;
}

void SkWindow::forceInvalAll() {
    fDirtyRgn.setRect(0, 0,
                      SkScalarCeilToInt(this->width()),
                      SkScalarCeilToInt(this->height()));
}

#ifdef SK_SIMULATE_FAILED_MALLOC
extern bool gEnableControlledThrow;
#endif

bool SkWindow::update(SkIRect* updateArea) {
    if (!fDirtyRgn.isEmpty()) {
        SkAutoTUnref<SkSurface> surface(this->createSurface());
        SkCanvas* canvas = surface->getCanvas();

        canvas->clipRegion(fDirtyRgn);
        if (updateArea) {
            *updateArea = fDirtyRgn.getBounds();
        }

        SkAutoCanvasRestore acr(canvas, true);
        canvas->concat(fMatrix);

        // empty this now, so we can correctly record any inval calls that
        // might be made during the draw call.
        fDirtyRgn.setEmpty();

#ifdef SK_SIMULATE_FAILED_MALLOC
        gEnableControlledThrow = true;
#endif
#ifdef SK_BUILD_FOR_WIN32
        //try {
            this->draw(canvas);
        //}
        //catch (...) {
        //}
#else
        this->draw(canvas);
#endif
#ifdef SK_SIMULATE_FAILED_MALLOC
        gEnableControlledThrow = false;
#endif

        return true;
    }
    return false;
}

bool SkWindow::handleChar(SkUnichar uni) {
    if (this->onHandleChar(uni))
        return true;

    SkView* focus = this->getFocusView();
    if (focus == nullptr)
        focus = this;

    SkEvent evt(SK_EventType_Unichar);
    evt.setFast32(uni);
    return focus->doEvent(evt);
}

bool SkWindow::handleKey(SkKey key) {
    if (key == kNONE_SkKey)
        return false;

    if (this->onHandleKey(key))
        return true;

    // send an event to the focus-view
    {
        SkView* focus = this->getFocusView();
        if (focus == nullptr)
            focus = this;

        SkEvent evt(SK_EventType_Key);
        evt.setFast32(key);
        if (focus->doEvent(evt))
            return true;
    }

    if (key == kUp_SkKey || key == kDown_SkKey) {
        if (this->moveFocus(key == kUp_SkKey ? kPrev_FocusDirection : kNext_FocusDirection) == nullptr)
            this->onSetFocusView(nullptr);
        return true;
    }
    return false;
}

bool SkWindow::handleKeyUp(SkKey key) {
    if (key == kNONE_SkKey)
        return false;

    if (this->onHandleKeyUp(key))
        return true;

    //send an event to the focus-view
    {
        SkView* focus = this->getFocusView();
        if (focus == nullptr)
            focus = this;

        //should this one be the same?
        SkEvent evt(SK_EventType_KeyUp);
        evt.setFast32(key);
        if (focus->doEvent(evt))
            return true;
    }
    return false;
}

void SkWindow::addMenu(SkOSMenu* menu) {
    *fMenus.append() = menu;
    this->onAddMenu(menu);
}

void SkWindow::setTitle(const char title[]) {
    if (nullptr == title) {
        title = "";
    }
    fTitle.set(title);
    this->onSetTitle(title);
}

bool SkWindow::onEvent(const SkEvent& evt) {
    if (evt.isType(SK_EventDelayInval)) {
        for (SkRegion::Iterator iter(fDirtyRgn); !iter.done(); iter.next())
            this->onHandleInval(iter.rect());
        fWaitingOnInval = false;
        return true;
    }
    return this->INHERITED::onEvent(evt);
}

bool SkWindow::onGetFocusView(SkView** focus) const {
    if (focus)
        *focus = fFocusView;
    return true;
}

bool SkWindow::onSetFocusView(SkView* focus) {
    if (fFocusView != focus) {
        if (fFocusView)
            fFocusView->onFocusChange(false);
        fFocusView = focus;
        if (focus)
            focus->onFocusChange(true);
    }
    return true;
}

void SkWindow::onHandleInval(const SkIRect&) {
}

bool SkWindow::onHandleChar(SkUnichar) {
    return false;
}

bool SkWindow::onHandleKey(SkKey) {
    return false;
}

bool SkWindow::onHandleKeyUp(SkKey) {
    return false;
}

bool SkWindow::handleClick(int x, int y, Click::State state, void *owner,
                           unsigned modifierKeys) {
    return this->onDispatchClick(x, y, state, owner, modifierKeys);
}

bool SkWindow::onDispatchClick(int x, int y, Click::State state,
                               void* owner, unsigned modifierKeys) {
    bool handled = false;

    // First, attempt to find an existing click with this owner.
    int index = -1;
    for (int i = 0; i < fClicks.count(); i++) {
        if (owner == fClicks[i]->fOwner) {
            index = i;
            break;
        }
    }

    switch (state) {
        case Click::kDown_State: {
            if (index != -1) {
                delete fClicks[index];
                fClicks.remove(index);
            }
            Click* click = this->findClickHandler(SkIntToScalar(x),
                                                  SkIntToScalar(y), modifierKeys);

            if (click) {
                click->fOwner = owner;
                *fClicks.append() = click;
                SkView::DoClickDown(click, x, y, modifierKeys);
                handled = true;
            }
            break;
        }
        case Click::kMoved_State:
            if (index != -1) {
                SkView::DoClickMoved(fClicks[index], x, y, modifierKeys);
                handled = true;
            }
            break;
        case Click::kUp_State:
            if (index != -1) {
                SkView::DoClickUp(fClicks[index], x, y, modifierKeys);
                delete fClicks[index];
                fClicks.remove(index);
                handled = true;
            }
            break;
        default:
            // Do nothing
            break;
    }
    return handled;
}

#if SK_SUPPORT_GPU

#include "GrContext.h"
#include "gl/GrGLInterface.h"
#include "gl/GrGLUtil.h"
#include "SkGr.h"

GrRenderTarget* SkWindow::renderTarget(const AttachmentInfo& attachmentInfo,
        const GrGLInterface* interface, GrContext* grContext) {
    GrBackendRenderTargetDesc desc;
    desc.fWidth = SkScalarRoundToInt(this->width());
    desc.fHeight = SkScalarRoundToInt(this->height());
    // TODO: Query the actual framebuffer for sRGB capable. However, to
    // preserve old (fake-linear) behavior, we don't do this. Instead, rely
    // on the flag (currently driven via 'C' mode in SampleApp).
    //
    // Also, we may not have real sRGB support (ANGLE, in particular), so check for
    // that, and fall back to L32:
    desc.fConfig = grContext->caps()->srgbSupport() && SkImageInfoIsGammaCorrect(info())
        ? kSkiaGamma8888_GrPixelConfig
        : kSkia8888_GrPixelConfig;
    desc.fOrigin = kBottomLeft_GrSurfaceOrigin;
    desc.fSampleCnt = attachmentInfo.fSampleCount;
    desc.fStencilBits = attachmentInfo.fStencilBits;
    GrGLint buffer;
    GR_GL_GetIntegerv(interface, GR_GL_FRAMEBUFFER_BINDING, &buffer);
    desc.fRenderTargetHandle = buffer;
    return grContext->textureProvider()->wrapBackendRenderTarget(desc);
}

#endif
