/*

Base Application

*/

Ext.BLANK_IMAGE_URL = '/demo/static/lib/ext/resources/images/default/s.gif';

AtexDNA.app = function() {
    // Private Properties
    // Private Methods
    return {
        // Public Properties
        // Privileged Methods
        init: function() {
            YAHOO.log("INIT", "info", ".app");

            if (AtexDNA.config.DEBUG === true) {
                AtexDNA.debug.Logger.init();
            }

            var data = AtexDNA.data;
            var progress = "";

            AtexDNA.templates.init();
            AtexDNA.data.Controller.init();
            AtexDNA.ui.Map.init();
            AtexDNA.ui.SingleSlider.init();
            this.clearSearchEl = Ext.get('clear-search');
            this.clearSearchEl.on('click', this.onClearSearch, this);
            AtexDNA.ui.DualSlider.init();
            AtexDNA.ui.DrillDown.init();
            AtexDNA.ui.SearchResults.init();
            
            AtexDNA.ui.SingleSlider.reset();
            AtexDNA.ui.DualSlider.reset();
            AtexDNA.ui.SearchKeywords.reset();
            AtexDNA.ui.DrillDown.reset();
            AtexDNA.ui.Pagination.reset();
            
            AtexDNA.ui.SearchKeywords.init();

            var prg = Ext.get("search-progress");
            prg.hide();

            YAHOO.log("INIT", "info", ".app");
        },

        onClearSearch: function() { // FIXME. Has to be hit twice
            AtexDNA.ui.SingleSlider.reset();
            AtexDNA.ui.DrillDown.reset();
            //AtexDNA.ui.Pagination.reset();
            AtexDNA.ui.SearchKeywords.reset();
            AtexDNA.data.Controller.reset(); // This goes last and is the only one that refreshes the request
            AtexDNA.ui.DualSlider.reset();
            YAHOO.log("On Clear Search", "info", ".app");
        }
    };
} ();

/*

Configuration

*/

AtexDNA.config.LOCATION = function() {
    var w = window.location + '',
    s = w.split("/");
    s.pop();
    s.pop();
    return s.join("/") + "/";
}

var CONFIG = AtexDNA.config;

// Ext.lib.Ajax.setDefaultPostHeader(false);
Ext.lib.Ajax.defaultPostHeader = 'application/json';

/*

Request Queue

*/
AtexDNA.data.Queue = {
    requests: [],
    running: false,
    // The interval ID is held here. When the polling has stopped, it == false.
    target: false,
    // The data store to call "load" on for queued requests.
    dryRuns: 0,
    // Incremented every time the polling is run with no items in the queue.
    paused: false,

    /*

    Are there any pending requests? 

    */
    pending: function() {
        //////YAHOO.log("Queue: Has Items? " + AtexDNA.data.Queue.requests.length);
        return this.requests.length > 0;
    },

    /*

    Don't stop the polling, but hold off on processing
    requests because one is still running right now.

    */
    pause: function() {
        if (this.paused)
        {
            this.paused = false;
        } else {
            this.paused = true;
        }
    },

    /*

    If the queue is running and not paused, run gets the next
    item and sends a request to the "target" which is most cases
    is the DataStore (a.k.a. Connection object).

    Since run is invoked by setInterval() it is in the 
    scope of "window" instead of AtexDNA.data.Queue

    */
    run: function() {
        if (AtexDNA.data.Queue.paused) {
            return false;
        }

        if (AtexDNA.data.Queue.pending()) {
            //////YAHOO.log("Queue: " + AtexDNA.data.Queue.requests);
            var nextRequest = AtexDNA.data.Queue.next();
            AtexDNA.data.Queue.load(nextRequest);
        } else {
            if (AtexDNA.data.Queue.dryRuns < CONFIG.QUEUE_MAX_DRY_RUNS) {
                //////YAHOO.log("Queue: No Items", "warn");
                AtexDNA.data.Queue.dryRuns++;
            } else {
                ////YAHOO.log("No Items: Stopping", "info", "data.Queue");
                AtexDNA.data.Queue.dryRuns = 0;
                AtexDNA.data.Queue.stop();
            }

        }
    },

    duplicate: function(request) {
        var c = AtexDNA.data.Request();
        c.set(request);
        cs = c.getString();

        var existing = this.requests;
        for (var i = 0; i < existing.length; i++)
        {
            var x = AtexDNA.data.Request();
            x.set(existing[i]);
            xs = x.getString();
            if (xs == cs) {
                return true;
            }
        }
        return false;
    },

    /* 

    Add a request to the queue

    */

    add: function(request) {

        if (this.duplicate(request))
        {
            ////YAHOO.log("Duplicate Found. Not adding.", "warn", "data.Controller.add");
            return false;
        }

        this.requests.push(request);

        if (this.running === false) {
            ////YAHOO.log("Restarting", "info", "data.Controller");
            this.start();
        }
        ////YAHOO.log("Add: " + this.requests, "info", "data.Controller");
    },

    /*

    Tell the connection to load the request

    */
    load: function(request) {
        this.target.load({
            params: request
        })
    },

    /*

    Clear all of the requests in the queue.
    This usually happens when a request is plucked
    from the queue. All of the unprocessed requests 
    before it are blown away because they're likely
    not valid. We only want the most recent request.

    */
    clear: function() {
        delete this.requests;
        this.requests = [];
    },

    /*

    LIFO : push / pop, then clear the stack to invalidate the earlier requests.

    */
    next: function() {

        var nextItem = this.requests.pop();

        this.clear();
        return nextItem;
    },

    /*

    Reset the "dryruns" (meaning when "run" runs and the request queue is dry)
    and start the polling

    */

    start: function() {
        this.dryRuns = 0;
        this.running = setInterval(this.run, CONFIG.QUEUE_POLLING_INTERVAL);
    },

    /*

    Clear the interval if it exists ("this.running" holds the interval id)
    if its not running, "this.running" == false

    */
    stop: function() {
        if (this.running)
        {
            clearInterval(this.running);
            this.running = false;
        }
    }
};

/*

Data Store

*/

AtexDNA.data.Controller = function() {
    return {
        retryCount: 0,
        subscribers: [],
        currentPage: 1,
        request: AtexDNA.data.Request(),
        init: function() {
            this.dataReader = new Ext.data.aJsonReader({
                id: CONFIG.DATA_ID_KEY,
                root: CONFIG.DATA_ROOT_KEY,
                drilldown: CONFIG.DATA_DRILLDOWN_ROOT_KEY,
                currentPage: 'currentPage',
                totalPages: 'totalPages',
                fields: CONFIG.DATA_FIELDS
            });

            this.dataStore = new Ext.data.Store({
                url: CONFIG.DATA_URL,
                reader: this.dataReader // FIXME: Populate this with data on first page load
            });

            AtexDNA.data.Queue.target = this.dataStore;
            AtexDNA.data.Queue.start();

            this.dataStore.on({
                'loadexception': this.onLoadException,
                'load': this.onLoad,
                'beforeload': this.beforeLoad,
                scope: this
            });

            Ext.StoreMgr.add(CONFIG.DSN, this.dataStore);

            this.load(this.request.get());

            ////YAHOO.log("Add to StoreMgr: " + Ext.StoreMgr.lookup(CONFIG.DSN), "info", "data.Controller");
            //YAHOO.log("INIT", "info", "data.Controller")
        },

        /*

        load() -> *beforeload* error? *loadexception* END//->
        load() -> *beforeload* success? *load* END//->

        */

        load: function(request) {
            AtexDNA.data.Queue.add(request);
        },

        refresh: function() {
            this.currentPage = 1;
            this.request.setPage(1);
            AtexDNA.data.Queue.add(this.request.get());
            //YAHOO.log("Refresh: " + this.request.getString(), "info", "data.Controller")
        },

        reset: function() {
            this.currentPage = 1;
            this.request = AtexDNA.data.Request();
            AtexDNA.data.Queue.load(this.request.get());
        },

        retry: function(params) {
            if (this.retryCount < CONFIG.DATA_MAX_RETRY_ON_FAIL)
            {
                this.retryCount++;
                //YAHOO.log("Retrying #" + this.retryCount + " time.", "error", "data.Controller");
                var req = this.request;
                req.set(params);
                this.load(req.get());
            } else {
                //YAHOO.log("Max retries reached.", "error", "data.Controller")
                AtexDNA.data.Queue.stop();

                //alert("The server is not responding right now. Try again in a little while.")
            }
        },

        getCurrentPage: function() {
            YAHOO.log("Get Current Page: " + this.dataStore.resultMeta.currentPage, "info", "data.Controller.getCurrentPage");
            return parseInt(this.dataStore.resultMeta.currentPage);
        },

        getLastPage: function() {
            ////YAHOO.log(parseInt(this.dataStore.resultMeta.totalPages), "info", "data.Controller.getLastPage");
            return parseInt(this.dataStore.resultMeta.totalPages);
        },

        getNextPage: function() {
            var last = this.getLastPage();
            var cur = this.getCurrentPage();
            if (cur < last)
            {
                this.currentPage++;
                YAHOO.log("Get Next Page: " + this.currentPage, "info", "data.Controller");
                return this.currentPage;
            } else {
                return false;
            }
        },

        getPrevPage: function() {
            var cur = this.getCurrentPage();
            YAHOO.log("This is the last page", "error", "data.Controller");
            if (cur > 1)
            {
                this.currentPage--;
                return this.currentPage;
            }
            return false;
        },

        nextPage: function() {
            var next = this.getNextPage();
            if (next)
            {
                var req = this.request;
                req.setPage(next);
                this.load(req.get());
                YAHOO.log("Next Page: " + req.params.page, "info", "data.Controller");
            } else {
                ////YAHOO.log("This is the last page", "error", "data.Controller");
                }
        },

        prevPage: function() {
            var prev = this.getPrevPage();
            if (prev)
            {
                var req = this.request;
                req.setPage(prev);
                this.load(req.get());
                YAHOO.log("Previous Page: " + req.params.page, "info", "data.Controller");
            } else {
                YAHOO.log("This is the first page", "error", "data.Controller");
            }

        },
        
        getPage: function(page) {
            this.request.setPage(page);
            this.load(this.request.get());
        },

        subscribe: function(subscriber) {
            ////YAHOO.log("Adding subscriber: " + subscriber.name, "info", "data.Controller");
            this.subscribers.push(subscriber);
        },

        onLoadException: function(e, request) {
            // YAHOO.log("On Load Exception: " + e.loadResponse, "error", "data.Controller")
            /*
            for (var prop in e) {
                YAHOO.log("On Load Exception: " + prop.loadResponse, "error", "data.Controller")
            }
            */

            AtexDNA.data.Queue.pause();
            this.retry(request.params);

            //var prg = Ext.get("search-progress")
            //prg.hide();
        },

        beforeLoad: function() {
            ////YAHOO.log("Before Load", "time", "data.Controller")
            AtexDNA.data.Queue.pause();

            //var prg = Ext.get("search-progress")
            //prg.hide();
        },

        onLoad: function() {
            //YAHOO.log("On Load", "time", "data.Controller")
            this.retryCount = 0;
            AtexDNA.data.Queue.pause();
        }
    }
} ();

/*

aJSON Reader

*/

Ext.data.aJsonReader = function(meta, recordType) {
    meta = meta || {};
    Ext.data.aJsonReader.superclass.constructor.call(this, meta, recordType || meta.fields);
};

Ext.extend(Ext.data.aJsonReader, Ext.data.JsonReader, {
    read: function(response) {
        //////YAHOO.log("aJsonReader: read");
        //Ext.data.aJsonReader.superclass.read.call(this, response);
        var json = response.responseText;
        var o = eval("(" + json + ")");
        if (!o)
        {
            throw {
                message: "JsonReader.read: Json object not found"
            };
        }
        if (o.metaData)
        {
            delete this.ef;
            this.meta = o.metaData;
            this.recordType = Ext.data.Record.create(o.metaData.fields);
            this.onMetaChange(this.meta, this.recordType, o);
        }
        return this.readRecords(o);
    },

    onMetaChange: function(meta, recordType, o) {
        //////YAHOO.log("aJsonReader: onMetaChange");
        Ext.data.aJsonReader.superclass.onMetaChange.call(this, meta, recordType, o);
    },

    simpleAccess: function(obj, subsc) {
        //////YAHOO.log("aJsonReader: simpleAccess");
        //Ext.data.aJsonReader.superclass.simpleAccess.call(this, obj, subsc);
        return obj[subsc];
    },

    getJsonAccessor: function() {
        var re = /[\[\.]/;
        return function(expr) {
            try {
                return (re.test(expr)) ? new Function("obj", "return obj." + expr) : function(obj) { return obj[expr]; };
            } catch(e) {
                //////YAHOO.log("getJsonAccessor: Exception " + e, "error");
                }
            return Ext.emptyFn;
        };
    } (),

    readRecords: function(o) {
        //////YAHOO.log("aJsonReader: readRecords");
        //Ext.data.aJsonReader.superclass.readRecords.call(this, o);
        this.jsonData = o;
        var s = this.meta,
        Record = this.recordType,
        f = Record.prototype.fields,
        fi = f.items,
        fl = f.length;

        if (!this.ef) {

            if (s.totalProperty) {
                this.getTotal = this.getJsonAccessor(s.totalProperty);
            }
            if (s.successProperty) {
                this.getSuccess = this.getJsonAccessor(s.successProperty);
            }

            if (s.drilldown) {
                //YAHOO.log("aJsonReader.readRecords : drilldown: " + s.drilldown, "info", "data.Controller");
                this.getDrilldown = this.getJsonAccessor(s.drilldown);
            }

            this.getRoot = s.root ? this.getJsonAccessor(s.root) : function(p) {
                return p;
            };
            if (s.id) {
                var g = this.getJsonAccessor(s.id);
                this.getId = function(rec) {
                    var r = g(rec);
                    return (r === undefined || r === "") ? null: r;
                }
            } else {
                this.getId = function() {
                    return null;
                };
            }
            this.ef = [];
            for (var i = 0; i < fl; i++) {
                f = fi[i];
                var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping: f.name;
                this.ef[i] = this.getJsonAccessor(map);
            }
        }

        /* Get drilldown data from outside of the result root */
        var drilldown = {},
        meta = {};
        var jsonRoot = 'o.';

        var dd = eval(jsonRoot + s.drilldown);

        if (dd) {
            drilldown = dd;
        }

        /*
        for (var i = 0; i < fl; i++) {
            var j = fi[i]
            var dd = eval(jsonRoot+s.drilldown+'.'+j.name);
            if (dd)
            {
                drilldown[j.name] = dd;
            }
        }
        */

        if (s.totalPages)
        {
            YAHOO.log("Total Pages: " + eval(jsonRoot + s.totalPages));
        }

        /* Get metadata from outside of the result root */
        //if (s.firstFuzzy) { meta.firstFuzzy = eval(jsonRoot+s.firstFuzzy) }
        if (s.currentPage) {
            meta.currentPage = eval(jsonRoot + 'currentPage')
        }
        if (s.totalPages) {
            meta.totalPages = eval(jsonRoot + 'totalPages')
        }

        var root = this.getRoot(o),
        c = root.length,
        totalRecords = c,
        success = true;
        if (s.totalProperty) {
            var v = parseInt(this.getTotal(o), 10);
            if (!isNaN(v)) {
                totalRecords = v;
            }
        }
        if (s.successProperty) {
            var v = this.getSuccess(o);
            if (v === false || v === 'false') {
                success = false;
            }
        }
        var records = [];
        for (var i = 0; i < c; i++) {
            var n = root[i];
            var values = {};
            var id = this.getId(n);
            for (var j = 0; j < fl; j++) {
                f = fi[j];
                var v = this.ef[j](n);
                values[f.name] = f.convert((v !== undefined) ? v: f.defaultValue, n);
            }
            var record = new Record(values, id);
            record.json = n;
            records[i] = record;
        }

        return {
            success: success,
            records: records,
            drilldown: drilldown,
            resultMeta: meta
            //totalRecords : totalRecords
        };
    }
});

/*

DEBUG

If AtexDNA.config.DEBUG == true, this creates an instance of the Yahoo! LogReader.

*/

AtexDNA.debug.Logger = function() {
    return {
        init: function() {
            var logWindow = new YAHOO.widget.LogReader();
            // Attach the log reader to the body
            logWindow.hideSource("LogReader");
            logWindow.hideSource("global");
            YAHOO.log("INIT", "info", "AtexDNA.debug.Logger");
        }
    };
} ();

/*

DrillDown

*/

AtexDNA.ui.DrillDown = function() {
    return {
        name: "DRILLDOWN",
        init: function() {
            var ocv = this.onChangeValue;
            $('#refine-bedrooms :checkbox').each(function(i) {
                $(this).bind("click",
                function() {
                    AtexDNA.ui.DrillDown.onChangeValue(this.id, this.checked);
                });
            });
            $('#refine-bathrooms :checkbox').each(function(i) {
                $(this).bind("click",
                function() {
                    AtexDNA.ui.DrillDown.onChangeValue(this.id, this.checked);
                });
            });
            this.bind(CONFIG.DSN);
        },

        onChangeValue: function(id, state) {

            switch (id) {
            case "1br-cb":
            case "2br-cb":
            case "3br-cb":
            case "4br-cb":
            case "5br-cb":
            case "6br-cb":
            case "7br-cb":
            case "8br-cb":
                this.update("bedrooms");
                break;
            case "1ba-cb":
            case "2ba-cb":
            case "3ba-cb":
            case "4ba-cb":
            case "5ba-cb":
            case "6ba-cb":
            case "7ba-cb":
            case "8ba-cb":
                this.update("bathrooms");
                break;
            default:
                this.update("both");
                break;
            }
        },

        beforeLoad: function() {

            },

        onLoad: function() {
            var dd = this.store.drilldown,
            p = AtexDNA.util.Parenthify;
            for (var i = 0; i < dd.length; i++)
            {
                var dd_obj = dd[i],
                type = dd_obj['dimension'] == 'housing_bedrooms' ? 'bed': 'bath',
                el_id = 'refine-' + type + '-';
                for (var j = 0; j < dd_obj['counts'].length; j++)
                {
                    var dd_item = dd_obj['counts'][j],
                    dd_el = '';
                    switch (dd_item.id)
                    {
                    case '8':
                    case '7':
                    case '6':
                    case '5':
                    case '4':
                    case '3':
                    case '2':
                    case '1':
                        dd_el = el_id + dd_item.id;
                        break;
                    default:
                        break;
                    }
                    
                    if (dd_el)
                    {
                        dd_el = document.getElementById(dd_el);
                        try {
                            dd_el.innerHTML = p(dd_item.count);
                        } catch(e) {
                            YAHOO.log("DrillDown Error: " + el_id+dd_item.id, "warn", "ui.DrillDown");
                        }
                    }
                }
            }
        },

        onLoadError: function() {

            },

        bind: function(store) {
            store = Ext.StoreMgr.lookup(store);
            store.on("beforeload", this.beforeLoad, this);
            store.on("load", this.onLoad, this);
            store.on("loadexception", this.onLoadError, this);
            this.store = store;
        },

        reset: function() {
            $('#refine-bedrooms :checkbox').each(function(i) {
                this.checked = false;
            });
            $('#refine-bathrooms :checkbox').each(function(i) {
                this.checked = false;
            });
            //this.update("both");
        },
        update: function(which) {
            var ct = AtexDNA.data.Controller;
            var refresh = false;
            if (which == "bedrooms" || which == "both")
            {
                var upd_br = [];
                $('#refine-bedrooms :checkbox').each(function(i) {
                    if (this.checked) {
                        var num = this.id.charAt(0);
                        switch (num) {
                            case "1": case "2": case "3": case "4":
                            case "5": case "6": case "7":
                                upd_br.push(num);
                                break;
                            case "8":
                                upd_br.push("[8,)");
                                break;
                        }
                    }
                });
                var str_br = upd_br.join(" ")
                ct.request.setBedrooms(str_br);
                refresh = true;
            }
            if (which == "bathrooms" || which == "both")
            {
                var upd_ba = [];
                $('#refine-bathrooms :checkbox').each(function(i) {
                    if (this.checked) {
                        var num = this.id.charAt(0);
                        switch (num) {
                            case "1":
                                upd_ba.push("[1,2)");
                                break;
                            case "2":
                                upd_ba.push("[2,3)");
                                break;
                            case "3":
                                upd_ba.push("[3,4)");
                                break;
                            case "4":
                                upd_ba.push("[4,5)");
                                break;
                            case "5":
                                upd_ba.push("[5,6)");
                                break;
                            case "6":
                                upd_ba.push("[6,7)");
                                break;
                            case "7":
                                upd_ba.push("[7,8)");
                                break;
                            case "8":
                                upd_ba.push("[8,)");
                                break;
                        }
                    }
                });
                var str_ba = upd_ba.join(" ")
                ct.request.setBathrooms(str_ba);
                refresh = true;
            }
            if (refresh)
            {
                ct.refresh();
            }
        }
    };
} ();

/*

Dual Slider

*/

AtexDNA.ui.DualSlider = function() {
    return {
        init: function() {
            this.minTxt = document.getElementById("refine-price-min"); // FIXME
            this.maxTxt = document.getElementById("refine-price-max"); // FIXME
            
            this.currentValues = [0,0];

            this.slider = YAHOO.widget.Slider.getHorizDualSlider(CONFIG.PRICE_SLIDER_ELEMENT,
            CONFIG.PRICE_SLIDER_MIN_THUMB_ELEMENT,
            CONFIG.PRICE_SLIDER_MAX_THUMB_ELEMENT,
            CONFIG.PRICE_RANGE,
            CONFIG.PRICE_TICK_SIZE);

            YAHOO.lang.augmentObject(this.slider, {
                _status: 'OK',
                _highlight: YAHOO.util.Dom.get(CONFIG.PRICE_SLIDER_HIGHLIGHT_ELEMENT),

                getMin: function() {
                    return priceValues[this.minVal / CONFIG.PRICE_TICK_SIZE]
                },

                getMax: function() {
                    return priceValues[this.maxVal / CONFIG.PRICE_TICK_SIZE]
                },

                getStatus: function() {
                    return this._status;
                },

                updateHighlight: function() {
                    var s = this,
                    delta = s.maxVal - s.minVal,
                    ts = CONFIG.PRICE_TICK_SIZE,
                    Dom = YAHOO.util.Dom;
                    newStatus = 'OK';


                    if (s.getStatus() !== newStatus) {
                        // Update the highlight class if status is changed
                        Dom.replaceClass(s._highlight, s._status, newStatus);
                        s._status = newStatus;
                    }

                    if (s.activeSlider === s.minSlider) {
                        // If the min thumb moved, move the highlight's left edge
                        Dom.setStyle(s._highlight,
                        'left',
                        (s.minVal + ts) + 'px');
                    }

                    // Adjust the width of the highlight to match inner boundary
                    Dom.setStyle(s._highlight,
                    'width',
                    Math.max(delta - ts, 0) + 'px');

                    //YAHOO.log("Update Highlight: change", "info", ".ui.DualSlider");
                }
            },
            true);

            this.slider.subscribe('change',this.slider.updateHighlight);

            this.slider.subscribe('change',this.updateText,this,true);
            this.slider.subscribe('slideEnd',this.onChangeValue,this,true);
            this.slider.subscribe("onAvailable", this.onReady, this, true);
            //this.reset();
            YAHOO.log("INIT", "info", ".ui.DualSlider");

        },

        updateText: function() {
            var ts = CONFIG.PRICE_TICK_SIZE,
            s = this.slider,
            min = s.minVal / ts,
            max = s.maxVal / ts;

            this.minTxt.value = this.getRealValue(min);
            this.maxTxt.value = this.getRealValue(max);
        },
        onReady: function() {},
        resetWithoutRefresh: function () {
            this.slider.updateHighlight();
        },
        reset: function() {
            var min = 0,
            max = CONFIG.PRICE_VALUES.length - 1;

            this.minTxt.value = this.getRealValue(min);
            this.maxTxt.value = this.getRealValue(max);
            this.slider.setValues(min, max * CONFIG.PRICE_TICK_SIZE, true, false, false);

            YAHOO.log("Reset", "info", ".ui.DualSlider");
        },
        getRealValue: function(value) {
            var fc = AtexDNA.util.FormatCurrency,
            cpv = CONFIG.PRICE_VALUES;
            return fc(cpv[value]);
        },
        getRequestValue: function(value) {
            var cpv = CONFIG.PRICE_VALUES;
            return cpv[value];
        },
        onChangeValue: function(min, max) {
            try {
                if (this.currentValue === (min+max)) { return false; } 
            } catch (e) { this.currentValue = (min+max) }

            var ts = CONFIG.PRICE_TICK_SIZE,
            s = this.slider, min = s.minVal / ts,
            max = s.maxVal / ts, ct = AtexDNA.data.Controller,
            rv = this.getRequestValue;

            this.minTxt.value = this.getRealValue(min);
            this.maxTxt.value = this.getRealValue(max);

            ct.request.setPriceRange([rv(min), rv(max)]);
            ct.refresh();

            //s.updateHighlight();
            //YAHOO.log("On Change Value: slideEnd", "info", ".ui.DualSlider");
        }
    };
} ();

/*

Map

*/

AtexDNA.ui.Map = function() {
    return {
        name: 'MAP',
        markers: [],
        init: function() {
            this.bind(CONFIG.DSN);
            this.createMap();
        },

        onReady: function() {
            //YAHOO.log("On Ready", "info", ".ui.Map");
            //this.markerManager = new MarkerManager(this.map, {borderPadding: 20, trackMarkers:false})
            },

        createMap: function() {
            try {
                if (this.map.isLoaded()) return true;
            }
            catch(e) {
                this.map = new GMap2(document.getElementById(CONFIG.MAP_CONTAINER_ELEMENT));
                GEvent.addListener(this.map, "load", this.onReady);

                this.map.addControl(new GSmallMapControl());
                
                this.geocoder = new GClientGeocoder();

                var c = new GLatLng(CONFIG.DEFAULT_MAP_CENTER.lat, CONFIG.DEFAULT_MAP_CENTER.lon);
                this.map.setCenter(c, CONFIG.DEFAULT_ZOOM_LEVEL);
            }
        },
        
        geocodeZip: function (zip, callback) {
            this.geocoder.getLatLng(zip, callback);
        },

        highlightMarker: function(id) {
            var i,
            mrkrs = this.markers,
            ml = mrkrs.length;

            for (i = 0; i < ml; i++) {
                var m = mrkrs[i],
                sel = (m.recordId == id),
                mi = this.getMarkerImage(m.num, m.exact, sel),
                mel = this.getMarkerElement(m),
                z = sel ? '500': '0';
                // <-- Using 'auto' throws a "Type Mismatch" error on IE7.
                m.setImage(mi);
                mel.style['zIndex'] = z;
                //YAHOO.log("Marker: " + mel, "info", ".ui.Map");
                //GEvent.trigger(m, "dragstart");
            }
        },

        getMarkerElement: function(marker) {
            /*

            In Google Maps (exclusively) the individual markers 
            are DIVs with IDs following naming convention:

            'mtgt_unnamed_[ N + ( 10 * times the map has reloaded / cleared markers ) ]' 

            loads = 0
            n = 1

            (1 - 1) + (0 * 10)
            0 + 0 = 0
            mtgt_unnamed_0

            This is kind of a hack since that naming
            convention could change in future versions.

            */
            var p = "mtgt_unnamed_",
            n = marker.num;

            return document.getElementById(p + ((n - 1) + (this.loads * 10)));
        },

        resetHighlights: function() {
            this.highlightMarker(null);
        },

        onLoad: function() {

            if (this.map.isLoaded()) {
                YAHOO.log("On Load: " + this.map.getCenter(), "info", ".ui.Map");
                this.map.clearOverlays();
                delete this.markers;
                this.markers = [];
                this.bounds = new GLatLngBounds();

                this.store.each(this.addMarker, this);

                this.setCenterZoom(this.bounds);

                if (this.loads >= 0) {
                    this.loads++;
                    /* This ties into finding the Google Marker Element */
                } else {
                    this.loads = 0;
                }
            } else {
                //YAHOO.log("Map not ready", "error", ".ui.Map");
                }

        },

        getMarkerImage: function(num, exact, hlt) {
            var type = exact ? 'exact': 'fuzzy',
            hi = hlt ? '-hi': '';
            return "/demo/static/images/map-markers/" + type + hi + "-" + num + ".png";
        },

        getMarkerIcon: function(num, exact) {
            var i = new GIcon();
            i.shadow = "http://www.google.com/mapfiles/shadow50.png";
            i.iconSize = new GSize(20, 34);
            i.shadowSize = new GSize(37, 34);
            i.iconAnchor = new GPoint(9, 34);
            i.infoWindowAnchor = new GPoint(9, 2);
            i.infoShadowAnchor = new GPoint(18, 25);
            i.image = this.getMarkerImage(num, exact, false);
            return i;
        },

        createMarker: function(pos, id, exact, num) {
            var m = new GMarker(pos, {
                draggable: false,
                icon: this.getMarkerIcon(num, exact),
                /* title: title, */
                zIndexProcess: function(m, b) {
                    //YAHOO.log("Z", "warn", ".ui.Map");
                    }
            }
            ),
            mid = 'r_' + id + '_m';
            return m;
        },

        addMarker: function(record, i) {

            var ll = new GLatLng(record.get("latitude"), record.get("longitude")),
            em = record.get("exact"),
            id = record.get("id"),
            /* ad = record.get("address1"), */
            num = i + 1;
            //YAHOO.log("Add Marker: " + em, "info", ".ui.Map");
            this.bounds.extend(ll);
            var m = this.createMarker(ll, id, em, num);

            m.recordId = 'r_' + id;
            m.num = num;
            m.exact = em;

            this.markers.push(m);
            this.map.addOverlay(m);
        },

        onLoadError: function() {
            //YAHOO.log("On Load", "info", ".ui.Map");
            },

        beforeLoad: function() {
            //YAHOO.log("On Load", "info", ".ui.Map");
            },

        setCenterZoom: function(bounds) {
            var bzl = this.map.getBoundsZoomLevel(bounds);
            bc = bounds.getCenter();

            this.map.setCenter(bc, bzl);
        },

        bind: function(store) {
            store = Ext.StoreMgr.lookup(store);
            store.on("beforeload", this.beforeLoad, this);
            store.on("load", this.onLoad, this);
            store.on("loadexception", this.onLoadError, this);
            this.store = store;
        }
    }
} ();

AtexDNA.ui.SearchKeywords = {
    init: function () {
        this.keywordSearchEl = Ext.get('apply-keywords');
        this.keywordSearchEl.on('click', this.onApplyKeywords, this);
        
        var kt = document.getElementById('keywords-txt');
        kt.onkeypress = this.enter;
    },
    onApplyKeywords: function () {
        var ct = AtexDNA.data.Controller,
            txt = $('#keywords-txt');
            ct.request.setKeywords(txt.val());
            ct.refresh();
    },
    enter: function (e) {
        if (window.event) { // for IE
            var num = window.event.keyCode; 
        } else {
            if (e.which) {
                var num = e.which;
            }
        }
        if (num === 13) { AtexDNA.ui.SearchKeywords.onApplyKeywords(); };
    },
    reset: function () {
        var txt = $('#keywords-txt');
        txt.val('');
    }
}

/* Pagination */
AtexDNA.ui.Pagination =  {
    name: "PAGINAION",
    init: function() {
        this.bind(CONFIG.DSN);
        YAHOO.log("COMPONENT INIT", "info", "ui.PagingToolbar");
        this.paginator;		
		//this.reset();
    },
    
    reset: function () {
		this.pageSize = 10;
		this.displaySize = 10;
		this.currentPage = 0;
		this.edgeEntries = 0;
		this.totalPages = 10000;
        this.redraw();
    },
    
    redraw: function () {
        $('#search-results-pages').pagination(this.totalPages, {
    		items_per_page:this.pageSize,
    		link:'#',
    		callback:this.onClick,
    		num_display_entries:this.displaySize,
    		current_page:this.currentPage,
    		num_edge_entries:this.edgeEntries,
    		next_text:"&raquo;",
    		prev_text:"&laquo;"
    	});        
    },
    
    lock: function () {
        this.locked = true;
    },
    
    unlock: function () {
        this.locked = false;
    },

    onClick: function(page, paginator) {
        YAHOO.log("Click", "info", "ui.PagingToolbar");
        var pt = AtexDNA.ui.Pagination;
        if (page < 8) {
            pt.displaySize = 10;
        }
        if (page >= 11) {
            pt.displaySize = 9;
        }
        if (page > 45) {
            pt.displaySize = 8;
        }
        if (page > 75) {
            pt.displaySize = 7;
        } else {

        }
        //pt.currentPage = page;
        //pt.redraw();
        pt.paginator = paginator;
        AtexDNA.data.Controller.getPage(page+1); // HACK: WTF?!
    },

    beforeLoad: function() {
        //YAHOO.log("Before Load", "info", "ui.PagingToolbar");
    },

    onLoad: function() {
        this.currentPage = this.store.resultMeta.currentPage-1;
        this.totalPages = this.store.resultMeta.totalPages;
        this.redraw();

        //YAHOO.log("Total Pages: " + , "info", "ui.PagingToolbar");
        //YAHOO.log("On Load", "info", "ui.PagingToolbar");
    },

    onLoadError: function() {
        //YAHOO.log("On Load Error", "warn", "ui.PagingToolbar");
    },

    bind: function(store) {
        store = Ext.StoreMgr.lookup(store);
        store.on("beforeload", this.beforeLoad, this);
        store.on("load", this.onLoad, this);
        store.on("loadexception", this.onLoadError, this);
        this.store = store;
    }
};


/*

Search Results Display

*/



AtexDNA.ui.SearchResults = function() {
    return {
        name: "SEARCH_RESULTS",
        prepareData: function(data, i) {
            //YAHOO.log("Prepare Data: " + i, "info", "ui.SearchResults");
            data.num = i + 1;
            //data.price = AtexDNA.util.FormatCurrency(data.price);
            //data.thumbnail = AtexDNA.util.GetPropertyThumbnail(data.id);
            return data;
        },

        init: function() {
            this.dataView = new Ext.DataView({
                tpl: AtexDNA.templates.SearchResultRow,
                autoHeight: true,
                emptyText: '',
                deferEmptyText: true,
                el: Ext.get(CONFIG.SEARCH_RESULTS_ELEMENT),
                prepareData: this.prepareData,
                overCls: 'search-result-row-over',
                itemSelector: 'div.search-result-row'
            });

            //YAHOO.log("INIT", "info", "ui.SearchResults");
            AtexDNA.ui.Pagination.init();

            //AtexDNA.data.Controller.subscribe(this);
            this.dataView.setStore(AtexDNA.data.Controller.dataStore);

            this.dataView.on('refresh', this.onRefresh, this);

            var outerPane = Ext.get("main");
            outerPane.on('mouseover', this.resetSelected, this, {
                stopEvent: true
            });
        },

        resetSelected: function() {
            //YAHOO.log("Reset Selected", "info", "ui.SearchResults");
            },

        onRefresh: function(e) {
            //YAHOO.log("Refresh", "info", "ui.SearchResults");
            var n = this.dataView.getNodes(0),
            i;
            for (i = 0; i < n.length; i++) {
                this.decorate(n[i]);
            }
            ////YAHOO.log("Items: "+n.length, "info", ".ui.SearchResults" );
        },

        decorate: function(item) {
            ////YAHOO.log("Item: "+item.id, "info", ".ui.SearchResults" );
            item.expanded = false;

            var e = Ext.get(item.id);
            e.on('mouseover', this.onMouseOver, e);
            e.on('mouseout', this.onMouseOut, e);
            e.addClassOnOver('search-result-row-over');
        },

        onClickItem: function(item) {
            this.expanded = this.expanded ? false: true;
            var h = this.expanded ? 50: 100;
            var e = Ext.get(this.id);
            //e.setHeight(h);
            ////YAHOO.log("Item Click: "+this, "info", ".ui.SearchResults" );
        },

        onMouseOut: function() {
            AtexDNA.ui.Map.resetHighlights();
        },

        onMouseOver: function() {
            ////YAHOO.log("Mouse Over: " + this.currentlyOver, "info", ".ui.SearchResults");
            AtexDNA.ui.Map.highlightMarker(this.id);
        }
    };
} ();

/*

Single Slider

*/



AtexDNA.ui.SingleSlider = function() {
    return {
        init: function() {
            this.milesTxt = document.getElementById('distance-slider-converted-value'); // FIXME
            this.fromTxt = document.getElementById('distance-from'); // FIXME

            this.slider = YAHOO.widget.Slider.getHorizSlider(CONFIG.DISTANCE_SLIDER_BG_ELEMENT,
            CONFIG.DISTANCE_SLIDER_THUMB_ELEMENT,
            CONFIG.DISTANCE_TOP_CONSTRAINT,
            CONFIG.DISTANCE_BOTTOM_CONSTRAINT,
            CONFIG.DISTANCE_TICK_SIZE);

            this.slider.subscribe("slideEnd", this.onChangeValue, this, true);
            this.slider.subscribe("onAvailable", this.onReady, this, true);
            //this.reset();
            /*

            Hack 

            */
            this.fromTxt.onchange = function() {
                // this.value = '94013';
                AtexDNA.ui.Map.geocodeZip(this.value, AtexDNA.ui.SingleSlider.onGeocode);
            }

            YAHOO.log("INIT", "info", ".ui.SingleSlider");
        },

        onGeocode: function (a) {
            try {
                if (a) {
                    var ct = AtexDNA.data.Controller;
                    ct.request.setCoordinates([a.lat(), a.lng()]);
                    ct.refresh();
                } else {
                    alert('Invalid Zipcode');
                    AtexDNA.ui.SingleSlider.fromTxt.value = AtexDNA.config.DEFAULT_ZIP_CODE;                    
                }
            } catch (e) {
                alert('Invalid Zipcode');
                AtexDNA.ui.SingleSlider.fromTxt.value = AtexDNA.config.DEFAULT_ZIP_CODE;
            }
        },

        onReady: function() {


        },

        reset: function() {
            var dv = CONFIG.DISTANCE_DEFAULT_VALUE,
            ts = CONFIG.DISTANCE_TICK_SIZE;
            this.milesTxt.innerHTML = this.getRealValue(dv / ts);
            
            // $('#distance-from').removeAttr("disabled"); // Enable temporarily 
            this.fromTxt.value = CONFIG.DEFAULT_ZIP_CODE; // Set default
            // $('#distance-from').attr("disabled", true); // Disable
            this.slider.setValue(dv, true, true, true);
        },

        getRealValue: function(value) {
            var dsv = CONFIG.DISTANCE_VALUES;
            return dsv[value];
        },

        onChangeValue: function() {
            var value = this.slider.getValue() / CONFIG.DISTANCE_TICK_SIZE;
            this.milesTxt.innerHTML = this.getRealValue(value);

            ct = AtexDNA.data.Controller;
            ct.request.setDistance(this.getRealValue(value));
            ct.refresh();

            //YAHOO.log("Change: " +  value, "info", ".ui.SingleSlider");
        }
    };
} ();


/*

Utility Functions

*/

AtexDNA.util = {
    GetPropertyThumbnail: function(id) {
        /*

        Test : http://demo2.transparensee.com/uberdemo/greatchicago/assets/photo/thumb/4263840.png

        */
        return CONFIG.PHOTO_ROOT + CONFIG.THUMBNAIL_ROOT + id + CONFIG.PHOTO_EXTENSION;
    },
    Parenthify: function(value) {
        return ' (' + value + ') ';
    },
    TruncateHighlighting: function(value) {
        var hlt = value.match(/<span\s+class=(\'|")highlight(\'|")>/);
        if (hlt) {
            var index = hlt.index;
            if (value.length > index + 140 && index > 0) {
                var body = value.slice(index - 10, index + 130);
                return '...'+body+'...';
            } else {
                if (index === 0) {
                    return value.slice(0,130)+'...';
                }
                return value;
            }
        } else {
            if (value.length > 130) {
                return value.slice(0,130)+'...';
            }
        }
        return value;
    },
    FormatCurrency: function(value) {
        //YAHOO.log(value, "info", ".util.FormatCurrency");
        var conversion = Ext.util.Format.usMoney(value);
        return Ext.util.Format.substr(conversion, 0, (conversion.length - 3));
    }
}
