/*
 * Copyright (C) 2015 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. 
 */

#include "config.h"
#include "ObjectPropertyConditionSet.h"

#include "JSCInlines.h"
#include <wtf/ListDump.h>

namespace JSC {

ObjectPropertyCondition ObjectPropertyConditionSet::forObject(JSObject* object) const
{
	for(iterator it = begin(); it != end(); ++it) {
        if (it->object() == object)
            return *it;
    }
    return ObjectPropertyCondition();
}

ObjectPropertyCondition ObjectPropertyConditionSet::forConditionKind(
    PropertyCondition::Kind kind) const
{
	for(iterator it = begin(); it != end(); ++it) {
        if (it->kind() == kind)
            return *it;
    }
    return ObjectPropertyCondition();
}

unsigned ObjectPropertyConditionSet::numberOfConditionsWithKind(PropertyCondition::Kind kind) const
{
    unsigned result = 0;
	for(iterator it = begin(); it != end(); ++it) {
        if (it->kind() == kind)
            result++;
    }
    return result;
}

bool ObjectPropertyConditionSet::hasOneSlotBaseCondition() const
{
    return (numberOfConditionsWithKind(PropertyCondition::Presence) == 1) != (numberOfConditionsWithKind(PropertyCondition::Equivalence) == 1);
}

ObjectPropertyCondition ObjectPropertyConditionSet::slotBaseCondition() const
{
    ObjectPropertyCondition result;
    unsigned numFound = 0;
	for(iterator it = begin(); it != end(); ++it) {
		const ObjectPropertyCondition& condition = *it;
        if (condition.kind() == PropertyCondition::Presence
			|| condition.kind() == PropertyCondition::Equivalence) {
            result = *it;
            numFound++;
        }
    }
    RELEASE_ASSERT(numFound == 1);
    return result;
}

ObjectPropertyConditionSet ObjectPropertyConditionSet::mergedWith(
    const ObjectPropertyConditionSet& other) const
{
    if (!isValid() || !other.isValid())
        return invalid();

    Vector<ObjectPropertyCondition> result;
    
    if (!isEmpty())
        result.appendVector(m_data->vector);

	for(iterator newIter = other.begin(); newIter != other.end(); ++newIter) {
		const ObjectPropertyCondition& newCondition = *newIter;
        bool foundMatch = false;
    	for(iterator existingIter = this->begin(); existingIter != this->end(); ++existingIter) {
    		const ObjectPropertyCondition& existingCondition = *existingIter;
            if (newCondition == existingCondition) {
                foundMatch = true;
                continue;
            }
            if (!newCondition.isCompatibleWith(existingCondition))
                return invalid();
    	}
        if (!foundMatch)
            result.append(newCondition);
    }

    return create(result);
}

bool ObjectPropertyConditionSet::structuresEnsureValidity(VM& vm) const
{
    if (!isValid())
        return false;

	for(iterator it = begin(); it != end(); ++it) {
        if (!it->structureEnsuresValidity(vm))
            return false;
    }
    return true;
}

bool ObjectPropertyConditionSet::structuresEnsureValidityAssumingImpurePropertyWatchpoint(VM& vm) const
{
    if (!isValid())
        return false;

	for(iterator it = begin(); it != end(); ++it) {
        if (!it->structureEnsuresValidityAssumingImpurePropertyWatchpoint(vm))
            return false;
    }
    return true;
}

bool ObjectPropertyConditionSet::needImpurePropertyWatchpoint() const
{
	for(iterator it = begin(); it != end(); ++it) {
        if (it->validityRequiresImpurePropertyWatchpoint())
            return true;
    }
    return false;
}

bool ObjectPropertyConditionSet::areStillLive() const
{
	for(iterator it = begin(); it != end(); ++it) {
        if (!it->isStillLive())
            return false;
    }
    return true;
}

void ObjectPropertyConditionSet::dumpInContext(PrintStream& out, DumpContext* context) const
{
    if (!isValid()) {
        out.print("<invalid>");
        return;
    }
    
    out.print("[");
    // if (m_data)
    //    out.print(listDumpInContext(m_data->vector, context));
    out.print("]");
}

void ObjectPropertyConditionSet::dump(PrintStream& out) const
{
    dumpInContext(out, 0);
}

namespace {

bool verbose = false;

ObjectPropertyCondition generateCondition(
    VM& vm, JSCell* owner, JSObject* object, UniquedStringImpl* uid, PropertyCondition::Kind conditionKind)
{
    Structure* structure = object->structure();
    if (verbose)
        dataLog("Creating condition ", conditionKind, " for ", pointerDump(structure), "\n");

    ObjectPropertyCondition result;
    switch (conditionKind) {
    case PropertyCondition::Presence: {
        unsigned attributes;
        PropertyOffset offset = structure->getConcurrently(vm, uid, attributes);
        if (offset == invalidOffset)
            return ObjectPropertyCondition();
        result = ObjectPropertyCondition::presence(vm, owner, object, uid, offset, attributes);
        break;
    }
    case PropertyCondition::Absence: {
        result = ObjectPropertyCondition::absence(
            vm, owner, object, uid, object->structure()->storedPrototypeObject());
        break;
    }
    case PropertyCondition::AbsenceOfSetter: {
        result = ObjectPropertyCondition::absenceOfSetter(
            vm, owner, object, uid, object->structure()->storedPrototypeObject());
        break;
    }
    case PropertyCondition::Equivalence: {
        unsigned attributes;
        PropertyOffset offset = structure->getConcurrently(vm, uid, attributes);
        if (offset == invalidOffset)
            return ObjectPropertyCondition();
        JSValue value = object->getDirect(offset);
        result = ObjectPropertyCondition::equivalence(vm, owner, object, uid, value);
        break;
    }
    default:
        RELEASE_ASSERT_NOT_REACHED();
        return ObjectPropertyCondition();
    }

    if (!result.structureEnsuresValidityAssumingImpurePropertyWatchpoint(vm)) {
        if (verbose)
            dataLog("Failed to create condition: ", result, "\n");
        return ObjectPropertyCondition();
    }

    if (verbose)
        dataLog("New condition: ", result, "\n");
    return result;
}

enum Concurrency {
    MainThread,
    Concurrent
};
template<typename Functor>
ObjectPropertyConditionSet generateConditions(
    VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype, const Functor& functor,
    Concurrency concurrency = MainThread)
{
    Vector<ObjectPropertyCondition> conditions;
    
    for (;;) {
        if (verbose)
            dataLog("Considering structure: ", pointerDump(structure), "\n");
        
        if (structure->isProxy()) {
            if (verbose)
                dataLog("It's a proxy, so invalid.\n");
            return ObjectPropertyConditionSet::invalid();
        }
        
        JSValue value = structure->prototypeForLookup(globalObject);
        
        if (value.isNull()) {
            if (!prototype) {
                if (verbose)
                    dataLog("Reached end of prototype chain as expected, done.\n");
                break;
            }
            if (verbose)
                dataLog("Unexpectedly reached end of prototype chain, so invalid.\n");
            return ObjectPropertyConditionSet::invalid();
        }
        
        JSObject* object = jsCast<JSObject*>(value);
        structure = object->structure(vm);
        
        // Since we're accessing a prototype repeatedly, it's a good bet that it should not be
        // treated as a dictionary.
        if (structure->isDictionary()) {
            if (concurrency == MainThread)
                structure->flattenDictionaryStructure(vm, object);
            else {
                if (verbose)
                    dataLog("Cannot flatten dictionary when not on main thread, so invalid.\n");
                return ObjectPropertyConditionSet::invalid();
            }
        }

        if (!functor(conditions, object)) {
            if (verbose)
                dataLog("Functor failed, invalid.\n");
            return ObjectPropertyConditionSet::invalid();
        }
        
        if (object == prototype) {
            if (verbose)
                dataLog("Reached desired prototype, done.\n");
            break;
        }
    }

    if (verbose)
        dataLog("Returning conditions: ", listDump(conditions), "\n");
    return ObjectPropertyConditionSet::create(conditions);
}

} // anonymous namespace

namespace
{
class MissFunctor
{
public:
	MissFunctor(VM& vm, JSCell* owner, UniquedStringImpl* uid)
	: m_vm(vm)
	, m_owner(owner)
	, m_uid(uid)
	{
	}

	bool operator()(Vector<ObjectPropertyCondition>& conditions, JSObject* object) const
	{
		ObjectPropertyCondition result =
				generateCondition(m_vm, m_owner, object, m_uid, PropertyCondition::Absence);
			if (!result)
				return false;
			conditions.append(result);
			return true;
	}
private:
	VM& m_vm;
	JSCell* m_owner;
	UniquedStringImpl* m_uid;
};
}

ObjectPropertyConditionSet generateConditionsForPropertyMiss(
    VM& vm, JSCell* owner, ExecState* exec, Structure* headStructure, UniquedStringImpl* uid)
{
    return generateConditions(
        vm, exec->lexicalGlobalObject(), headStructure, 0, MissFunctor(vm, owner, uid));
}

namespace {
class SetterMissFunctor
{
public:
	SetterMissFunctor(VM& vm, JSCell* owner, UniquedStringImpl* uid)
	: m_vm(vm)
	, m_owner(owner)
	, m_uid(uid)
	{
	}

	bool operator()(Vector<ObjectPropertyCondition>& conditions, JSObject* object) const {
		ObjectPropertyCondition result =
			generateCondition(m_vm, m_owner, object, m_uid, PropertyCondition::AbsenceOfSetter);
		if (!result)
			return false;
		conditions.append(result);
		return true;
	}
private:
	VM& m_vm;
	JSCell* m_owner;
	UniquedStringImpl* m_uid;
};
}

ObjectPropertyConditionSet generateConditionsForPropertySetterMiss(
    VM& vm, JSCell* owner, ExecState* exec, Structure* headStructure, UniquedStringImpl* uid)
{

    return generateConditions(
        vm, exec->lexicalGlobalObject(), headStructure, 0, SetterMissFunctor(vm, owner, uid));
}

namespace {
class HitFunctor
{
public:
	HitFunctor(VM& vm, JSCell* owner, JSObject* prototype, UniquedStringImpl* uid)
	: m_vm(vm)
	, m_owner(owner)
	, m_prototype(prototype)
	, m_uid(uid)
	{
	}

	bool operator()(Vector<ObjectPropertyCondition>& conditions, JSObject* object) const
	{
        PropertyCondition::Kind kind =
            object == m_prototype ? PropertyCondition::Presence : PropertyCondition::Absence;
        ObjectPropertyCondition result =
            generateCondition(m_vm, m_owner, object, m_uid, kind);
        if (!result)
            return false;
        conditions.append(result);
        return true;
	}
private:
	VM& m_vm;
	JSCell* m_owner;
	JSObject* m_prototype;
	UniquedStringImpl* m_uid;
};
}

ObjectPropertyConditionSet generateConditionsForPrototypePropertyHit(
    VM& vm, JSCell* owner, ExecState* exec, Structure* headStructure, JSObject* prototype,
    UniquedStringImpl* uid)
{
    return generateConditions(
        vm, exec->lexicalGlobalObject(), headStructure, prototype, HitFunctor(vm, owner, prototype, uid));
}

ObjectPropertyConditionSet generateConditionsForPrototypeEquivalenceConcurrently(
    VM& vm, JSGlobalObject* globalObject, Structure* headStructure, JSObject* prototype, UniquedStringImpl* uid)
{
	class EquivalenceConcurrentlyFunctor
	{
	public:
		EquivalenceConcurrentlyFunctor(VM& vm, JSObject* prototype, UniquedStringImpl* uid)
		: m_vm(vm)
		, m_prototype(prototype)
		, m_uid(uid)
		{
		}
		bool operator()(Vector<ObjectPropertyCondition>& conditions, JSObject* object) const {
            PropertyCondition::Kind kind =
                object == m_prototype ? PropertyCondition::Equivalence : PropertyCondition::Absence;
            ObjectPropertyCondition result = generateCondition(m_vm, 0, object, m_uid, kind);
            if (!result)
                return false;
            conditions.append(result);
            return true;
        }
	private:
		VM& m_vm;
		JSObject* m_prototype;
		UniquedStringImpl* m_uid;
	};
    return generateConditions(vm, globalObject, headStructure, prototype, EquivalenceConcurrentlyFunctor(vm, prototype, uid), Concurrent);
}

namespace
{
class MissConcurrentlyFunctor
{
public:
	MissConcurrentlyFunctor(VM& vm, JSCell* owner, UniquedStringImpl* uid)
	: m_vm(vm)
	, m_owner(owner)
	, m_uid(uid)
	{
	}

	bool operator()(Vector<ObjectPropertyCondition>& conditions, JSObject* object) const
	{
		ObjectPropertyCondition result = generateCondition(m_vm, m_owner, object, m_uid, PropertyCondition::Absence);
		if (!result)
			return false;
		conditions.append(result);
		return true;
	}
private:
	VM& m_vm;
	JSCell* m_owner;
	UniquedStringImpl* m_uid;
};
}

ObjectPropertyConditionSet generateConditionsForPropertyMissConcurrently(
    VM& vm, JSGlobalObject* globalObject, Structure* headStructure, UniquedStringImpl* uid)
{
    return generateConditions(
        vm, globalObject, headStructure, 0, MissConcurrentlyFunctor(vm, 0, uid));
}

namespace
{
class SetterMissConcurrentlyFunctor
{
public:
	SetterMissConcurrentlyFunctor(VM& vm, JSCell* owner, UniquedStringImpl* uid)
	: m_vm(vm)
	, m_owner(owner)
	, m_uid(uid)
	{
	}

	bool operator()(Vector<ObjectPropertyCondition>& conditions, JSObject* object) const
	{
        ObjectPropertyCondition result =
            generateCondition(m_vm, m_owner, object, m_uid, PropertyCondition::AbsenceOfSetter);
        if (!result)
            return false;
        conditions.append(result);
        return true;
	}
private:
	VM& m_vm;
	JSCell* m_owner;
	UniquedStringImpl* m_uid;
};
}

ObjectPropertyConditionSet generateConditionsForPropertySetterMissConcurrently(
    VM& vm, JSGlobalObject* globalObject, Structure* headStructure, UniquedStringImpl* uid)
{
    return generateConditions(
        vm, globalObject, headStructure, 0, SetterMissConcurrentlyFunctor(vm, 0, uid), Concurrent);
}

ObjectPropertyCondition generateConditionForSelfEquivalence(
    VM& vm, JSCell* owner, JSObject* object, UniquedStringImpl* uid)
{
    return generateCondition(vm, owner, object, uid, PropertyCondition::Equivalence);
}

} // namespace JSC

