﻿//<![CDATA[

/**************************
    Module constants and variables
**************************/

// enumerated constants & static objects
var PERSPECTIVE = { Expert: 0, HouseWizard: 5};
var MODE = { Select: 0, Modifying: 1, Creating: 2, Created: 3 };
var FEATURESTATE = { Unmodified: 0, Modifying: 1, Modified: 2, Deleted: 3, Saving: 4, Saved: 5, Creating: 10, Created: 11, Disabled: 99 };

// module vars
var m_EditActive = false;
var m_EditActiveFull = false;
var m_Mode = MODE.Select;
var m_SubMode = "";
var m_ActiveLIDWorthyFeatureType;
var m_UserSelectedTexture;
var m_Events = new OpenLayers.Events();

var m_CreateButtons = [];
var m_tabvwPerspective, m_tabvwTexture, m_tabvwPropertyType, m_tabvwQuickShape;
var m_tabvwTextureMode = "";
var m_mnubtnFeatureLayer, m_chkbxFillMode, m_sldrFeatureTextureShading;
var m_ctrlKeyboardEditor, m_ctrlMouseClickEditor;
var m_ctlModify, m_ctlAddLine, m_ctlAddPoly, m_ctlAddPoint;
var m_styleSelected, m_styleInvisible, m_styleModified, m_styleDeleted;

/**************************
    Map editor initialization
**************************/

function initializeMapEditor()
{
	var style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style['default']);
    style.strokeWidth = 2;
    style.strokeColor = "#FFFF22";
    style.fillColor = "#FFFF22";
    style.fillOpacity = 0;
    style.pointRadius = 3;
    m_styleSelected = style;

    style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style['default']);
    style.strokeWidth = 0;
    style.strokeColor = "#FFFFFF";
    style.fillOpacity = 0;
    m_styleInvisible = style;

    style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style['default']);
    style.strokeWidth = 2;
    style.strokeColor = "#EF0000";
    style.fillOpacity = 0;
    style.pointRadius = 3;
    m_styleModified = style;

    style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style['default']);
    style.strokeWidth = 4;
    style.strokeColor = "#EF0000";
    style.strokeOpacity = 0.4;
    style.fillOpacity = 0;
    m_styleDeleted = style;

    var myStyles = new OpenLayers.StyleMap({
        "default": m_styleSelected
        , "select": m_styleSelected
        , "invisible": m_styleInvisible
        , "modified": m_styleModified
        , "deleted": m_styleDeleted
    });

    m_Events.addEventType("cancel");
    m_Events.on({ "cancel": onCancelMode });
    m_Events.addEventType("save");
    m_Events.on({ "save": onSaveChanges });

    m_layerEdit = new OpenLayers.Layer.WFS.JIT("roads", EP_WFST
		,{
			typename: "wa:roads,wa:feature_line,wa:feature_poly"
		    , layerName: "roads,feature_line,feature_poly"
			//, maxfeatures: 10
            , geometryCol: "the_geom"
            , gkt: m_GKT
        }
		,{
		    layerName: "roads,feature_line,feature_poly"
            , featureNS: "http://zoomatlas.com/NS"
            , extractAttributes: true
            , onBeforeFeaturesAdded: onFilterSelection
            , onSuccess: onSaveSuccess
            , onFailure: onSaveFailure
            //, style: m_styleSelected
            , styleMap: myStyles
		}
	);
	
	// register handler to display selected feature info
    m_layerEdit.events.on(
        {
            "featuresadded": onLoadEndEditLayer
            , "featuremodified": onFeatureModified
            , "featureunselected": onFeatureUnselected
        }
    );

    m_layerEdit.writer = new OpenLayers.Format.WFS(
        {
            layerName:''
            , message: 'default message'
        }
        , m_layerEdit
    );

	m_Map.addLayer(m_layerEdit);

	m_ctlAddLine = new OpenLayers.Control.DrawFeature(m_layerEdit, OpenLayers.Handler.Path, {handlerOptions: {'freehand': false}});
	m_ctlAddLine.featureAdded = onCreateLineFeature;
    m_ctlAddLine.setMap(m_Map);

	m_ctlAddPoly = new OpenLayers.Control.DrawFeature(m_layerEdit, OpenLayers.Handler.Polygon, {handlerOptions: {'freehand': false}});
	m_ctlAddPoly.featureAdded = onCreatePolyFeature;
    m_ctlAddPoly.setMap(m_Map);

    m_ctlAddPoint = new OpenLayers.Control.DrawFeature(m_layerEdit, OpenLayers.Handler.Point, { handlerOptions: { 'freehand': false} });
	m_ctlAddPoint.featureAdded = onCreatePointFeature;
    m_ctlAddPoint.setMap(m_Map);
    
    m_Map.events.register("zoomend", this, this.onZoom);

    m_ctlModify = new OpenLayers.Control.ZoomAtlasModifyFeature(m_layerEdit,
	    {
	        clickout: true
	        , toggle: false
	        , mode: OpenLayers.Control.ModifyFeature.DRAG | OpenLayers.Control.ModifyFeature.RESIZE | OpenLayers.Control.ModifyFeature.ROTATE | OpenLayers.Control.ModifyFeature.RESHAPE
	    }
	);
    m_ctlModify.setMap(m_Map);
    m_ctlModify.activate();

    m_ctrlMouseClickEditor = new OpenLayers.Control();
    OpenLayers.Util.extend(m_ctrlMouseClickEditor,
            {
                draw: function()
                {
                    this.click = new OpenLayers.Handler.Click(this,
                        {
                            "click": onMapClickEditor
                        }
                    );
                    this.click.keyMask = [OpenLayers.Handler.MOD_CTRL, OpenLayers.Handler.MOD_ALT, OpenLayers.Handler.MOD_META];
                    this.click.activate();
                }
            }
        );
    m_Map.addControl(m_ctrlMouseClickEditor);

    // our custom key handler to indicate selection helper state
    m_ctrlKeyboardEditor = new OpenLayers.Control();

    m_ctrlKeyboardEditor = OpenLayers.Util.extend(m_ctrlKeyboardEditor,
        {
            draw: function ()
            {
                this.keyboard = new OpenLayers.Handler.Keyboard(this, {'keydown': this.onKeyDown, 'keyup': this.onKeyUp});
                this.keyboard.activate();
            },

            onKeyDown: function (e)
            {
                switch (e.keyCode)
                {
                    case 27:
                        // ESC: cancel editing or create mode & unselect features
                        onCancel();
                        return;
                        break;
                    default:
                        return;
                        break;
                }
            },

            onKeyUp: function (e)
            {
                switch (e.keyCode)
                {
                    case 13:
                        // ENTER: save changes
                        onSave();
                        return;
                        break;
                    case 16:
                        // SHIFT: toggle road endpoint behavior
                        onToggleRoadEndpointGlue();
                        return;
                        break;
                    case 46:
                        // DEL: feature or vertex delete
                        onDelete(e.ctrlKey);
                        return;
                        break;
                    case 67:
                        if (e.ctrlKey) onDuplicate();
                        return;
                        break;
                    default:
                        return;
                        break;
                }
            }
        }
    );
    m_Map.addControl(m_ctrlKeyboardEditor);

    m_tabvwPerspective = new YAHOO.widget.TabView("perspective");
    m_tabvwPerspective.getTab(0).ACTIVE_TITLE = "Create and edit properties, shapes, and other map objects";

    m_tabvwTexture = new YAHOO.widget.TabView("texturegadget");
    m_tabvwTexture.addListener('activeIndexChange', onTabClickTexture);
    showTexturePalette("#Cement");

    var ddtarget = new YAHOO.util.DDTarget("map");

    m_tabvwPropertyType = new YAHOO.widget.TabView("propertytypegadget");
    m_tabvwPropertyType.addListener('activeIndexChange', onTabClickPropertyType);
    m_tabvwQuickShape = new YAHOO.widget.TabView("quickshapegadget");
    m_tabvwQuickShape.addListener('activeIndexChange', onTabClickQuickShape);
    initFeatureTypeControls();
    //getTabForFeatureTypeMasterCategory("#Campus").tab.ACTIVE_TITLE = "Ctrl-click to select an existing campus (Alt-click for Mac users)";
    showFeatureTypePalette("#Home");

    var btnPoly = new YAHOO.widget.Button("btnPoly", { type: "checkbox", checked: false });
    btnPoly.addListener("beforeCheckedChange", onClickCreatePoly);
    btnPoly.CHECKBOX_CHECKED_TITLE = "Shape create mode active - unpress or ESC key to cancel";
    var btnLine = new YAHOO.widget.Button("btnLine", { type: "checkbox", checked: false });
    btnLine.addListener("beforeCheckedChange", onClickCreateLine);
    btnLine.CHECKBOX_CHECKED_TITLE = "Line create mode active - unpress or ESC key to cancel";
    m_CreateButtons = [{ btn: btnPoly, type: "poly", title: 'Draw Shape', titledisable: 'Zoom in closer to draw a shape' }
        , { btn: btnLine, type: "line", title: 'Draw Line', titledisable: 'Zoom in closer to draw a line'}];

    m_mnubtnFeatureLayer = new YAHOO.widget.Button("mnubtnFeatureLayer", { type: "menu", menu: "mnuFeatureLayer" });
    m_mnubtnFeatureLayer.getMenu().subscribe("click", onMenuClickFeatureLayer);

    m_modFeatureLineWidth = new YAHOO.widget.Module("modFeatureLineWidth");
    m_modFeatureLineWidth.render();

    m_chkbxFillMode = new YAHOO.widget.Button("chkbxFillMode", { type: "checkbox", checked: false });
    m_chkbxFillMode.addListener("checkedChange", onClickTextureFillMode);
    m_chkbxFillMode.CHECKBOX_DEFAULT_TITLE = "Filled - click to change to border only";
    m_chkbxFillMode.CHECKBOX_CHECKED_TITLE = "Border only - click to change to filled";

    m_modFeatureFillMode = new YAHOO.widget.Module("modFeatureFillMode", { visible: false });
    m_modFeatureFillMode.render();
    
    m_sldrFeatureTextureShading = YAHOO.widget.Slider.getHorizSlider("slider-bg", "slider-thumb", 39, 39, 6);
    m_sldrFeatureTextureShading.setValue(0, true);
    m_sldrFeatureTextureShading.subscribe("change", function(newOffset)
    {
        var shadingvalue = (newOffset / 6) * 15;
        var shadinglabel = (0 == shadingvalue) ? "none" : ((shadingvalue > 0 ? "+" : "") + shadingvalue.toString() + "%");
        var oSliderEl = Dom.get("slider-bg");
        oSliderEl.title = "Shading = " + shadinglabel;
        var elcurrentshading = Dom.get("lblCurrentShading");
        elcurrentshading.innerHTML = shadinglabel;
        onChangeFeatureTextureShading(shadingvalue);
    });

    var args = OpenLayers.Util.getParameters();
//    if (args.fbuid)
//    {
//        Dom.get("btnReturn").innerHTML = "Return to FreeFall";
//    }

    //m_Layout.getUnitByPosition("right").on("resize", onResizeTab);

    // start off in property view and select mode
    setMode(MODE.Select);
    
}

function onResizeTab(e)
{
    var lu = m_Layout.getUnitByPosition("right");
    var height = lu.get("height");
    height -= 35;
    Dom.setStyle("edittabcontents", "height", height);
    return false;
}

function g_enableMapEditMode(enable, inview)
{
    m_EditActiveFull = (enable && !inview);
    m_EditActive = enable;

    if (enable)
    {
        if (!inview) endViewEdit();
        closePopup();
        m_ctrlMouseClickEditor.activate();
        m_ctrlKeyboardEditor.activate();
        Dom.setStyle("gnomebuttons", "visibility", "visible");
        if (!inview)
        {
            m_EventsSearchMap.onsearchresults.subscribe(onSearchResultsEdit);

            if (g_SelectedLid.length > 0)
            {
                // there is a selected lid, let's sync the editor
                m_layerEdit.loadFeaturesFromLID(g_SelectedLid, { typename: 'feature_poly', sn: Math.round(Math.random() * 1000000) });
            }
        }
    }
    else
    {
        cancelMode(true, true);
        m_ctrlMouseClickEditor.deactivate();
        m_ctrlKeyboardEditor.deactivate();
        Dom.setStyle("gnomebuttons", "visibility", "hidden");

        m_EventsSearchMap.onsearchresults.unsubscribe(onSearchResultsEdit);
    }
}

/**************************
    Feature selection event handlers for JIT vector layer
**************************/
var EDITSTATE = { Enabled: 0, NoMouse: 1, NoMove: 2, NoSize: 4, NoShape: 8, NoRotate: 16, NoModify: 32, NoCreate: 64 };
var m_EditState = EDITSTATE.Enabled;
var m_bRestoreModifyCtl = false;
var m_bRestoreMouseClickCtl = false;

function onZoom()
{
    if (m_EditActive)
    {
        if (m_Map.zoom >= 10)
        {
            enableEdit();
        }
        else
        {
            disableEdit(EDITSTATE.NoCreate | EDITSTATE.NoMouse | EDITSTATE.NoModify);
        }
    }
}

function enableEdit()
{
    if (EDITSTATE.Enabled != m_EditState)
    {
        // enable editing
        if (m_EditState & EDITSTATE.NoMove)
        {
            m_ctlModify.mode |= OpenLayers.Control.ModifyFeature.DRAG;
        }
        if (m_EditState & EDITSTATE.NoSize)
        {
            m_ctlModify.mode |= OpenLayers.Control.ModifyFeature.RESIZE;
        }
        if (m_EditState & EDITSTATE.NoShape)
        {
            m_ctlModify.mode |= OpenLayers.Control.ModifyFeature.RESHAPE;
        }
        if (m_EditState & EDITSTATE.NoRotate)
        {
            m_ctlModify.mode |= OpenLayers.Control.ModifyFeature.ROTATE;
        }
        if (m_EditState & EDITSTATE.NoModify)
        {
            m_ctlModify.activate();
        }
        if (m_EditState & EDITSTATE.NoMouse)
        {
            m_ctrlMouseClickEditor.activate();
        }
        if (m_EditState & EDITSTATE.NoCreate)
        {
            setCreateButtonsDisabled(false);
        }
        indicateFeatureState(); // make sure any message is removed

        m_EditState = EDITSTATE.Enabled;
    }
}

function disableEdit(mask)
{
    if (mask & EDITSTATE.NoCreate)
    {
        if (m_ctlAddLine.active || m_ctlAddPoint.active || m_ctlAddPoly.active)
        {
            cancelMode(true);
        }
        setCreateButtonsDisabled(true);
    }
    if (mask & EDITSTATE.NoMouse)
    {
        if (m_ctrlMouseClickEditor.active)
        {
            m_ctrlMouseClickEditor.deactivate();
        }
    }
    if (mask & EDITSTATE.NoModify)
    {
        if (m_ctlModify.active)
        {
            m_ctlModify.selectControl.unselectAll();
            m_ctlModify.deactivate();
        }
    }
    if (mask & EDITSTATE.NoMove)
    {
        m_ctlModify.mode &= ~OpenLayers.Control.ModifyFeature.DRAG;
    }
    if (mask & EDITSTATE.NoSize)
    {
        m_ctlModify.mode &= ~OpenLayers.Control.ModifyFeature.RESIZE;
    }
    if (mask & EDITSTATE.NoShape)
    {
        m_ctlModify.mode &= ~OpenLayers.Control.ModifyFeature.RESHAPE;
    }
    if (mask & EDITSTATE.NoRotate)
    {
        m_ctlModify.mode &= ~OpenLayers.Control.ModifyFeature.ROTATE;
    }
    m_EditState |= mask;
}

function onMapClickEditor(e)
{
    if (m_ctrlMouseClickEditor.active)
    {
        loadFeatureAt(e, this.click.checkModifiers(e), false);
    }
    else if (m_EditActive && m_Map.zoom < 12)
    {
        indicateFeatureState(FEATURESTATE.Disabled);
    }
    return false;
}

function loadFeatureAt(e, ctrldown)
{
    setStatusBar("Selecting...");
    cursor_wait();
         
    // we vary the pickslop depending on resolution
    var pickslop = m_Map.getResolution() * 3;

    var andfilter;
    if (ctrldown)
    {
        andfilter =
            "<And>" +
                "<PropertyIsGreaterThanOrEqualTo>" +
                    "<PropertyName>feature_type</PropertyName>" +
                    "<Literal>64</Literal>" +
                "</PropertyIsGreaterThanOrEqualTo>" +
                "<PropertyIsLessThan>" +
                    "<PropertyName>feature_type</PropertyName>" +
                    "<Literal>160</Literal>" +
                "</PropertyIsLessThan>" +
            "</And>";
    }
    else
    {
        andfilter =
            "<Or>" +
                "<PropertyIsLessThan>" +
                    "<PropertyName>feature_type</PropertyName>" +
                    "<Literal>0</Literal>" +
                "</PropertyIsLessThan>" +
                "<PropertyIsBetween>" +
                    "<PropertyName>feature_type</PropertyName>" +
                    "<LowerBoundary>" +
                        "<Literal>160</Literal>" +
                    "</LowerBoundary>" +
                    "<UpperBoundary>" +
                        "<Literal>256</Literal>" +
                    "</UpperBoundary>" +
                "</PropertyIsBetween>" +
                "<PropertyIsBetween>" +
                    "<PropertyName>feature_type</PropertyName>" +
                    "<LowerBoundary>" +
                        "<Literal>300</Literal>" +
                    "</LowerBoundary>" +
                    "<UpperBoundary>" +
                        "<Literal>32512</Literal>" +
                    "</UpperBoundary>" +
                "</PropertyIsBetween>" +
                "<PropertyIsBetween>" +
                    "<PropertyName>feature_type</PropertyName>" +
                    "<LowerBoundary>" +
                        "<Literal>32600</Literal>" +
                    "</LowerBoundary>" +
                    "<UpperBoundary>" +
                        "<Literal>131072</Literal>" +
                    "</UpperBoundary>" +
                "</PropertyIsBetween>" +
                "<PropertyIsBetween>" +
                    "<PropertyName>feature_type</PropertyName>" +
                    "<LowerBoundary>" +
                        "<Literal>132000</Literal>" +
                    "</LowerBoundary>" +
                    "<UpperBoundary>" +
                        "<Literal>8323072</Literal>" +
                    "</UpperBoundary>" +
                "</PropertyIsBetween>" +
            "</Or>";
    }
    
    m_layerEdit.loadFeaturesFromEvent(e, pickslop, null, andfilter);
}

function onLoadEndEditLayer(e)
{
    OpenLayers.Console.log("za:onLoadEndEditLayer");
    var feature = (e.features.length > 0) ? e.features[0] : null;

    // update our global var shared with other components (wiki tab, etc.)
    g_SelectedLid = getLidFromAttributes(feature ? feature.attributes : null);
    // set the hash for link
    document.location.hash = g_SelectedLid.length > 0 ? g_SelectedLid : "#";

    if (feature) delete feature.attributes["param"]; // we clear out any old sys params

    // JIT edit layer has loaded, update properties panel and mode to show selected feature (if any)
    cursor_clear();
    setStatusBar("");

    if (m_ctrlMouseClickEditor.active)
        setMode(MODE.Select, null, feature);

    return true;
}

function getPickPriority(feature)
{
    var featuretype = getTypeCodeFromFeature(feature);
    if (featuretype < 0)
    {
        return 10; // roads are picked highest
    }
    else if (featuretype >= 65536)
    {
        return 8; // then lines
    }
    else if (featuretype >= 256)
    {
        return 4; // then poly details
    }
    else if (featuretype >= 64)
    {
        return 2; // then properties
    }
    return 0;
}

function onFilterSelection(features)
{
    if (features.length > 1)
    {
        var filteredfeatures = [];

        // look for topmost features
        var highpickpriority = 0;
        var highpickindex = 0;
        for (var i = 0; i < features.length; i++)
        {
            var pickpriority = getPickPriority(features[i]);
            if (pickpriority > highpickpriority)
            {
                highpickpriority = pickpriority;
                highpickindex = i;
            }
        }

        if (highpickpriority < 8) // a prop or detail, some more work to do
        {
            // now we have the highest pick priority bucket, compare layer and gid
            for (i = 0; i < features.length; i++)
            {
                var highpicklayer = parseInt(features[highpickindex].attributes.layer);
                var layer = parseInt(features[i].attributes.layer);
                var highpickfid = features[highpickindex].fid;
                highpickfid = parseInt(highpickfid.slice(highpickfid.indexOf(".") + 1));
                var fid = features[i].fid;
                fid = parseInt(fid.slice(fid.indexOf(".") + 1));
                if (getPickPriority(features[i]) == highpickpriority)
                {
                    if ((layer > highpicklayer) || (layer == highpicklayer && fid > highpickfid))
                    {
                        highpickindex = i;
                    }
                }
            }
        }
        // else it's a road or line, good enough, were done
        filteredfeatures.push(features[highpickindex]);
        return filteredfeatures;
    }
    return features;
}

/**************************
Feature modification event handlers
**************************/

function g_onActivateEditSize()
{
    if (g_SelectedLid.length)
    {
        if (!m_EditActiveFull)
            m_layerEdit.events.register("featuresadded", { state: (EDITSTATE.NoCreate | EDITSTATE.NoMove | EDITSTATE.NoSize | EDITSTATE.NoRotate) }, onLoadEndEditLayerViewEdit);
        cursor_wait();
        m_layerEdit.loadFeaturesFromLID(g_SelectedLid, { typename: 'feature_poly', sn: Math.round(Math.random() * 1000000) });
    }
}

function g_onActivateEditMove()
{
    if (g_SelectedLid.length)
    {
        if (!m_EditActiveFull)
            m_layerEdit.events.register("featuresadded", { state: (EDITSTATE.NoCreate | EDITSTATE.NoSize | EDITSTATE.NoShape | EDITSTATE.NoRotate) }, onLoadEndEditLayerViewEdit);
        cursor_wait();
        m_layerEdit.loadFeaturesFromLID(g_SelectedLid, { typename: 'feature_poly', sn: Math.round(Math.random() * 1000000) });
    }
}

function onLoadEndEditLayerViewEdit(e)
{
    // it's a one-shot, so unhook our event handler
    m_layerEdit.events.unregister("featuresadded", this, onLoadEndEditLayerViewEdit);

    var feature = (e.features.length > 0) ? e.features[0] : null;
    if (feature)
    {
        g_enableMapEditMode(true, true); // enable edit for 'in view' editing

        // we don't call the register method on the click handler because that does a
        // registerPriority and we don't want to have priority over drag and other events
        m_Map.events.register("click", m_ctrlMouseClickEditor.click, onViewEditClickout);

        m_Events.registerPriority("cancel", null, onViewEditCancel);
        m_Events.registerPriority("save", null, onViewEditSave);
        // disable all but resize and rotate
        disableEdit(this.state);
        setMode(MODE.Select, null, feature);
    }
    cursor_clear();
    return true;
}

function onViewEditSave(e)
{
    viewEditCancel(true);
    return false;
}

function onViewEditClickout(e)
{
    return viewEditCancel(false);
}

function onViewEditCancel(e)
{
    viewEditCancel();
    return false;
}

function viewEditCancel(autosave, prompt)
{
    var retval = true;

    endViewEdit();

    if (m_ctlModify.feature && m_ctlModify.feature.state == OpenLayers.State.UPDATE)
    {
        if (autosave || (prompt && confirm("Save changes?")))
        {
            saveChanges(onViewEditSaveSuccess);
            cursor_wait();
        }

        retval = false;
    }
    g_enableMapEditMode(false);
    g_enableMapViewMode(true);

    return retval;
}

function endViewEdit()
{
    // unhook our event handlers
    m_Map.events.unregister("click", m_ctrlMouseClickEditor.click, onViewEditClickout);
    m_Events.unregister("cancel", null, onViewEditCancel);
    m_Events.unregister("save", null, onViewEditSave);

    enableEdit();
}

function onViewEditSaveSuccess()
{
    m_layerEdit.destroyFeatures();

    // tile regeneration should happen via geoserver
    var bounds = m_Map.getExtent();
    RedrawMap(bounds.left, bounds.bottom, bounds.right, bounds.top, true);

    // call infobox (in included module) to refresh points
    refreshPoints();
}

function onFeatureModified(e)
{
    OpenLayers.Console.log("za:onFeatureModified");
    
    if (OpenLayers.State.INSERT != e.feature.state)
    {
        // we mark it modified (unless it's a new feature)
        e.feature.state = OpenLayers.State.UPDATE;
    }
    
    e.feature.style = m_styleModified;
    e.feature.renderIntent = "modified";
    e.feature.layer.drawFeature(e.feature);
    m_ctlModify.resetVertices();
    
    setMode(MODE.Select, null, e.feature);
}

function onFeatureUnselected(e)
{
    OpenLayers.Console.log("za:onFeatureUnselected");
    if (MODE.Created == getMode())
    {
        // new object unselected, update prop panel
        setMode(MODE.Created);
    }
}

function onToggleRoadEndpointGlue()
{
    if (m_layerEdit.selectedFeatures.length == 0)
    {
        return; // NOP
    }
    
    var activefeature = getActiveFeature(true);
    var type = getTypeCodeFromFeature(activefeature);
    var isroad = (type < 0);

    if (isroad)
    {
        var noendpointglue = getSysParam(activefeature, "epng");
        if (noendpointglue)
        {
            setSysParam(activefeature, "epng", null); // delete sysparam
        }
        else
        {
            setSysParam(activefeature, "epng", 1, { 'epng' : 1 });
        }
        
        m_ctlModify.resetVertices();
    }
}

function setSysParam(feature, name, value, obj)
{
    var format = new OpenLayers.Format.JSON();
    var sysparams = format.read(feature.attributes.param);

    if (!sysparams || sysparams[name] == undefined)
    {
        sysparams = OpenLayers.Util.extend(obj, sysparams);
    }
    else if (value) // update existing param
    {
        sysparams[name] = value;
    }
    else // delete param
    {
        delete sysparams[name];
    }
    sysparams = format.write(sysparams);
    assignAttribute(feature, "param", sysparams, { 'param': sysparams });
}

function getSysParam(feature, name)
{
    var format = new OpenLayers.Format.JSON();
    var sysparams = format.read(feature.attributes.param);

    if (!sysparams || sysparams[name] == undefined)
    {
        return null;
    }
    return sysparams[name];
}

function onDuplicate()
{
    //if (cancelMode(true))
    {
        if (!m_layerEdit.features.length)
        {
            return; // NOP
        }
        var original = getActiveFeature(true);
        var featuretype = getTypeCodeFromFeature(original);
        var copy = new OpenLayers.Feature.Vector(original.geometry);
        copy.state = OpenLayers.State.INSERT;
        
        var delta = getPointFromPixel(new OpenLayers.Pixel(15, -15));
        copy.geometry.move(delta.x, delta.y);

        m_layerEdit.addFeatures([copy]);

        if (isRoad(featuretype))
        {
            copy.attributes = OpenLayers.Util.extend({
                'feature_type': featuretype
                , 'layer': original.attributes.layer
                , 'characteristics': original.attributes.characteristics
                },
                copy.attributes);
        }
        else
        {
            if (isLine(featuretype))
            {
                copy.attributes = OpenLayers.Util.extend({
                    'feature_type': featuretype
                    , 'layer': original.attributes.layer
                    , 'texture': original.attributes.texture
                    , 'shading': original.attributes.shading
                    , 'width': original.attributes.width
                    },
                    copy.attributes);
            }
            else
            {
                copy.attributes = OpenLayers.Util.extend({
                    'feature_type': featuretype
                    , 'layer': original.attributes.layer
                    , 'texture': original.attributes.texture
                    , 'shading': original.attributes.shading
                    , 'fill_mode': original.attributes.fill_mode
                    },
                    copy.attributes);
            }
        }

        copy.renderIntent = "modified";
        copy.style = m_styleModified;
        copy.layer.drawFeature(copy);

        setMode(MODE.Created, featuretype, copy);
    }
}

function onDelete(ctrlDown)
{
    if (!m_layerEdit.features.length)
    {
        return; // NOP
    }
    var activefeature = getActiveFeature(true);
    var type = getTypeCodeFromFeature(activefeature);
    var isprop = isProp(type);

    if (isprop)
    {
        clearProperty(activefeature, ctrlDown);
    }
    else // mark feature as deleted
    {
        if (OpenLayers.State.INSERT != activefeature.state)
        {
            if (!confirm("Are you sure you want to delete this?"))
            {
                return;
            }
            // we mark it deleted (unless it's a new feature)
            activefeature.state = OpenLayers.State.DELETE;

            activefeature.style = m_styleDeleted;
            activefeature.renderIntent = "deleted";
            activefeature.layer.drawFeature(activefeature);

            setMode(MODE.Select, null, activefeature);

            // force dot handles off, user must commit before selecting another object
            m_ctlModify.selectControl.unselectAll();

            saveChanges(); // we go ahead and commit
        }
    }
}

function clearProperty(feature, ctrlDown)
{
    var userid = YAHOO.util.Cookie.get("UserId");
    if (ctrlDown && !userid)
    {
        alert("Only registered users can delete entire properties");
        return;
    }
    //if (!feature.attributes.version || feature.attributes.version < 10)
    {
        var lid = getLidFromAttributes(feature.attributes);
        if (lid.length > 0)
        {
            if (!confirm(ctrlDown ? "Are you sure you want to delete this property?" : "Are you sure you want to clear this property?"))
            {
                return;
            }

            // property, do clear operation
            var params = {};
            params.lid = lid;
            params.z = (m_Map.zoom + 1);
            params.bbox = ctrlDown ? "deleteProperty" : "purgeProperty";
            params.sn = m_SerialVersion;

            var request = OpenLayers.Request.GET({
            url: "/MapManager/MapManager"
                , params: params
                , success: ctrlDown ? this.onSaveSuccess : this.onRegenSuccess
                , failure: ctrlDown ? this.onSaveFailure : this.onRegenSuccess
                , scope: this
            });
        }
    }
}

function showDelete(feature)
{
    var btn = Dom.get("btnDelete");
    if (feature)
    {
        var enabled = true;
        var prompt = "Delete Detail Feature";
        var tooltip = "Deletes the selected detail feature (DEL key is the shortcut)";
        var type = getTypeCodeFromFeature(feature);
        if (type < 0)
        {
            prompt = "Delete Road Segment";
            tooltip = "Deletes the selected road segment (DEL key is the shortcut)";
        }
        else if (type < 256)
        {
            prompt = "Clear Property Details";
            tooltip = "Clears all the computer generated property detail features so you can start fresh (DEL key is the shortcut)";

            var userid = YAHOO.util.Cookie.get("UserId");
            if (userid)
            {
                tooltip += ". Using CTRL + DEL keys will delete the property.";
            }
            enabled = (feature.attributes && (!feature.attributes.version || feature.attributes.version < 10));
        }

        if (enabled && m_EditActiveFull) // we only show if we are not in 'view edit' mode
        {
            btn.style.visibility = "visible";
            btn.innerHTML = prompt;
            btn.title = tooltip;
            return;
        }
    }
    // FALL-THRU
    btn.style.visibility = "hidden";
}

/**************************
    Feature creation event handlers
**************************/

function onCreateLineFeature(feature, featuretype)
{
    if (!featuretype)
        featuretype = getSubMode();        

    if (FEATURES[featuretype].Geom == "multiline")
    {
        // convert from linestring to multilinestring
	    var oldgeom = feature.geometry;
	    feature.layer.renderer.eraseGeometry(oldgeom);
	    feature.geometry = new OpenLayers.Geometry.MultiLineString(oldgeom);
    }
	feature.state = OpenLayers.State.INSERT;
	
	// assign feature_type
    feature.attributes = OpenLayers.Util.extend({'feature_type': featuretype, 'layer': FEATURES[featuretype].Layer}, feature.attributes);

    if (m_UserSelectedTexture)
    {
        assignAttribute(feature, "texture", m_UserSelectedTexture, { 'texture': m_UserSelectedTexture });
    }
    feature.style = m_styleModified;
    feature.renderIntent = "modified";
	feature.layer.drawFeature(feature);

	setMode(MODE.Created, featuretype, feature);
}

function onCreatePolyFeature(feature, featuretype)
{
    if (!featuretype)
        featuretype = getSubMode();        
    
	var oldgeom = feature.geometry;
	feature.layer.renderer.eraseGeometry(oldgeom);
	feature.geometry = new OpenLayers.Geometry.MultiPolygon(oldgeom);
	feature.state = OpenLayers.State.INSERT;

    feature.attributes = OpenLayers.Util.extend({'feature_type': featuretype, 'layer': FEATURES[featuretype].Layer}, feature.attributes);
    if (m_UserSelectedTexture)
    {
        assignAttribute(feature, "texture", m_UserSelectedTexture, { 'texture': m_UserSelectedTexture });
    }
    if (isProp(featuretype))
    {
        // property types start off as border fill style
        assignAttribute(feature, "fill_mode", "B", { 'fill_mode': "B" });
    }
    feature.style = m_styleModified;
    feature.renderIntent = "modified";
    feature.layer.drawFeature(feature);

	setMode(MODE.Created, featuretype, feature);
}

function onCreatePointFeature(feature)
{
    OpenLayers.Console.log("za:onCreatePointFeature");

    var featuretype = getSubMode();        
    
	var oldgeom = feature.geometry;
	feature.layer.renderer.eraseGeometry(oldgeom);
	feature.geometry = new OpenLayers.Geometry.MultiPoint(oldgeom);
	feature.state = OpenLayers.State.INSERT;
    feature.attributes = OpenLayers.Util.extend({'feature_type': featuretype, 'layer': FEATURES[featuretype].Layer}, feature.attributes);
    feature.style = m_styleModified;
    feature.renderIntent = "modified";
    feature.layer.drawFeature(feature);

	setMode(MODE.Created, featuretype, feature);
}

/**************************
    Mode and SubMode management
    
            
    Select Mode             Active Controls         Clickout Action     Escape Action
    -----------             ---------------         ---------------     -------------
    No Selection            ctlClick, ctlModify     n/a                 n/a
    Selected, unmodified    ctlClcik, ctlModify     Unselect            Unselect
    Selected, modified      ctlModify               NOP                 Prompt then unselect
    Activated, unmodified   ctlModify               Unselect            Unselect
    Activated, modified     ctlModify               Deactivate          Deactivate

    Create Mode             Active Controls         Clickout Action     Escape Action
    -----------             ---------------         ---------------     -------------

**************************/

function setMode(mode, submode, feature)
{
    OpenLayers.Console.log("za:setMode from " + m_Mode + " - " + m_SubMode + " to " + mode + " - " + submode);
    switch (mode)
    {
        case MODE.Select:
            // make sure our create controls are off
            m_ctlAddPoly.deactivate();
            m_ctlAddPoint.deactivate();
            m_ctlAddLine.deactivate();

            unsetCreateButtons();

            // cancel crosshair cursor in case we were in creating mode
            m_Map.div.style.cursor = "default";

            var featurestate = FEATURESTATE.Unmodified;
            // state of our click handler:
            // if active feature is unmodified, or no active feature turn on
            // else if modified feature turn off
            if (feature)
            {
                // determine submode from feature
                submode = getTypeCodeFromFeature(feature);

                if (feature.state)
                {
                    // feature is modified so we don't want
                    // clickouts to unselect a modified feature
                    if (OpenLayers.State.UPDATE == feature.state || OpenLayers.State.INSERT == feature.state)
                    {
                        featurestate = FEATURESTATE.Modified;
                    }
                    else if (OpenLayers.State.DELETE == feature.state)
                    {
                        featurestate = FEATURESTATE.Deleted;
                    }
                }

                if (!(m_EditState & EDITSTATE.NoShape)) // only reset reshape bit if we are not in the noshape state
                {
                    if (isMeta(submode) || isShape(submode)) // put into meta shape editing mode
                    {
                        m_ctlModify.mode &= ~OpenLayers.Control.ModifyFeature.RESHAPE; // make sure reshape is off
                    }
                    else
                    {
                        m_ctlModify.mode |= OpenLayers.Control.ModifyFeature.RESHAPE;
                    }
                }
            }

            if (FEATURESTATE.Unmodified == featurestate)
            {
                if (feature)
                {
                    feature.style = (m_EditState & EDITSTATE.NoModify) ? m_styleInvisible : m_styleSelected;
                    feature.renderIntent = (m_EditState & EDITSTATE.NoModify) ? "invisble" : "selected";
                    feature.layer.drawFeature(feature);
                }
                if (!(m_EditState & EDITSTATE.NoMouse)) m_ctrlMouseClickEditor.activate();
            }
            else
            {
                // in deference to experienced users, we don't deactivate mouse select anymore
                // if it's a modified feature (users can 'click-out' and abort changes)
                //m_ctrlMouseClickEditor.deactivate();
            }

            if (feature)
            {
                // activate modify control
                // we need to do this after the feature has been drawn
                // by drawFeature or the dot handles don't work properly
                m_ctlModify.selectControl.select(feature);
            }

            // sync UI
            indicateFeatureState(feature ? featurestate : null, feature);
            showFeatureTexture(feature);
            showFeatureLineWidthOrFillMode(feature);
            showFeatureType(feature);
            showFeatureLayer(feature);
            showFeatureTextureShading(feature);
            showDelete(feature);
            break;

        case MODE.Creating:

            // unselect dot handles if selected (we might be coming from created mode)
            m_ctlModify.selectControl.unselectAll();
            m_layerEdit.destroyFeatures();

            // going into feature create, turn off our click selection control
            m_ctrlMouseClickEditor.deactivate();

            // activate create control
            switch (FEATURES[submode].Geom)
            {
                case "poly":
                    m_ctlAddLine.deactivate();
                    m_ctlAddPoint.deactivate();
                    m_ctlAddPoly.activate();
                    break;
                case "line":
                case "multiline":
                    m_ctlAddPoly.deactivate();
                    m_ctlAddPoint.deactivate();
                    m_ctlAddLine.activate();
                    break;
                case "point":
                    m_ctlAddPoly.deactivate();
                    m_ctlAddLine.deactivate();
                    m_ctlAddPoint.activate();
                    break;
            }

            if (m_layerEdit.features.length > 0)
            {
                if (feature)
                {
                    showFeatureType(feature);
                }
                indicateFeatureState(FEATURESTATE.Created);
            }
            else
            {
                indicateFeatureState(FEATURESTATE.Creating);
            }
            showFeatureTexture(feature);
            showFeatureLayer(feature);
            showFeatureLineWidthOrFillMode(feature);
            showFeatureTextureShading(feature);
            showDelete(feature ? feature : null);
            m_Map.div.style.cursor = "crosshair";
            break;

        case MODE.Created:
            // make sure our create controls are off
            m_ctlAddPoly.deactivate();
            m_ctlAddPoint.deactivate();
            m_ctlAddLine.deactivate();
            unsetCreateButtons();

            m_Map.div.style.cursor = "default";

            if (feature)
            {
                if (isMeta(submode) || isShape(submode))
                {
                    m_ctlModify.mode &= ~OpenLayers.Control.ModifyFeature.RESHAPE;
                }
                else
                {
                    m_ctlModify.mode |= OpenLayers.Control.ModifyFeature.RESHAPE;
                }
                m_ctlModify.selectControl.select(feature);
            }
            showFeatureTexture(feature);
            showFeatureLineWidthOrFillMode(feature);
            showFeatureType(feature);
            showFeatureLayer(feature);
            showFeatureTextureShading(feature);
            showDelete(feature ? feature : null);
            indicateFeatureState(FEATURESTATE.Created);
            break;
    }
    
    // update globals
    m_Mode = mode;
    m_SubMode = (submode != null) ? submode : "";
    setPhantomFeature(feature ? false : true );

    if (YAHOO.util.Cookie.get("UserId") < 200)
    {
        Dom.get("modedisplay").innerHTML = mode + "-" + submode + " (" + m_layerEdit.features.length + ")" +
        (m_ctrlMouseClickEditor.active ? " ctlMouseClick" : "") + (m_ctlModify.active ? " ctlModify" : "") +
        (m_ctlAddLine.active ? " ctlAddLine" : "") + (m_ctlAddPoly.active ? " ctlAddPoly" : "") +
        (m_ctlAddPoint.active ? " ctlAddPoint" : "") + (feature ? " [" + feature.fid + ": " + getLidFromAttributes(feature.attributes) + "]" : "");
    }
}

function onCancel()
{
    m_Events.triggerEvent("cancel");
}

function onCancelMode(e)
{
    if (m_EditActive)
        cancelMode();
}

function cancelMode(bForceDiscard, bNoPrompt)
{
    var activefeature = getActiveFeature(true);

    switch (m_Mode)
    {
        case MODE.Select:
//                if (activefeature && activefeature.state && !bNoPrompt)
//                {
//                    if (!confirm("Changes will be discarded!"))
//                    {
//                        return false;
//                    }
//                }

            m_layerEdit.destroyFeatures();
            setMode(MODE.Select);
            break;
            
        case MODE.Creating:
            if (!bForceDiscard)
            {
                // go into create mode if objects were created, otherwise select mode
                if (m_layerEdit.features.length > 0)
                {
                    setMode(MODE.Created, m_SubMode, m_layerEdit.features[m_layerEdit.features.length-1]);
                }
                else
                {
                    setMode(MODE.Select);
                }
            }
            else
            {
//                    if (m_layerEdit.features.length > 0 && !bNoPrompt)
//                    {
//                        if (!confirm("Changes will be discarded!"))
//                        {
//                            return false;
//                        }
//                    }

                m_layerEdit.destroyFeatures();
                setMode(MODE.Select);
            }
            break;

        case MODE.Created:            
//                if (m_layerEdit.features.length > 0 && !bNoPrompt)
//                {
//                    if (!confirm("Changes will be discarded!"))
//                    {
//                        return false;
//                    }
//                }
            
            m_layerEdit.destroyFeatures();
            setMode(MODE.Select);
            break;
    }
    return true;
}

var m_PhantomFeature;

function setPhantomFeature(isphantom)
{
    if (isphantom)
    {
        m_PhantomFeature = { isPhantom: true };
    }
    else if (m_layerEdit.features.length)
    {
        m_PhantomFeature = null;
        //m_layerEdit.features[0] = OpenLayers.Util.extend({ isPhantom: false }, m_layerEdit.features[0]);
    }
}

function getActiveFeature(bNoPhantom)
{
    if (!bNoPhantom && null != m_PhantomFeature)
    {
        return m_PhantomFeature;
    }
    if (m_layerEdit.features.length)
    {
        return m_layerEdit.features[0];
    }
    return null;
}

function getMode()
{
    return m_Mode;
}

function getSubMode()
{
    if (m_SubMode.length > 0)
    {
        return m_SubMode;
    }
    return 0;
}

function onSave()
{
    m_Events.triggerEvent("save");
}

function onSaveChanges(e)
{
    saveChanges();
}

function saveChanges(onsuccess, onfailure)
{
    m_layerEdit.onSuccess = onsuccess ? onsuccess : this.onSaveSuccess;
    m_layerEdit.onFailure = onfailure ? onfailure : this.onSaveFailure;
    
    if (m_layerEdit.features.length)
    {
        indicateFeatureState(FEATURESTATE.Saving);
        setStatusBar("Saving changes...");
        cursor_wait();

        if (m_layerEdit.selectedFeatures.length > 0)
        {
            // unselect dot handles if selected
            m_ctlModify.selectControl.unselectAll();
        }

        // update layer featureName and writer layerName to match feature type
        // TODO: openlayers cannot handle commits to more than one table
        var featurename = FEATURES[m_layerEdit.features[0].attributes.feature_type].Table;
        m_layerEdit.writer.layerName = "wa:" + featurename;
        m_layerEdit.writer.featureName = featurename;

        for (var i=0; i < m_layerEdit.features.length; i++)
        {
            if (featurename != "roads") // TODO: activate when userid column has been added to roads
            {
                var userid = YAHOO.util.Cookie.get("UserId");
                if (!userid)
                {
                    userid = YAHOO.util.Cookie.get("TempId");
                }
                if (userid)
                {
                    var attributes = m_layerEdit.features[i].attributes;

                    if (attributes.userid)
                    {
                        m_layerEdit.features[i].attributes.userid = userid;
                    }
                    else
                    {
                        m_layerEdit.features[i].attributes = OpenLayers.Util.extend({ 'userid': userid }, attributes);
                    }
                }
            }
            setSysParam(m_layerEdit.features[i], "z", (m_Map.zoom + 1), { 'z': (m_Map.zoom + 1) });
        }

        m_layerEdit.commit({ gkt: m_GKT });
    }
}

function onSaveSuccess(response)
{
    indicateFeatureState(FEATURESTATE.Saved);

    // we get the active feature before we call setmode
    var activefeature = getActiveFeature(true);
    
    m_layerEdit.destroyFeatures();
    setMode(MODE.Select);

    // tile regeneration should happen via geoserver
    var bounds = m_Map.getExtent();
    RedrawMap(bounds.left, bounds.bottom, bounds.right, bounds.top, true);
    
    cursor_clear();

    // call infobox (in included module) to refresh points
    refreshPoints();

    var fid;
    if (activefeature && activefeature.fid && activefeature.fid.length > 0)
        fid = activefeature.fid;

    // now see if we have a newly inserted object
    var fidfrominsert = false;
    // if we get a full response object (with responseXML) then it's not from the layer save but a MM call (like prop delete)
    if (typeof(response) == "string")
    {
        var responseXML = OpenLayers.parseXMLString(response);
        var insertResult = responseXML.getElementsByTagName("wfs:InsertResult")[0];
        var featureIds = insertResult.getElementsByTagName("ogc:FeatureId");
        for (var index = 0; index < featureIds.length; index++)
        {
            var featureId = featureIds[index];
            if (featureId)
            {
                insertfid = featureId.getAttribute("fid");
                if (insertfid != "none")
                {
                    fid = insertfid;
                    fidfrominsert = true;
                    break;
                }
            }
        }
    }

    if (fidfrominsert && isMeta(getTypeCodeFromFeature(activefeature)))
        return; // we don't try to select object after a meta create, the identity value is not the master object

    // now select the new or existing object
    if (fid)
    {
        m_layerEdit.loadFeaturesFromFID(fid, { sn: Math.round(Math.random() * 1000000), async: false });
    }
}

function onSaveFailure(response)
{
    alert(response);

    cursor_clear();
    indicateFeatureState(FEATURESTATE.Modified, getActiveFeature(true)); //TODO: what if feature was deleted?
}

/**************************
    Mode and feature indication and propeties
**************************/

function indicateFeatureState(state, feature)
{
    var msg = "";
    switch (state)
    {
        case FEATURESTATE.Unmodified:
            msg = "Feature selected - ";
            if (m_ctlModify.mode & OpenLayers.Control.ModifyFeature.DRAG) msg += "Drag center dot handle to move shape. "
            if (m_ctlModify.mode & OpenLayers.Control.ModifyFeature.RESHAPE)
            {
                // only display text that talks about path adjust when we don't have the 'noshape' bit on
                // or when we have something other than a meta object or shape selected
                msg += "Drag vertex dot handle to adjust it, midpoint to create new segment. Hover over vertex and hit 'D' key to delete it. ";
                var featuretype = getTypeCodeFromFeature(feature);
                if (isRoad(featuretype)) msg += "Toggle road end point 'glue' with the shift key (green endpoints are unglued). ";
            }
            if (m_ctlModify.mode & OpenLayers.Control.ModifyFeature.RESIZE) msg += "Drag resize or rotate handles to adjust entire shape. "
            msg += "ESC key or 'click-out' to unselect."
            break;
        case FEATURESTATE.Modified:
            msg = "Feature modified - click Save or ENTER key to commit, Cancel or ESC key to discard.";
            break;
        case FEATURESTATE.Deleted:
            msg = "Feature deleted - click Save or ENTER key to commit, Cancel or ESC key to discard";
            break;
        case FEATURESTATE.Saving:
            msg = "Saving changes...";
            break;
        case FEATURESTATE.Saved:
            msg = "Changes saved";
            break;
        case FEATURESTATE.Creating:
            msg = "Creation mode is active - click on map to create the vertex points. Double-click to finish. Hit ESC key to cancel.";
            break;
        case FEATURESTATE.Created:
            msg = "You can adjust this new shape by dragging the handles. When ready, click Save or ENTER key to commit the new object, or the Cancel or ESC key to discard.";
            break;
        case FEATURESTATE.Disabled:
            msg = "You need to zoom in closer to select an object for editing.";
            break;
    }

    setGnomeMessage(msg);
}

function getDefaultOrPhantomFeatureType(defaultfeaturetype, roadonly)
{
    var activefeature = getActiveFeature();
    var activefeaturetype = getTypeCodeFromFeature(activefeature);
    if (activefeaturetype)
    {
        // we use the feature type from the active feature (it could be the phantom)
        if ((roadonly && activefeaturetype < 0) || (!roadonly && activefeaturetype > 0))
        {
            defaultfeaturetype = activefeaturetype;
        }
        // else the selected feature type does not match the class (road or prop)
    }
    return defaultfeaturetype;
}

function getDefaultOrPhantomTexture(defaulttexture, roadonly)
{
    var activefeature = getActiveFeature();
    var activetexture = getFeatureTexture(activefeature);
    if (activetexture)
    {
        // we use the texture from the active feature (it could be the phantom)
        var isroadtexture = isNaN(parseInt(activetexture));
        if ((roadonly && isroadtexture) || (!roadonly && !isroadtexture))
        {
            defaulttexture = activetexture;
        }
    }
    return defaulttexture;
}

function onClickCreatePoly(e)
{
    onClickCreate("poly", (e.newValue ? "32512" : null), getDefaultOrPhantomTexture("800", false));
    return false; // we handled the change, abort framework actions
}

function onClickCreateLine(e)
{
    onClickCreate("line", (e.newValue ? "8323072" : null), getDefaultOrPhantomTexture("2948", false));
    return false; // we handled the change, abort framework actions
}

function onClickCreate(type, submode, texture)
{
    if (cancelMode(true) && submode)
    {
        setCreateButton(type);
        if (texture != null)
        {
            m_UserSelectedTexture = texture;
        }
        setMode(MODE.Creating, submode);
        showTexturePalette(null, m_UserSelectedTexture, submode);
        showFeatureLineWidthOrFillMode(null, submode);
        showFeatureTypePalette(null, submode);
    }
}

function setCreateButton(type)
{
    for (var i = 0; i < m_CreateButtons.length; i++)
    {
        var btn = m_CreateButtons[i].btn;
        btn.set("checked", (type == m_CreateButtons[i].type ? true : false), true);
        btn.blur();
    }
}

function unsetCreateButtons()
{
    for (var i = 0; i < m_CreateButtons.length; i++)
    {
        var btn = m_CreateButtons[i].btn;
        btn.set("checked", false, true);
        btn.blur();
    }
}

function setCreateButtonsDisabled(bDisabled)
{
    for (var i = 0; i < m_CreateButtons.length; i++)
    {
        var btn = m_CreateButtons[i].btn;
        btn.set("disabled", bDisabled, true);
        btn.CHECKBOX_DEFAULT_TITLE = (bDisabled ? m_CreateButtons[i].titledisable : m_CreateButtons[i].title);
        btn.refresh(null, true);
    }
}

/**************************
    Feature type selection and indication
**************************/
function getTypeCodeFromFeature(feature)
{
    var featuretypecode = null;

    if (feature)
    {
        var attributes = feature.attributes;
        
        if (attributes)
        {
            featuretypecode = attributes.feature_type;
        }
    }
    return featuretypecode;
}

function showFeatureType(feature)
{
    var featuretype = getTypeCodeFromFeature(feature);
    var mastercategory = getMasterCategoryForFeatureType(featuretype);
    showFeatureTypePalette(mastercategory, featuretype);
    
    if (!featuretype || isProp(featuretype) || isMeta(featuretype)) // property or metaroof type
    {
        m_ActiveLIDWorthyFeatureType = featuretype;
    }
    updateStatusBar(feature);
}

function onTabClickPropertyType(e)
{
    var tab = m_tabvwPropertyType.getTab((e != null) ? e.newValue : 0);
    var mastercategory = getTabId(tab);

    showFeatureTypePalette(mastercategory, m_ActiveLIDWorthyFeatureType);
}

function onTabClickQuickShape(e)
{
    var tab = m_tabvwQuickShape.getTab((e != null) ? e.newValue : 0);
    var mastercategory = getTabId(tab);

    showFeatureTypePalette(mastercategory, m_ActiveLIDWorthyFeatureType);
}

function showFeatureTypePalette(mastercategory, featuretype)
{
    var selectit = false;
    
    // convert some of the legacy auto-generated featuretypes into a freehand poly or line
    if (featuretype && isFreehandPoly(featuretype)) featuretype = 32512;
    if (featuretype && isFreehandLine(featuretype)) featuretype = 8323072;
    
    if (mastercategory || featuretype) // if passed master category or featuretype show it
    {
        if (!mastercategory && featuretype)
        {
            // determine master category from feature type
            mastercategory = getMasterCategoryForFeatureType(featuretype);
        }

        var obj;
        obj = getTabForFeatureTypeMasterCategory(mastercategory);
        if (obj)
        {
            selectit = true;
            obj.tabview.set("activeTab", obj.tab, true);
        }
    }

    // now indicate selected feature type (if any)
    var els = Dom.getElementsByClassName("paletteitemfeaturetype");
    if (els)
    {
        Dom.removeClass(els, "paletteitemselected");
        if (featuretype && selectit) // when a detail feature is selected there is no tab, just leave current tab alone
        {
            for (var i = 0; i < els.length; i++)
            {
                var el = els[i];
                if (el.id == ("paletteitem" + featuretype))
                {
                    Dom.addClass(el, "paletteitemselected");  
                }
            }
        }
    }
    // first unselect all thumbnail images
    setPaletteItemThumbnail(null, "img/blank.gif", "no selection");
    
    if (selectit)
    {
        var curfeaturetypeobj = FEATURES[featuretype];
        var curfeaturetypemastercategory = getMasterCategoryForFeatureType(featuretype);
        if (curfeaturetypeobj != null && curfeaturetypemastercategory != null && curfeaturetypemastercategory == mastercategory)
        {
            image = EP_PIX + curfeaturetypeobj.Image;
            title = (curfeaturetypeobj.Class.length ? curfeaturetypeobj.Class + " - " + curfeaturetypeobj.Type : curfeaturetypeobj.Type)
            setPaletteItemThumbnail(mastercategory, image, title);
        }
    }
}

var m_FeatureTypeDDInfo = {};

function initFeatureTypeControls()
{
    for (var i = 0; i < m_tabvwPropertyType.get("tabs").length; i++)
    {
        var tab = m_tabvwPropertyType.getTab(i);
        var content = getFeatureTypePaletteContent(getTabId(tab));
        tab.set("content", content);
    }
    for (var i = 0; i < m_tabvwQuickShape.get("tabs").length; i++)
    {
        var tab = m_tabvwQuickShape.getTab(i);
        var content = getFeatureTypePaletteContent(getTabId(tab));
        tab.set("content", content);
    }
    // we also initialize any other palettes (the house wizard for now)
    var hwpalettecontent = getFeatureTypePaletteContent("#HouseWizard");
    var els = Dom.getElementsByClassName("hwpalettecontent");
    for (var i = 0; i < els.length; i++)
    {
        els[i].innerHTML = hwpalettecontent;
    }
}

// Our custom drag and drop implementation, extending YAHOO.util.DD
YAHOO.util.DDZA = function(id, sGroup, config)
{
    YAHOO.util.DDZA.superclass.constructor.apply(this, arguments);
    this.isTarget = false;
    this.centerFrame = true;
};

YAHOO.extend(YAHOO.util.DDZA, YAHOO.util.DDProxy, {
    origZ: 0,
    _resizeProxy: function()
    {
        if (this.resizeFrame)
        {
            var dragEl = this.getDragEl();

            Dom.setStyle(dragEl, "width", "58px");
            Dom.setStyle(dragEl, "height", "58px");
        }
        var obj = m_FeatureTypeDDInfo[this.getEl().id];
        Dom.setStyle(dragEl, "background-image", "url(" + obj.image + ")");
        Dom.setStyle(dragEl, "opacity", "0.4");
    },
    startDrag: function(x, y)
    {
        var el = this.getEl();
        // fixup display because the mousemove gets eaten in ie
        //Dom.setStyle(el, "padding", "3px 3px 3px 3px");
        //Dom.setStyle(el, "border", "none");
    },
    endDrag: function(e)
    {
        // we don't call the base class so that the linked item is not moved
        //YAHOO.util.DDProxy.superclass.endDrag.call(this, arguments);
    },
    onDragDrop: function(e)
    {
        if (cancelMode(true))
        {
            var dd = this.getEl();
            var obj = m_FeatureTypeDDInfo[dd.id];
            var featuretype = obj.featuretype;
            obj = FEATURES[featuretype];

            var texture = getDefaultOrPhantomTexture(obj.DefaultTexture, false);
            if (texture != null)
            {
                m_UserSelectedTexture = texture;
            }
            showTexturePalette(null, m_UserSelectedTexture, featuretype);
            showFeatureLineWidthOrFillMode(null, featuretype);
            showFeatureTypePalette(null, featuretype);

            var mappos = Dom.getXY("map");
            var pixel = new OpenLayers.Pixel(e.clientX - mappos[0], e.clientY - mappos[1]);
            var geom = getDropGeometry(obj.DropGeom, pixel);

            var feature = new OpenLayers.Feature.Vector(geom);
            feature.state = OpenLayers.State.INSERT;
            m_layerEdit.addFeatures([feature]);

            if (m_DropGeoms[obj.DropGeom].geom != "line")
            {
                onCreatePolyFeature(feature, featuretype);
            }
            else
            {
                onCreateLineFeature(feature, featuretype);
            }
        }
    }
});

function getFeatureTypePaletteContent(mastercategory)
{
    var isshape = ("#Shape" == mastercategory);
    var content = "";
    content += "<div id='paletteitemthumbnail'>";
    content += "<img id='paletteitemthumbnailimg' class='paletteitemthumbnailimg paletteitemthumbnailimg" + mastercategory + "' title='no selection' src='img/blank.gif' />";
    content += "</div>"
    content += "<div class='paletteitems'>"
    content += isshape ? "<div style='height: 31px; float: left; width: 132px'>" : "<div style='height: 31px;'>";

    var j = 0;
    for (var i = 0; i < FEATUREPICKLIST.length; i++)
    {
        var featuretype = FEATUREPICKLIST[i];
        var featuretypeobj = FEATURES[featuretype];

        if (featuretypeobj.MasterCategory == mastercategory)
        {
            var image = EP_PIX + featuretypeobj.Image;
            var title = (featuretypeobj.Class.length ? featuretypeobj.Class + " - " + featuretypeobj.Type : featuretypeobj.Type);
            var id = "paletteitem" + featuretype;
            content += "<img id='" + id + "' class='paletteitem paletteitemfeaturetype' title='" + title + "' src='" + image + "' onmouseover='onHoverFeatureType(\"" + image + "\", \"" + title + "\", \"" + mastercategory + "\");' onmouseout='onUnHoverFeatureType(\"" + mastercategory + "\");' onclick='onClickFeatureType(\"" + featuretype + "\");' >";
            content += "</img>"

            if (m_FeatureTypeDDInfo[id] == undefined)
            {
                // add DD handler
                var dd = new YAHOO.util.DDZA(id);
                m_FeatureTypeDDInfo[id] = { dd: dd, featuretype: featuretype, image: image};
            }
            
            j++;
            if (!(j % 7))
            {
                content += isshape ? "</div><div style='height: 31px; float: left; width: 132px'>" : "</div><div style='height: 31px;'>"
            }
        }
    }
    content += "</div>";
    if (isshape)
    {
        content += "<div style='float: right; width: 98px; height: 60px; padding: 0px 2px 0px 2px; border-left: 1px solid black'>";
        content += "<div class='titlesel titleselsm' title='Click a button to enter create mode'>Freehand&nbsp;Shapes</div>"
        content += "<div align='center' style='width: 98px; padding: 4px 0px 2px 0px;'><span id='btnPoly' class='yui-button yui-checkbox-button createpoly paletteitemfeaturetype'><span class='first-child'><button type='button'></button></span></span>";
        content += "<span id='btnLine' class='yui-button yui-checkbox-button createline'><span class='first-child'><button type='button'></button></span></span>";
        content += "<div id='paletteitem32512' class='paletteitemfeaturetype' style='width: 36px; height: 1px; float: left; margin-left: 9px; border-left: none !important; border-right: none !important; border-bottom: none !important;'></div><div id='paletteitem8323072' class='paletteitemfeaturetype' style='width: 36px; height: 1px; float: right; margin-right: 9px; border-left: none !important; border-right: none !important; border-bottom: none !important;' /></div></div>";
        content += "</div>";
    }
    content += "</div>";
    return content;
}

var m_DropGeoms = { "parcel": { geom: "poly", points: [new OpenLayers.Pixel(-50, -75), new OpenLayers.Pixel(-75, 50), new OpenLayers.Pixel(75, 50), new OpenLayers.Pixel(50, -75)] }
    , "house": { geom: "poly", points: [new OpenLayers.Pixel(-35, -25), new OpenLayers.Pixel(35, -25), new OpenLayers.Pixel(35, 25), new OpenLayers.Pixel(-35, 25)] }
    , "campus": { geom: "poly", points: [new OpenLayers.Pixel(-75, -75), new OpenLayers.Pixel(-75, 75), new OpenLayers.Pixel(75, 75), new OpenLayers.Pixel(75, -75)] }
    , "tennis": { geom: "poly", points: [new OpenLayers.Pixel(-39, -18), new OpenLayers.Pixel(-39, 18), new OpenLayers.Pixel(39, 18), new OpenLayers.Pixel(39, -18)]} // 78x36
    , "baseball": { geom: "poly", points: [new OpenLayers.Pixel(0, -50), new OpenLayers.Pixel(50, 0), new OpenLayers.Pixel(0, 50), new OpenLayers.Pixel(-50, 0)] }
    , "football": { geom: "poly", points: [new OpenLayers.Pixel(-108, -48), new OpenLayers.Pixel(-108, 48), new OpenLayers.Pixel(108, 48), new OpenLayers.Pixel(108, -48)]} // 360x160
    , "parking": { geom: "poly", points: [new OpenLayers.Pixel(-30, -8), new OpenLayers.Pixel(-30, 8), new OpenLayers.Pixel(30, 8), new OpenLayers.Pixel(30, -8)] }
    , "triangle": { geom: "polyreg", numsides: 3, radius: new OpenLayers.Pixel(30, 30) }
    , "rectangle": { geom: "polyreg", numsides: 4, radius: new OpenLayers.Pixel(30, 30) }
    , "circle": { geom: "polyreg", numsides: 45, radius: new OpenLayers.Pixel(30, 30) }
    , "line": { geom: "line", points: [new OpenLayers.Pixel(-10, -14), new OpenLayers.Pixel(8, 12)] }
    , "road": { geom: "line", points: [new OpenLayers.Pixel(-100, -140), new OpenLayers.Pixel(80, 120)] }
};

function getDropGeometry(type, pixel)
{
    var geom;

    var obj = m_DropGeoms[type];
    if (obj.geom == "poly" || obj.geom == "polyreg")
    {
        if (obj.geom == "polyreg")
        {
            var radius = getPointFromPixel(obj.radius).x;
            geom = OpenLayers.Geometry.Polygon.createRegularPolygon(new OpenLayers.Pixel(0, 0), radius, obj.numsides, 0);
        }
        else
        {
            var ring = new OpenLayers.Geometry.LinearRing();
            for (point in obj.points)
            {
                var pt = obj.points[point];
                ring.addComponent(getPointFromPixel(pt));
            }
            geom = new OpenLayers.Geometry.Polygon([ring]);
        }
    }
    else if (obj.geom == "line")
    {
        geom = new OpenLayers.Geometry.LineString();
        for (point in obj.points)
        {
            var pt = obj.points[point];
            geom.addPoint(getPointFromPixel(pt));
        }
    }

    var lonlat = m_layerEdit.getLonLatFromViewPortPx(pixel);
    geom.move(lonlat.lon, lonlat.lat);
    geom.calculateBounds();
    return geom;
}

function getPointFromPixel(pixel)
{
    var res = m_Map.getResolution();
    return new OpenLayers.Geometry.Point(pixel.x * res, pixel.y * res);
}

function onHoverFeatureType(image, title, mastercategory)
{
    setPaletteItemThumbnail(mastercategory, image, title);
}

function onUnHoverFeatureType(mastercategory)
{
    // now set thumbnail image
    var image = "img/blank.gif";
    var title = "no selection";
    
    if (getMasterCategoryForFeatureType(m_ActiveLIDWorthyFeatureType) == mastercategory)
    {
        // restore thumbnail for selected feature
        var curfeaturetypeobj = FEATURES[m_ActiveLIDWorthyFeatureType];
        if (curfeaturetypeobj != null)
        {
            image = EP_PIX + curfeaturetypeobj.Image;
            title = (curfeaturetypeobj.Class.length ? curfeaturetypeobj.Class + " - " + curfeaturetypeobj.Type : curfeaturetypeobj.Type)
        }
    }
    
    setPaletteItemThumbnail(mastercategory, image, title);
}

function setPaletteItemThumbnail(mastercategory, image, title)
{
    var els = Dom.getElementsByClassName("paletteitemthumbnailimg" + (mastercategory ? mastercategory : ""));
    for (var i = 0; i < els.length; i++)
    {
        var imgel = els[i];
        if (Dom.getAncestorByClassName(imgel, "yui-hidden"))
            continue; // we don't update thumbnail images in hidden containers
        imgel.src = image;
        imgel.title = title;
    }
}

function onClickFeatureType(featuretype)
{
    var activefeature = getActiveFeature(true);

    if (activefeature)
    {
        var activefeaturetype = getTypeCodeFromFeature(activefeature);
        var activefeaturemorphgroup = FEATURES[activefeaturetype].MorphGroup;
        var morphgroup = FEATURES[featuretype].MorphGroup;

        if (!activefeaturemorphgroup || !morphgroup || activefeaturemorphgroup != morphgroup)
        {
            return; // we ignore clicks if morphgroup does not match
        }
        var lid = getLidFromAttributes(activefeature.attributes);
        //if (activefeaturetype > 0 && lid.length > 0)
        if (isProp(activefeaturetype) && lid.length > 0)
        {
            if (confirm("Are you sure you want to change this property from '" + FEATURES[activefeaturetype].Type + "' to '" + FEATURES[featuretype].Type + "'?"))
            {
                changeFeatureType(activefeature, activefeaturetype, featuretype);
            }
            return;
        }

        assignAttribute(activefeature, "feature_type", featuretype, { 'feature_type': featuretype });
        var layer = FEATURES[featuretype].Layer;
        assignAttribute(activefeature, "layer", layer, { 'layer': layer });

        if (featuretype)
        {
            // a non-null type so the active feature is not the phantom, update state
            if (!activefeature.isPhantom && activefeature.state != OpenLayers.State.INSERT)
            {
                // set feature state to modified (unless it's a new object)
                activefeature.state = OpenLayers.State.UPDATE;
                activefeature.style = m_styleModified;
                activefeature.renderIntent = "modified";
                activefeature.layer.drawFeature(activefeature);
                indicateFeatureState(FEATURESTATE.Modified);
            }
        }

        showFeatureType(activefeature);
    }
}

function changeFeatureType(feature, oldfeaturetype, newfeaturetype)
{
    if (feature)
    {
        var lid = getLidFromAttributes(feature.attributes);
        if (lid.length > 0)
        {
            var params = {};
            params.edit = "Y";
            params.lid = lid;
            params.fieldName = "featureFullname";
            params.fieldLongName = "";
            params.featureType = oldfeaturetype;
            params.featureFullname = newfeaturetype;
            params.sn = m_SerialVersion;

            new OpenLayers.Ajax.Request(
                EP_INFOBOX,
                {
                    method: 'get',
                    parameters: params,
                    onComplete: OpenLayers.Function.bind(this.onChangeFeatureTypeSuccess, this),
                    onFailure: function(request) { OpenLayers.Console.error(request.responseText); }
                }
            );
        }
    }
}

function onChangeFeatureTypeSuccess()
{
    // call for map redraw, this insures that our serial number is refreshed
    var bounds = m_Map.getExtent();
    RedrawMap(bounds.left, bounds.bottom, bounds.right, bounds.top, true);

    var activefeature = getActiveFeature(true);
    if (activefeature && activefeature.fid.length > 0)
    {
        m_layerEdit.loadFeaturesFromFID(activefeature.fid, { sn: m_SerialVersion });
    }
}

function getTabForFeatureTypeMasterCategory(mastercategory)
{
    for (i = 0; i < 2; i++)
    {
        var tabview = (i ? m_tabvwQuickShape : m_tabvwPropertyType);
        for (var j = 0; j < tabview.get("tabs").length; j++)
        {
            var tab = tabview.getTab(j);
            if (mastercategory == getTabId(tab))
            {
                return { tab: tab, tabview: tabview };
            }
        }
    }
    return null;
}

function getTabId(tab)
{
    var id = "";
    var href = tab.get("href");
    if (href.length)
    {
        id = href.substr(href.indexOf("#"));
    }
    return id;
}

function getMasterCategoryForFeatureType(featuretype)
{
    var featureobj = FEATURES[featuretype];
    if (!featureobj)
    {
        return null;
    }
    return featureobj.MasterCategory;
}

function updateStatusBar(feature)
{
    var body = "No item selected.";

    if (feature)
    {
        var lid = getLidFromAttributes(feature.attributes);
        var featuretype = getTypeCodeFromFeature(feature);

        if (lid.length > 0)
        {
            var params = {};
            params.lid = lid;
            params.featureType = featuretype;
            params.click = "Y";
            params.floatMode = "t";
            params.seqnum = Math.round(Math.random() * 1000000);

            OpenLayers.Request.GET({ url: EP_INFOBOX, params: params, success: loadStatusBar, scope: this });

            body = "Loading info...";
        }
        else if (!feature.isPhantom) // no lid, is either an unassociated detail or new road or property
        {
            if (isDetail(featuretype) || isMeta(featuretype))
            {
                var featurename = FEATURES[featuretype].Type;
                if (feature.state == OpenLayers.State.INSERT)
                {
                    body = "New " + featurename;
                }
                else
                {
                    body = "This " + featurename + " has no address association";
                }
            }
            else
            {
                body = "New property";
            }
            // else it's the phantom feature object, show nothing
        }
    }

    loadStatusBar(null, body);
}

function loadStatusBar(response, text)
{
    var content = "Error getting property info for this feature";

    if (response)
    {
        // getting called from ajax request
        if (200 == response.status)
        {
            var format = new OpenLayers.Format.JSON();
            var data = format.read(response.responseText);

            if (data.style == "cloud")
            {
                m_ActiveLIDWorthyFeatureType = data.featuretype;
            }
            var featuretype = getTypeCodeFromFeature(getActiveFeature(true));
            if (isDetail(featuretype) || isMeta(featuretype))
            {
                content = FEATURES[featuretype].Type + " at  " + data.shortnamehtml;
            }
            else
            {
                content = data.shortnamehtml;
            }
        }
    }
    else
    {
        content = text;
    }

    setStatusBar(content);
}

/**************************
Texture selection and indication
**************************/
function getFeatureTexture(feature)
{
    var curtexture = null;
    if (feature && feature.attributes)
    {
        var type = getTypeCodeFromFeature(feature);
        if (type < 0)
        {
            if (feature.attributes.characteristics)
            {
                curtexture = feature.attributes.characteristics;
            }
            else
            {
                switch (type)
                {
                    case "-1100":
                        curtexture = "IS_gYwwwW";
                        break;
                    case "-1200":
                        curtexture = "PR_bYwWc";
                        break;
                    case "-1300":
                        curtexture = "SR_YwwWc";
                        break;
                    case "-1400":
                        curtexture = "LR_yWc";
                        break;
                }
            }
        }
        else
        {
            curtexture = feature.attributes.texture;
        }
    }
    return curtexture;
}

function showFeatureTexture(feature)
{
    var curtexture = getFeatureTexture(feature);
    var mastercategory = getMasterCategoryForTexture(curtexture);
    var featuretype = getTypeCodeFromFeature(feature);

    showTexturePalette(mastercategory, curtexture, featuretype);
}

function onTabClickTexture(e)
{
    var tab = m_tabvwTexture.getTab((e != null) ? e.newValue : 0);
    var mastercategory = getTabId(tab);
    var activefeature = getActiveFeature();
    var textureid = getFeatureTexture(activefeature);
    
    showTexturePalette(mastercategory, textureid);
}

function showTexturePalette(mastercategory, textureid, featuretype)
{
    var content;
    if (mastercategory || textureid) // if passed master category or texture show it
    {
        if (!mastercategory && textureid)
        {
            // determine master category from texture id
            mastercategory = getMasterCategoryForTexture(textureid);
            if (!mastercategory && featuretype)
            {
                // else we've got an object with a texture we don't know about
                // so as a last ditch effort we set mode from featuretype
                setTextureMode((featuretype < 0) ? "road" : "texture");
            }
        }
        if (mastercategory)
        {
            setTextureMode((mastercategory.indexOf("#Road") == 0) ? "road" : "texture");

            content = getTexturePaletteContent(mastercategory, textureid);
            tab = getTabForTextureMasterCategory(mastercategory);
            m_tabvwTexture.set("activeTab", tab, true);
        }
    }
    else
    {
        // no object or current texture selected, update current tab to reflect new state
        tab = m_tabvwTexture.get("activeTab");
        if (tab)
            content = getTexturePaletteContent(getTabId(tab));
    }
    if (content)
    {
        if (tab) tab.set("content", content);
            
        // also set any other texturepalette div's
        var els = Dom.getElementsByClassName("texturepalettecontent");
        if (els)
        {
            for (var i = 0; i < els.length; i++)
            {
                els[i].innerHTML = content;
            }
        }
    }
}

function setTextureMode(mode)
{
    if (m_tabvwTextureMode != mode)
    {
        while (m_tabvwTexture.get("tabs").length > 0)
        {
            m_tabvwTexture.removeTab(m_tabvwTexture.getTab(0));
        }
        if ("road" == mode)
        {
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Local Roads", href: "#RoadsLocal" }));
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Major Roads", href: "#RoadsMajor" }));
        }
        else
        {
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Asphalt &amp; Cement", href: "#Cement" }));
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Brick", href: "#Brick" }));
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Earth &amp; Rock", href: "#EarthRock" }));
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Plants", href: "#Plants" }));
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Trees &amp; Crops", href: "#Trees" }));
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Ground Cover", href: "#GroundCover" }));
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Materials", href: "#Materials" }));
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Wood", href: "#Wood" }));
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Roof Shingles", href: "#Roofing1" }));
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Roof Tiles", href: "#Roofing2" }));
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Water", href: "#Water" }));
            m_tabvwTexture.addTab(new YAHOO.widget.Tab({ label: "Colors &amp; Effects", href: "#ColorsEffects" }));
        }
        m_tabvwTextureMode = mode;
    }
}

function getTexturePaletteContent(mastercategory, selectedtextureid, hiderotate)
{
    var categories = getTexturesForMasterCategory(mastercategory);
    var curtextureobj = TEXTURES[selectedtextureid];
    var curtexturemastercategory = getMasterCategoryForTexture(selectedtextureid);

    var selectedsrc = "img/blank.gif";
    var selectedtitle = "no selection";
    var selectedcanrotate = false;
    var rotationimages = hiderotate ? "" : "<img src='img/countercwoff.png' style='padding-top: 5px; padding-right: 3px;' title='Rotate texture counter-clockwise' /><img src='img/clockwiseoff.png' style='padding-top: 5px; padding-left: 3px;' title='Rotate texture clockwise' />"
    if (curtextureobj != null && curtexturemastercategory == mastercategory)
    {
        selectedsrc = EP_THUMBNAILS + curtextureobj.Image;
        selectedtitle = curtextureobj.CategoryName + " - " + curtextureobj.Name;
        if (!hiderotate && curtextureobj.Next)
        {
            rotationimages = "<img src='img/countercw.png' style='padding-top: 5px; padding-right: 3px; cursor: pointer;' title='Rotate texture counter-clockwise' onClick='onClickTextureRotateCCW();' /><img src='img/clockwise.png' style='padding-top: 5px; padding-left: 3px; cursor: pointer;' title='Rotate texture clockwise'  onClick='onClickTextureRotateCW();' />"
            selectedcanrotate = true;
        }
    }
    
    var content = "";
    content += "<div id='paletteitemrotator'>";
        content += "<img id='paletteitemrotatorimg' title='" + selectedtitle + "' src='" + selectedsrc + "' />";
        content += "<div align='center'>" + rotationimages + "</div>";
    content += "</div>"
    content += "<div class='paletteitems'><div style='height: 31px;'>";

    var k = 0;
    for (var i = 0; i < TEXTUREPICKLIST.length; i++)
    {
        var textureid = TEXTUREPICKLIST[i];
        var textureobj = TEXTURES[textureid];

        if (!textureobj)
        {
            OpenLayers.Console.log("za: Warning, no texture info for texture id " + textureid);
        }
        else
        {
            for (var j = 0; j < categories.length; j++)
            {
                if (textureobj.Category == categories[j])
                {
                    var style = "paletteitem";
                    if (curtextureobj != null && curtextureobj.PickId == textureid)
                    {
                        style += " paletteitemselected";
                    }
                    var canrotate = (!hiderotate && textureobj.Next) ? true : false;
                    var image = EP_THUMBNAILS + textureobj.Image;
                    var title = textureobj.CategoryName + " - " + textureobj.Name;

                    content += "<img class='" + style + "' title='" + title + "' src='" + image + "' onmouseover='onHoverTexture(\"" + image + "\", \"" + title + "\", " + canrotate + ");' onmouseout='onHoverTexture(\"" + selectedsrc + "\", \"" + selectedtitle + "\", " + selectedcanrotate + ");' onclick='onClickTexture(\"" + textureobj.PickId + "\");' >";
                    content += "</img>"

                    k++;
                    if (!(k % 7))
                    {
                        content += "</div><div style='height: 31px;'>"
                    }
                }
            }
        }
    }
    content += "</div></div>"
    return content;
}

function isPaletteItemRotatorImg(el)
{
    return (el.id == "paletteitemrotatorimg") ? true : false;
}

function onHoverTexture(image, title, canrotate)
{
    var els = Dom.getElementsByClassName("texturepalette");
    for (var i = 0; i < els.length; i++)
    {
        var imgels = Dom.getElementsBy(this.isPaletteItemRotatorImg, null, els[i]);
        for (var j = 0; j < imgels.length; j++)
        {
            var imgel = imgels[j];
            if (Dom.getAncestorByClassName(imgel, "yui-hidden"))
                continue; // we don't update thumbnail images in hidden containers
            imgel.src = image;
            imgel.title = title;
            var el = imgel.nextSibling.firstChild;
            if (el)
            {
                el.src = canrotate ? "img/countercw.png" : "img/countercwoff.png";
                el.nextSibling.src = canrotate ? "img/clockwise.png" : "img/clockwiseoff.png";
            }
        }
    }
}

function onClickTexture(texture)
{
    var activefeature = getActiveFeature();
    m_UserSelectedTexture = texture;

    var type = getTypeCodeFromFeature(activefeature);
    if (type < 0)
    {
        assignAttribute(activefeature, "characteristics", texture, { 'characteristics': texture });
    }
    else
    {
        assignAttribute(activefeature, "texture", texture, { 'texture': texture });
        if ("-1" == texture)
        {
            // assigning shadow texture, also set a shading value
            assignAttribute(activefeature, "shading", -30, { 'shading': -30 });
            Dom.get("lblCurrentShading").innerHTML = "-30%";
        }
    }

    if (!activefeature.isPhantom && activefeature.state != OpenLayers.State.INSERT)
    {
        // set feature state to modified (unless it's a new object)
        activefeature.state = OpenLayers.State.UPDATE;
        activefeature.style = m_styleModified;
        activefeature.renderIntent = "modified";
	    activefeature.layer.drawFeature(activefeature);
        indicateFeatureState(FEATURESTATE.Modified);
    }

    showFeatureTexture(activefeature);
}

function onClickTextureRotateCCW(e)
{
    rotateTexture(false);
}

function onClickTextureRotateCW(e)
{
    rotateTexture(true);
}

function rotateTexture(CW)
{
    var activefeature = getActiveFeature();
    var curtexture = activefeature.attributes.texture;
    var curtextureobj = TEXTURES[curtexture];
    activefeature.attributes.texture = CW ? (parseInt(curtexture) - curtextureobj.Next) : (parseInt(curtexture) + curtextureobj.Prev);

    if (activefeature.state != OpenLayers.State.INSERT)
    {
        // set feature state to modified (unless it's a new object)
        activefeature.state = OpenLayers.State.UPDATE;
        activefeature.style = m_styleModified;
        activefeature.renderIntent = "modified";
        activefeature.layer.drawFeature(activefeature);
        indicateFeatureState(FEATURESTATE.Modified);
    }

    // reflect modified state
    redrawFeatureTextureRotation(activefeature);
}

function redrawFeatureTextureRotation(feature)
{
    if (feature)
    {
        var curtexture = feature.attributes.texture;
        if (curtexture)
        {
            var curtextureobj = TEXTURES[curtexture];
            var tab = m_tabvwTexture.get("activeTab");
            var content = getTexturePaletteContent(getTabId(tab), curtexture);
            tab.set("content", content);
        }
    }
}

function getTabForTextureMasterCategory(mastercategory)
{
    for (var i = 0; i < m_tabvwTexture.get("tabs").length; i++)
    {
        var tab = m_tabvwTexture.getTab(i);
        if (mastercategory == getTabId(tab))
        {
            return tab;
        }
    }
    return null;
}

function getMasterCategoryForTexture(textureid)
{
    var textureobj = TEXTURES[textureid];
    if (!textureobj)
    {
        return null;
    }
    var category = textureobj.Category;
    switch (category)
    {
        case "ASPLT":
        case "CEMNT":
        case "STEPS":
            return "#Cement";
            break;
        case "COBBL":
        case "BRICK":
            return "#Brick";
            break;
        case "EARTH":
        case "SAND":
        case "GRAVL":
        case "ROCK":
            return "#EarthRock";
            break;
        case "HEDGE":
        case "FLOWR":
            return "#Plants";
            break;
        case "TREES":
        case "CROP":
            return "#Trees";
            break;
        case "GRASS":
        case "VEGET":
        case "MULCH":
            return "#GroundCover";
            break;
        case "TILE":
        case "METAL":
        case "GLASS":
        case "SEATS":
            return "#Materials";
            break;
        case "WOOD":
        case "DECK":
            return "#Wood";
            break;
        case "MTCHN":
        case "SHNGL":
        case "TAR":
            return "#Roofing1";
            break;
        case "RFTIL":
            return "#Roofing2";
            break;
        case "WATER":
            return "#Water";
            break;
        case "COLOR":
        case "EFFECTS":
            return "#ColorsEffects";
            break;
        case "LR":
        case "SR":
            return "#RoadsLocal";
            break;
        case "PR":
        case "IS":
            return "#RoadsMajor";
            break;
    }
}

function getTexturesForMasterCategory(mastercategory)
{
    switch (mastercategory)
    {
        case "#Cement":
            return ["ASPLT", "CEMNT", "STEPS"];
            break;
        case "#Brick":
            return ["COBBL", "BRICK"];
            break;
        case "#EarthRock":
            return ["EARTH", "SAND", "GRAVL", "ROCK"];
            break;
        case "#Plants":
            return ["HEDGE", "FLOWR"];
            break;
        case "#Trees":
            return ["TREES", "CROP"];
            break;
        case "#GroundCover":
            return ["GRASS", "VEGET", "MULCH"];
            break;
        case "#Materials":
            return ["TILE", "METAL", "GLASS", "SEATS"];
            break;
        case "#Wood":
            return ["WOOD", "DECK"];
            break;
        case "#Roofing1":
            return ["MTCHN", "SHNGL", "TAR"];
            break;
        case "#Roofing2":
            return ["RFTIL"];
            break;
        case "#Water":
            return ["WATER"];
            break;
        case "#ColorsEffects":
            return ["COLOR", "EFFECTS"];
            break;
        case "#RoadsLocal":
            return ["LR", "SR"];
            break;
        case "#RoadsMajor":
            return ["PR", "IS"];
            break;
        default:
            return [];
            break;
    }
}

/**************************
Feature layer indication and event handlers
**************************/
function showFeatureLayer(feature)
{
    var disabled = true;
    if (feature)
    {
        var featuretype = getTypeCodeFromFeature(feature);
        if (featuretype == 32512 || featuretype == 8323072 || isShape(featuretype) || isMeta(featuretype)) // we only consider user-generated detail types
        {
            var layer = parseInt(feature.attributes.layer) - 159;
            if (layer < 1 || layer > 7)
            {
                layer = 1;
            }
            m_mnubtnFeatureLayer.set("label", layer.toString());
            disabled = false;
        }
    }
    if (disabled)
    {
        m_mnubtnFeatureLayer.set("label", "auto");
    }
    m_mnubtnFeatureLayer.set('disabled', disabled);
}

function onMenuClickFeatureLayer(e, o)
{
    var layer = o[1].id;

    if (layer)
    {
        var layertext = parseInt(layer) - 159;
        m_mnubtnFeatureLayer.set("label", layertext.toString());
        var activefeature = getActiveFeature();
        assignAttribute(activefeature, "layer", layer, { 'layer': layer });

        if (activefeature.state != OpenLayers.State.INSERT)
        {
            // set feature state to modified (unless it's a new object)
            activefeature.state = OpenLayers.State.UPDATE;
            activefeature.style = m_styleModified;
            activefeature.renderIntent = "modified";
            activefeature.layer.drawFeature(activefeature);
            indicateFeatureState(FEATURESTATE.Modified);
        }
    }
}

/**************************
Feature line width and fill mode indication and event handlers
**************************/
function showFeatureLineWidthOrFillMode(feature, type)
{
    var showfillmode = false;
    var disablelinewidth = true;
    var linewidthvalue = "n/a";
    var fillmodevalue = false;

    if (feature)
    {
        type = getTypeCodeFromFeature(feature);
        var ispoly = (isProp(type) || (isDetail(type) && FEATURES[type].Geom == "poly"));
        var islinear = ((isDetail(type) && FEATURES[type].Geom == "multiline") || (isRoad(type) && FEATURES[type].Geom == "line"));

        if (ispoly)
        {
            showfillmode = true;
            fillmodevalue = (feature.attributes.fill_mode ? (feature.attributes.fill_mode == 'B' ? true : false) : false);
        }
        else if (islinear)
        {
            if (type >= 256) // for now we don't allow setting width of roads
            {
                var val = (feature.attributes.width * 40.0);
                if (!val) val = 36;
                linewidthvalue = val.toFixed(1);
                disablelinewidth = false;
            }
        }
    }
    else if (type)
    {
        // if in create mode we want to show the fill mode button for property types
        showfillmode = true;
        fillmodevalue = true;
    }

    Dom.get("ctlLineWidthOrFillModeHeader").innerHTML = showfillmode ? "Fill Style" : "Width (inches)";

    // we always set the contents of the fill width
    var elwidth = Dom.get("ctlLineWidth");
    elwidth.value = linewidthvalue;
    elwidth.disabled = disablelinewidth;
    elwidth.readonly = disablelinewidth;
    
    if (showfillmode) // hide linewidth and show fill mode
    {
        m_modFeatureLineWidth.hide();

        m_chkbxFillMode.set("checked", fillmodevalue, true);
        m_modFeatureFillMode.render();
        m_modFeatureFillMode.show();

    }
    else // hide fill mode and show line width
    {
        m_modFeatureFillMode.hide();
        m_modFeatureLineWidth.render();
        m_modFeatureLineWidth.show();
    }

    // hide the texture picker if we have a boundary-only property showing
    //Dom.get('textureaccordian').style.display = (showfillmode && (type < 256) && fillmodevalue) ? "none" : "block";
}

function onClickTextureFillMode(e)
{
    this.blur();

    var fillmode = e.newValue ? "B" : "";
    var activefeature = getActiveFeature();
    assignAttribute(activefeature, "fill_mode", fillmode, { 'fill_mode': fillmode });

    if (!activefeature.isPhantom && activefeature.state != OpenLayers.State.INSERT)
    {
        // set feature state to modified (unless it's a new object)
        activefeature.state = OpenLayers.State.UPDATE;
        activefeature.style = m_styleModified;
        activefeature.renderIntent = "modified";
        activefeature.layer.drawFeature(activefeature);
        indicateFeatureState(FEATURESTATE.Modified);
    }
    showFeatureLineWidthOrFillMode(activefeature);
}

function onChangeLineWidth()
{
    var width = parseFloat(Dom.get("ctlLineWidth").value) / 40; // rough conversion inches to 3395 units

    var activefeature = getActiveFeature();
    assignAttribute(activefeature, "width", width, { 'width': width });

    if (activefeature.state != OpenLayers.State.INSERT)
    {
        // set feature state to modified (unless it's a new object)
        activefeature.state = OpenLayers.State.UPDATE;
        activefeature.style = m_styleModified;
        activefeature.renderIntent = "modified";
        activefeature.layer.drawFeature(activefeature);
        indicateFeatureState(FEATURESTATE.Modified);
    }
}

/**************************
Feature shading indication and event handlers
**************************/
function showFeatureTextureShading(feature)
{
    var disabled = true;
    var curtexture = getFeatureTexture(feature);
    var shadingvalue = 0;

    if (curtexture)
    {
        var type = getTypeCodeFromFeature(feature);

        if (isDetail(type)) // only show shading for details
        {
            shadingvalue = (feature.attributes.shading ? feature.attributes.shading : 0);
            disabled = false;
        }
    }

    var sliderval = (shadingvalue * 6) / 15;
    m_sldrFeatureTextureShading.setValue(sliderval, true, true, true);

    var el = Dom.get("lblCurrentShading");
    if (disabled)
    {
        el.innerHTML = "n/a";
        m_sldrFeatureTextureShading.lock();
    }
    else
    {
        var shadinglabel = (0 == shadingvalue) ? "none" : ((shadingvalue > 0 ? "+" : "") + shadingvalue.toString() + "%");
        var oSliderEl = Dom.get("slider-bg");
        oSliderEl.title = "Shading = " + shadinglabel;
        el.innerHTML = shadinglabel;
        m_sldrFeatureTextureShading.unlock();
    }
}

function onChangeFeatureTextureShading(shadingvalue)
{
    var activefeature = getActiveFeature();
    assignAttribute(activefeature, "shading", shadingvalue, { 'shading': shadingvalue });

    if (activefeature.state != OpenLayers.State.INSERT)
    {
        // set feature state to modified (unless it's a new object)
        activefeature.state = OpenLayers.State.UPDATE;
        activefeature.style = m_styleModified;
        activefeature.renderIntent = "modified";
        activefeature.layer.drawFeature(activefeature);
        indicateFeatureState(FEATURESTATE.Modified);
    }
}

function getNameFromAttributes(attributes)
{
    var name = "";
    if (attributes.fullname)
    {
        name = attributes.fullname;
    }
    return name;
}

function getLidFromAttributes(attributes, nullvalue)
{
    var lid = "";

    if (attributes)
    {
        if (attributes.tlid)
        {
            lid = attributes.tlid;
        }
        else if (attributes.lid)
        {
            lid = attributes.lid;
        }
        if (!lid.length && nullvalue)
        {
            lid = nullvalue;
        }
    }
    return lid;
}

function assignAttribute(feature, name, value, obj)
{
    if (!feature.attributes || feature.attributes[name] == undefined)
    {
        // add attribute
        feature.attributes = OpenLayers.Util.extend(obj, feature.attributes);
    }
    else
    {
        feature.attributes[name] = value;
    }
}

function onLidNameSet(name)
{
    // refresh status
    updateStatusBar(getActiveFeature(true));
    // 'return to article' functionality should work because we go by lid
}

function isProp(featuretype)
{
    if (featuretype > 0 && featuretype < 256) return true;
    return false;
}

function isCampus(featuretype)
{
    if (featuretype > 0 && featuretype < 160) return true;
    return false;
}

function isLine(featuretype)
{
    if (featuretype >= 65536 && featuretype <= 8323072) return true;
    return false;
}

function isRoad(featuretype)
{
    if (featuretype < 0) return true;
    return false;
}

function isDetail(featuretype)
{
    if ((featuretype >= 256 && featuretype <= 32512) || (featuretype >= 65536 && featuretype <= 8323072)) return true;
    return false;
}

function isShape(featuretype)
{
    if ((featuretype >= 32500 && featuretype <= 32511) || (featuretype == 8323071)) return true;
    return false;
}

function isFreehandPoly(featuretype)
{
    if (featuretype >= 256 && featuretype <= 32499) return true;
    return false;
}

function isFreehandLine(featuretype)
{
    if (featuretype >= 65536 && featuretype <= 8323000) return true;
    return false;
}

function isMeta(featuretype)
{
    if (featuretype >= 32600 && featuretype <= 32999) return true;
    return false; 
}

function onSearchResultsEdit(type, args)
{
    var data = args[0];

    m_layerEdit.destroyFeatures();

    if ((data.feature_type >= 64 && data.feature_type != 160) || data.feature_type == -2130706432)
    {
        // should be able to find this by lid, call geoserver
        m_layerEdit.loadFeaturesFromLID(data.lid, { typename: 'feature_poly', sn: Math.round(Math.random() * 1000000) });
        OpenLayers.Util.extend(data, { selectable: true });
    }
    else if (data.feature_type < 0)
    {
        // TODO
        //m_layerEdit.loadFeaturesFromLID(data.lid, { typename: 'roads' });
    }
    // zoom to extent
    m_Map.zoomToExtent(new OpenLayers.Bounds.fromString(data.BBOX));

    return false; // we stop event processing chaing when edit is active
}
