/*
 * 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 "SkAtomics.h"
#include "SkOSMenu.h"
#include <stdarg.h>

static int gOSMenuCmd = 7000;

SkOSMenu::SkOSMenu(const char title[]) {
    fTitle.set(title);
}

SkOSMenu::~SkOSMenu() {
    this->reset();
}

void SkOSMenu::reset() {
    fItems.deleteAll();
    fTitle.reset();
}

const SkOSMenu::Item* SkOSMenu::getItemByID(int itemID) const {
    for (int i = 0; i < fItems.count(); ++i) {
        if (itemID == fItems[i]->getID())
            return fItems[i];
    }
    return nullptr;
}

void SkOSMenu::getItems(const SkOSMenu::Item* items[]) const {
    if (items) {
        for (int i = 0; i < fItems.count(); ++i) {
            items[i] = fItems[i];
        }
    }
}

void SkOSMenu::assignKeyEquivalentToItem(int itemID, SkUnichar key) {
    for (int i = 0; i < fItems.count(); ++i) {
        if (itemID == fItems[i]->getID())
            fItems[i]->setKeyEquivalent(key);
    }
}

bool SkOSMenu::handleKeyEquivalent(SkUnichar key) {
    int value = 0, size = 0;
    bool state;
    SkOSMenu::TriState tristate;
    for (int i = 0; i < fItems.count(); ++i) {
        Item* item = fItems[i];
        if (item->getKeyEquivalent()== key) {
            SkString list;
            switch (item->getType()) {
                case kList_Type:
                    SkOSMenu::FindListItemCount(*item->getEvent(), &size);
                    SkOSMenu::FindListIndex(*item->getEvent(), item->getSlotName(), &value);
                    value = (value + 1) % size;
                    item->setInt(value);
                    break;
                case kSwitch_Type:
                    SkOSMenu::FindSwitchState(*item->getEvent(), item->getSlotName(), &state);
                    item->setBool(!state);
                    break;
                case kTriState_Type:
                    SkOSMenu::FindTriState(*item->getEvent(), item->getSlotName(), &tristate);
                    if (kOnState == tristate)
                        tristate = kMixedState;
                    else
                        tristate = (SkOSMenu::TriState)((int)tristate + 1);
                    item->setTriState(tristate);
                    break;
                case kAction_Type:
                case kCustom_Type:
                case kSlider_Type:
                case kTextField_Type:
                default:
                    break;
            }
            item->postEvent();
            return true;
        }
    }
    return false;
}

////////////////////////////////////////////////////////////////////////////////

SkOSMenu::Item::Item(const char label[], SkOSMenu::Type type,
                     const char slotName[], SkEvent* evt) {
    fLabel.set(label);
    fSlotName.set(slotName);
    fType = type;
    fEvent = evt;
    fKey = 0;
    fID = sk_atomic_inc(&gOSMenuCmd);
}

void SkOSMenu::Item::setBool(bool value) const {
    SkASSERT(SkOSMenu::kSwitch_Type == fType);
    fEvent->setBool(fSlotName.c_str(), value);
}

void SkOSMenu::Item::setScalar(SkScalar value) const {
    SkASSERT(SkOSMenu::kSlider_Type == fType);
    fEvent->setScalar(fSlotName.c_str(), value);
}

void SkOSMenu::Item::setInt(int value) const {
    SkASSERT(SkOSMenu::kList_Type == fType);
    fEvent->setS32(fSlotName.c_str(), value);
}

void SkOSMenu::Item::setTriState(TriState value) const {
    SkASSERT(SkOSMenu::kTriState_Type == fType);
    fEvent->setS32(fSlotName.c_str(), value);
}

void SkOSMenu::Item::setString(const char value[]) const {
    SkASSERT(SkOSMenu::kTextField_Type == fType);
    fEvent->setString(fSlotName.c_str(), value);
}

////////////////////////////////////////////////////////////////////////////////

static const char* gMenuEventType = "SkOSMenuEventType";
static const char* gSlider_Min_Scalar = "SkOSMenuSlider_Min";
static const char* gSlider_Max_Scalar = "SkOSMenuSlider_Max";
static const char* gDelimiter = "|";
static const char* gList_Items_Str = "SkOSMenuList_Items";
static const char* gList_ItemCount_S32 = "SkOSMenuList_ItemCount";

int SkOSMenu::appendItem(const char label[], Type type, const char slotName[],
                         SkEvent* evt) {
    SkOSMenu::Item* item = new Item(label, type, slotName, evt);
    fItems.append(1, &item);
    return item->getID();
}

int SkOSMenu::appendAction(const char label[], SkEventSinkID target) {
    SkEvent* evt = new SkEvent(gMenuEventType, target);
    //Store label in event so it can be used to identify the action later
    evt->setString(label, label);
    return appendItem(label, SkOSMenu::kAction_Type, "", evt);
}

int SkOSMenu::appendList(const char label[], const char slotName[],
                         SkEventSinkID target, int index, const char option[], ...) {
    SkEvent* evt = new SkEvent(gMenuEventType, target);
    va_list args;
    if (option) {
        SkString str(option);
        va_start(args, option);
        int count = 1;
        for (const char* arg = va_arg(args, const char*); arg != nullptr; arg = va_arg(args, const char*)) {
            str += gDelimiter;
            str += arg;
            ++count;
        }
        va_end(args);
        evt->setString(gList_Items_Str, str);
        evt->setS32(gList_ItemCount_S32, count);
        evt->setS32(slotName, index);
    }
    return appendItem(label, SkOSMenu::kList_Type, slotName, evt);
}

int SkOSMenu::appendSlider(const char label[], const char slotName[],
                           SkEventSinkID target, SkScalar min, SkScalar max,
                           SkScalar defaultValue) {
    SkEvent* evt = new SkEvent(gMenuEventType, target);
    evt->setScalar(gSlider_Min_Scalar, min);
    evt->setScalar(gSlider_Max_Scalar, max);
    evt->setScalar(slotName, defaultValue);
    return appendItem(label, SkOSMenu::kSlider_Type, slotName, evt);
}

int SkOSMenu::appendSwitch(const char label[], const char slotName[],
                           SkEventSinkID target, bool defaultState) {
    SkEvent* evt = new SkEvent(gMenuEventType, target);
    evt->setBool(slotName, defaultState);
    return appendItem(label, SkOSMenu::kSwitch_Type, slotName, evt);
}

int SkOSMenu::appendTriState(const char label[], const char slotName[],
                             SkEventSinkID target, SkOSMenu::TriState defaultState) {
    SkEvent* evt = new SkEvent(gMenuEventType, target);
    evt->setS32(slotName, defaultState);
    return appendItem(label, SkOSMenu::kTriState_Type, slotName, evt);
}

int SkOSMenu::appendTextField(const char label[], const char slotName[],
                              SkEventSinkID target, const char placeholder[]) {
    SkEvent* evt = new SkEvent(gMenuEventType, target);
    evt->setString(slotName, placeholder);
    return appendItem(label, SkOSMenu::kTextField_Type, slotName, evt);
}

bool SkOSMenu::FindListItemCount(const SkEvent& evt, int* count) {
    return evt.isType(gMenuEventType) && evt.findS32(gList_ItemCount_S32, count);
}

bool SkOSMenu::FindListItems(const SkEvent& evt, SkString items[]) {
    if (evt.isType(gMenuEventType) && items) {
        const char* text = evt.findString(gList_Items_Str);
        if (text != nullptr) {
            SkString temp(text);
            char* token = strtok((char*)temp.c_str(), gDelimiter);
            int index = 0;
            while (token != nullptr) {
                items[index].set(token, strlen(token));
                token = strtok (nullptr, gDelimiter);
                ++index;
            }
        }
        return true;
    }
    return false;
}

bool SkOSMenu::FindSliderMin(const SkEvent& evt, SkScalar* min) {
    return evt.isType(gMenuEventType) && evt.findScalar(gSlider_Min_Scalar, min);
}

bool SkOSMenu::FindSliderMax(const SkEvent& evt, SkScalar* max) {
    return evt.isType(gMenuEventType) && evt.findScalar(gSlider_Max_Scalar, max);
}

bool SkOSMenu::FindAction(const SkEvent& evt, const char label[]) {
    return evt.isType(gMenuEventType) && evt.findString(label);
}

bool SkOSMenu::FindListIndex(const SkEvent& evt, const char slotName[], int* value) {
    return evt.isType(gMenuEventType) && evt.findS32(slotName, value);
}

bool SkOSMenu::FindSliderValue(const SkEvent& evt, const char slotName[], SkScalar* value) {
    return evt.isType(gMenuEventType) && evt.findScalar(slotName, value);
}

bool SkOSMenu::FindSwitchState(const SkEvent& evt, const char slotName[], bool* value) {
    return evt.isType(gMenuEventType) && evt.findBool(slotName, value);
}

bool SkOSMenu::FindTriState(const SkEvent& evt, const char slotName[], SkOSMenu::TriState* value) {
    return evt.isType(gMenuEventType) && evt.findS32(slotName, (int*)value);
}

bool SkOSMenu::FindText(const SkEvent& evt, const char slotName[], SkString* value) {
    if (evt.isType(gMenuEventType)) {
        const char* text = evt.findString(slotName);
        if (!text || !*text)
            return false;
        else {
            value->set(text);
            return true;
        }
    }
    return false;
}
