/*
 * Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 */
package jace.core;

import jace.core.RAMEvent.TYPE;

/**
 * A Ram Listener waits for a specific ram event, as specified by access type
 * (read/write/execute, etc) or a memory address, or range of addresses. The
 * subclass must define the address range (scope start/end) via the doConfig
 * method. Ram listeners are used all over the emulator, but especially in cheat
 * modules and the softswitch and I/O cards.
 *
 * @author Brendan Robert (BLuRry) brendan.robert@gmail.com 
 */
public abstract class RAMListener implements RAMEvent.RAMEventHandler {
    
    private RAMEvent.TYPE type;
    private RAMEvent.SCOPE scope;
    private RAMEvent.VALUE value;
    private int scopeStart;
    private int scopeEnd;
    private int valueStart;
    private int valueEnd;
    private int valueAmount;

    /**
     * Creates a new instance of RAMListener
     * @param t
     * @param s
     * @param v
     */
    public RAMListener(RAMEvent.TYPE t, RAMEvent.SCOPE s, RAMEvent.VALUE v) {
        setType(t);
        setScope(s);
        setValue(v);
        doConfig();
    }

    public RAMEvent.TYPE getType() {
        return type;
    }

    public final void setType(RAMEvent.TYPE type) {
        this.type = type;
    }

    public RAMEvent.SCOPE getScope() {
        return scope;
    }

    public final void setScope(RAMEvent.SCOPE scope) {
        this.scope = scope;
    }

    public RAMEvent.VALUE getValue() {
        return value;
    }

    public final void setValue(RAMEvent.VALUE value) {
        this.value = value;
    }

    public int getScopeStart() {
        return scopeStart;
    }

    public void setScopeStart(int scopeStart) {
        this.scopeStart = scopeStart;
    }

    public int getScopeEnd() {
        return scopeEnd;
    }

    public void setScopeEnd(int scopeEnd) {
        this.scopeEnd = scopeEnd;
    }

    public int getValueStart() {
        return valueStart;
    }

    public void setValueStart(int valueStart) {
        this.valueStart = valueStart;
    }

    public int getValueEnd() {
        return valueEnd;
    }

    public void setValueEnd(int valueEnd) {
        this.valueEnd = valueEnd;
    }

    public int getValueAmount() {
        return valueAmount;
    }

    public void setValueAmount(int valueAmount) {
        this.valueAmount = valueAmount;
    }

    public boolean isRelevant(RAMEvent e) {
        // Skip event if it's not the right type
        if (type != TYPE.ANY && e.getType() != TYPE.ANY) {
            if ((type != e.getType())) {
                if (type == TYPE.READ) {
                    if (!e.getType().isRead()) {
                        return false;
                    }
                } else {
                    return false;
                }
            }
        }
        // Skip event if it's not in the scope we care about
        if (scope != RAMEvent.SCOPE.ANY) {
            if (scope == RAMEvent.SCOPE.ADDRESS && e.getAddress() != scopeStart) {
                return false;
            } else if (scope == RAMEvent.SCOPE.RANGE && (e.getAddress() < scopeStart || e.getAddress() > scopeEnd)) {
                return false;
            }
        }

        // Skip event if the value modification is uninteresting
        if (value != RAMEvent.VALUE.ANY) {
            if (value == RAMEvent.VALUE.CHANGE_BY && e.getNewValue() - e.getOldValue() != valueAmount) {
                return false;
            } else if (value == RAMEvent.VALUE.EQUALS && e.getNewValue() != valueAmount) {
                return false;
            } else if (value == RAMEvent.VALUE.NOT_EQUALS && e.getNewValue() == valueAmount) {
                return false;
            } else if (value == RAMEvent.VALUE.RANGE && (e.getNewValue() < valueStart || e.getNewValue() > valueEnd)) {
                return false;
            }
        }

        // Ok, so we've filtered out the uninteresting stuff
        // If we've made it this far then the event is valid.
        return true;
    }
    
    @Override
    public void handleEvent(RAMEvent e) {
        if (isRelevant(e)) {
            doEvent(e);
        }
    }

    abstract protected void doConfig();

    abstract protected void doEvent(RAMEvent e);
}
