///////////////////////////////////////////////////////////////
//
//  Script: Service Search
//  Version: 2.4
//  Author: Craig Nelson / Classic Labs Development
//
//  Features: 
//    - can accept query strings
//    - can be used on an external domain
//    - more comprehensive search capabilities such as
//      being able to search by city, city and state
//      (combined) or state
//    - suggestions for services
//    - html can be passed in as a template
//    - page numbers within url hash for managing back 
//      navigation and providing unique urls for any page
//      within a paged set
//

    var Ascribe = Ascribe || {};
  
    Ascribe.Search = function (config) {
	    // scope adjustment
	    var that = this;
    
        // data
        this.projects = config.projects || null;
    
        // elements
        this.searchFields = config.searchFields || $$(".search-field");
        this.searchFieldLocation = config.searchFieldLocation || $("search-location");
        this.searchFieldService = config.searchFieldService || $("search-service");
        this.groupDropDownHitSpot = config.groupDropDownHitSpot || $("groups-dropdown");
        this.arrow = config.arrow || $("arrow");
        this.dropdown = config.dropdown || $("groups");
        this.groups = config.groups || $$("#groups li");
        this.selectedGroup = config.selectedGroup || $("selected-group");
        this.searchSubmit = config.searchSubmit || $("search-submit");
        this.projectsContainer = config.projectsContainer || $("projects-container");
        this.pagesContainer = config.pagesContainer || $("pages-container");
    
        // ids
        this.pagesListID = config.pagesListID || "pages";
        this.serviceSuggestionsContainer = config.serviceSuggestionsContainer || "service-suggestions-container";
        this.serviceSuggestions = config.serviceSuggestions || "service-suggestions";
    
        // classnames
        this.activePageClassName = config.activePageClassName || "active";
        this.cycleClassNames1 = config.cycleClassNames1 || "odd";
        this.cycleClassNames2 = config.cycleClassNames2 || "even";
        this.dropDownClassName = config.dropDownClassName || "dropdown";
    
        // projectSearch() configs
        this.searchFieldLocationInitialValue = config.searchFieldLocationInitialValue || "Building Location (City/State)";
        this.searchFieldServiceInitialValue = config.searchFieldServiceInitialValue || "Service (ex. Architectural)";
        this.groupLabel = config.groupLabel || "Project Group";
        this.searchFieldLocation.value = this.searchFieldLocationInitialValue;
        this.searchFieldService.value = this.searchFieldServiceInitialValue;
        this.searchType;
        this.activeSearch = false; // is there an active search being performed?
        this.queryStr; // query string
        this.portfolioURL = config.portfolioURL || null;
        this.fieldFontColor = config.fieldFontColor || "#888888";
        this.fieldFontColorActive = config.fieldFontColorActive || "#000000";
        this.primaryRoles = config.primaryRoles || null;
    
        // projectPaging() configs
        this.projectsPerPage = config.projectsPerPage || 12; // number of results to display per page
    
        // misc configs
        this.filterNoMarks = (config.filterNoMarks !== "undefined" && config.filterNoMarks === false) ? false : true;
    
        // html template configs
        // general projects html
        this.generalProjectsOpeningTag = config.generalProjectsOpeningTag || '<ul id="projects" class="portfolio">';
        this.generalProjectsTemplate = config.generalProjectsTemplate || 
        '<li>' +
          '<a href="{projectPath}" title="View Project"><img src="{projectThumbnailPath}" alt="" /></a>' +
          '<h3>{projectTitle}</h3>' +
        '</li>';
        this.generalProjectsClosingTag = config.generalProjectsClosingTag || '</ul><!-- #projects -->';
    
        // services projects html
        this.servicesProjectsOpeningTag = config.servicesProjectsOpeningTag || '<ul id="projects" class="portfolio services">';
        this.servicesProjectsTemplate = config.servicesProjectsTemplate || 
        '<li class="results">' +
          '<a href="{projectPath}"><img src="{projectThumbnailPath}" alt="" /></a>' +
          '<a class="view" href="{projectPath}">View Project</a>' +
          '<h3>{projectTitle}</h3>' +
          '<p class="location">{projectCity} <abbr title="{projectFullState}">{projectState}</abbr> {projectZip}</p>' +
          '<ul class="companies">';
        this.servicesCompaniesTemplate = config.servicesCompaniesTemplate || 
        '<li>' +
          '<h4 class="company">' +
            '<a href="{companyPortfolioPath}" target="_blank">{companyName}</a>' +
          '</h4>' +
          '<div class="services"><span>Services provided:</span> {services}</div>' +
        '</li>';
        this.servicesCompaniesClosingTag = config.servicesCompaniesClosingTag || '</ul><!-- .companies --></li>';
        this.servicesProjectsClosingTag = config.servicesProjectsClosingTag || '</ul><!-- #projects -->';
    
        // methods
        this.projectSearch = function (location, service) {
            var results = []; // search results
            var roles = ""; // holds primary and/or additional roles
            var matchFound = false; // so we can output no match message
            var cityState;
            var cityFullState;
            location = location.toLowerCase().stripTags().escapeHTML().replace(/\W+/g, " ").replace(/\s+/g, " "); // escape query to match entities in raw project data. also, remove any non-word characters (ex: commas) and trim any inner white space to single space
            service = service.toLowerCase().stripTags().escapeHTML(); // escape query to match entities in raw project data.
            this.searchType = ""; // reset on each search

            this.projects.each(function (project) {
                cityState = project.projectCity + " " + project.projectState;
                cityFullState = project.projectCity + " " + project.projectFullState;
                
                if (!location.blank() && service.blank() && location !== that.searchFieldLocationInitialValue.toLowerCase()) {
                    if (cityState.toLowerCase().search(location) !== -1 || cityFullState.toLowerCase().search(location) !== -1) {
                        matchFound = true;
                        results.push(project);
                    }
                } // location query
                
                if (!service.blank() && service !== that.searchFieldServiceInitialValue.toLowerCase()) {
                    that.searchType = "service";
                    project.serviceHits = []; // create on first search. clear on each following search
                    
                    project["projectParticipants"].each(function (participant) {
                        var roleMatchFound = false; // our push below is inside each iteration. so we don't push on each iteration
            
                        if (participant.companyRole.toLowerCase().search(service) !== -1 || participant.companyAdditionalRole.toLowerCase().search(service) !== -1) {
                            matchFound = true;
                            roleMatchFound = true;
                            roles = (participant.companyAdditionalRole.blank()) ? participant.companyRole : participant.companyRole + ", " + participant.companyAdditionalRole;
                        }
                        
                        if (roleMatchFound) {
                            project.serviceHits.push({"companyName": participant.companyName, "services": roles, "companyPortfolioPath": participant.companyPortfolioPath});
                            results.push(project);
                        }
                    }); // project["projectParticipants"].each(function (participant)
                    
                    if (!location.blank() && location !== that.searchFieldLocationInitialValue.toLowerCase()) {
                        if ((cityState + " " + cityFullState).toLowerCase().search(location) === -1) {
                            results = results.without(project);
                            
                            if (results.length === 0) {
                                matchFound = false; // since we are filtering out projects that don't match the location search, all projects will be removed from results if there is no match
                            }
                        }
                    }
                } // service query
            }); // this.projects.each

            results = this.removeDuplicates(results, "projectPath"); // remove duplicate projects
            
            if (this.filterNoMarks) {
                results = this.removeNoMarks(results); // remove all projects that have no marks after a search. because we are always searching our complete data source.
                if (results.length === 0) {
                    matchFound = false;
                }
            }

            if (matchFound) {
                this.activeSearch = true;
                this.projectPaging(results);
            }
            else {
                this.pagesContainer.style.display = "none";
                this.projectsContainer.update('<p>Your search did not match any projects.</p>');
            }
            
            return;
        }; // projectSearch()
    
        this.generateProjectsHTML = function (offset, set, aProjects) {
            var output = this.generalProjectsOpeningTag;
            var classnames;
            
            for (var i=offset; i<set; i++) {
                output += this.generalProjectsTemplate.supplant(aProjects[i]);
            }
            
            output += this.generalProjectsClosingTag;
            output = output.replace(/<li class="\w*"><div class="mask">/g, function () {
                classnames = (classnames == that.cycleClassNames2) ? that.cycleClassNames1 : that.cycleClassNames2;
                return '<li class="' + classnames + '"><div class="mask">';
            });
            
            return output;
        }; // generateProjectsHTML()
    
        this.generateServicesProjectsHTML = function (offset, set, aProjects) {
            var output = this.servicesProjectsOpeningTag;
            var classnames;
            
            for (var i=offset; i<set; i++) {
                output += this.servicesProjectsTemplate.supplant(aProjects[i]);
                
                for (var j=0; j<aProjects[i].serviceHits.length; j++) {
                    output += this.servicesCompaniesTemplate.supplant(aProjects[i].serviceHits[j]);
                }
                
                output += this.servicesCompaniesClosingTag;
            }
            
            output += this.servicesProjectsClosingTag;
            output = output.replace(/<li class="\w*">/g, function () {
                classnames = (classnames == that.cycleClassNames2) ? that.cycleClassNames1 : that.cycleClassNames2;
                return '<li class="results ' + classnames + '">';
            });
            
            return output;
        }; // generateServicesProjectsHTML()
    
        this.projectPaging = function (aProjects, page, activePaging) {
            var projectsHTML = '';
            var pagesOutput = '';
            var total = aProjects.length;
            var limit = this.projectsPerPage;
            var page = page || 1;
            var pages = Math.ceil(total / limit);
            var offset = (page * limit - limit);
            var pageNumberFromURL = (window.location.href.match(/#\w+-\d+$/)) ? window.location.href.replace(/.+(\d)+$/, "$1") : false;
            pageNumberFromURL = (pageNumberFromURL <= pages) ? pageNumberFromURL : false;
            
            if (pageNumberFromURL) {
                offset = (pageNumberFromURL * limit - limit);
            }
            
            var set = limit + offset;
            
            if (set > total) {
                set = total; // so the last iteration of our forloop will be set correctly
            }
            
            if (this.activeSearch) {
                pagesOutput = ''; // if there is a search being performed, clear previous pages
            }

            if (total > limit) { // only show paging if there is more than one page
                if (!activePaging || this.activeSearch) { // only write pages html once on initial page load and once per search
                    pagesOutput += '<h5>Pages &#187;</h5>';
                    pagesOutput += '<ul id="' + this.pagesListID + '">';
                    
                    for (var i=1; i<pages + 1; i++) {
                        pagesOutput += '<li><a href="#page-' + i + '">' + i + '</a></li>';
                    }
                    
                    pagesOutput += '</ul><!-- #pages -->';
                    this.pagesContainer.style.display = "block";
                    this.pagesContainer.update(pagesOutput);

                    // onclick events for pages list
                    var pageLinks = $$("#" + this.pagesListID + " li a");
                    
                    for (var j=0; j<pageLinks.length; j++) {
                        if (!pageNumberFromURL && j === 0) {
                            pageLinks[j].className = this.activePageClassName; // set first page to active when script is first run
                        }
                        
                        if (pageNumberFromURL) {
                            pageLinks[pageNumberFromURL - 1].className = this.activePageClassName;
                        }
                        
                        Event.observe(pageLinks[j], "click", function (event) {
                            for (var k=0; k<pageLinks.length; k++) {
                                pageLinks[k].className = "";
                            }
                            
                            this.className = that.activePageClassName;
                            window.location.href = window.location.href.replace(/#\w+-\d+$/, "") + "#page-" + this.href.replace(/.+(\d)+$/, "$1");
                            this.blur();
                            that.activeSearch = false;
                            that.projectPaging(aProjects, this.innerHTML, true);
                            event.preventDefault();
                        });
                    }
                }
            }
            else {
                this.pagesContainer.style.display = "none";
            }
      
            // output scenarios
            projectsHTML = (this.searchType === "service") ? this.generateServicesProjectsHTML(offset, set, aProjects) : this.generateProjectsHTML(offset, set, aProjects);
            // --output scenarios
      
            this.projectsContainer.update(projectsHTML); // output HTML
            
            return;
        }; // projectPaging()
    
        this.removeDuplicates = function (collection, key) {
            ///////////////////////////////////////////////
            //  receives array/collection and returns new collection without duplicates.
            //  NOTES: method can iterate over a numerical array or an array of objects.
            //    - key is any key from the array (if the array is an array of objects).
            //    - example: checking to see if there are any duplicate project paths because project paths should always be unique
            var uniqueArray = [];
            var lastValue;

            // sort by projectPath so duplicates are removed properly
            collection = (key) ? collection.sortBy(function (item) { return item.projectPath }) : collection.sort();

            for (var i=0; i<collection.length; i++) {
                var currentValue = (key) ? collection[i][key] : collection[i]; // if key was passed, array of objects, otherwise, numerical array
                
                if (currentValue != lastValue) {
                    uniqueArray.push(collection[i]);
                }
                
                lastValue = currentValue;
            }
            
            return uniqueArray;
        }; // removeDuplicates()
    
        this.parseQueryString = function (url) {
            return (window.location.href.match(/\?/g)) ? window.location.href.toQueryParams() : false;
        }; // this.parseQueryString()
    
        this.initiateSearch = function (event) {
            ///////////////////////////////////////////
            //  this method is an event handler which is why we are using that instead of this.
            var location = (that.searchFieldLocation.value !== that.searchFieldLocationInitialValue) ? that.searchFieldLocation.value.trim() : "";
            var service = (that.searchFieldService.value !== that.searchFieldServiceInitialValue) ? that.searchFieldService.value.trim() : "";
            var searchGroup = that.selectedGroup.innerHTML;
            
            if (searchGroup === that.groupLabel) {
                alert(that.groupLabel + " is required");
                return;
            }
            else {
                // regex for project group urls
                searchGroup = searchGroup.trim();
                searchGroup = searchGroup.replace(/_+/g, " ");
                searchGroup = searchGroup.replace(/\s+/g, "-");
                searchGroup = searchGroup.replace(/&.+;/g, ""); // remove any html entities
                searchGroup = searchGroup.replace(/(\w)([^a-z0-9-_])(\w)/gi, "$1$3");
                searchGroup = searchGroup.replace(/(-*)(\W)(-*)/g, "-");
                searchGroup = searchGroup.toLowerCase();
                window.location = that.portfolioURL + searchGroup + "/?loc=" + encodeURI(location) + "&svc=" + encodeURI(service);
                return;
            }
            
            that.projectSearch(location, service);
            
            return;
        }; // initiateSearch()

        this.showSuggestions = function (value) {
            var regEx = new RegExp("^" + value.toLowerCase().trim());
            var suggestions = [];
            var suggestionsLength;
            var html = '';
            
            if (this.primaryRoles !== null) {
                this.primaryRoles.each(function (item) {
                    if (!value.blank() && item.toLowerCase().search(regEx) !== -1) {
                        suggestions.push(item);
                    }
                });
            }
            
            suggestionsLength = (suggestions.length < 7) ? suggestions.length : 7;
            
            if (suggestionsLength !== 0) {
                if (!$(this.serviceSuggestionsContainer).visible()) {
                    $(this.serviceSuggestionsContainer).show();
                }
                
                html = '<ul id="' + this.serviceSuggestions + '" class="' + this.dropDownClassName + '">';
                
                for (var i=0; i<suggestionsLength; i++) {
                    html += '<li>' + suggestions[i] + '</li>';
                }
                
                html += '</ul>';
                
                $(this.serviceSuggestionsContainer).update(html);
                
                $$("#" + this.serviceSuggestions + " li").each(function (e) {
                    Event.observe(e, "click", function (event) {
                        that.searchFieldService.value = this.innerHTML;
                        $(that.serviceSuggestionsContainer).hide();
                    });
                });
            }
            else {
                $(this.serviceSuggestionsContainer).hide();
            }
            
            return;
        }; // showSuggestions()

        // events
        Event.observe(this.searchSubmit, "click", this.initiateSearch);
    
        this.searchFields.each(function (e) {
            Event.observe(e, "focus", function (event) {
                if (this.value === that.searchFieldLocationInitialValue || this.value === that.searchFieldServiceInitialValue) {
                    this.value = "";
                    this.style.color = that.fieldFontColorActive;
                }
            });
            
            Event.observe(e, "blur", function (event) {
                if (this.value.blank()) {
                    this.value = (this.id === that.searchFieldLocation.id) ? that.searchFieldLocationInitialValue : that.searchFieldServiceInitialValue;
                    this.style.color = that.fieldFontColor;
                }
            });
        });
    
        Event.observe(this.searchFieldLocation, "keypress", function (event) { // set search on pressing enter key
            if (event.keyCode === 13) {
                that.initiateSearch();
                event.preventDefault();
                return;
            }
        });
        
        Event.observe(this.searchFieldService, "keypress", function (event) { // set search on pressing enter key
            if (event.keyCode === 13) {
                that.initiateSearch();
                event.preventDefault();
                return;
            }
        });
        
        Event.observe(this.groupDropDownHitSpot, "click", function (event) {
            var el = Event.element(event);
            
            if (el.descendantOf(that.groupDropDownHitSpot)) {
                (that.dropdown.visible()) ? that.dropdown.hide() : that.dropdown.show();
            }
            else {
                (that.dropdown.visible()) ? that.dropdown.hide() : that.dropdown.show();
            }
        });
        
        Event.observe(document.body, "click", function (event) {
            var el = Event.element(event);
            
            if (el != that.groupDropDownHitSpot && el != that.arrow && el != that.selectedGroup) {
                if (that.dropdown.visible()) {
                    that.dropdown.hide();
                }
                if ($(that.serviceSuggestions) && $(that.serviceSuggestions).visible()) {
                    $(that.serviceSuggestionsContainer).hide();
                }
            }
        });
        
        this.groups.each(function (e) {
            Event.observe(e, "click", function (event) {
                that.selectedGroup.style.color = that.fieldFontColorActive;
                that.selectedGroup.innerHTML = this.innerHTML;
            });
        });
        
        Event.observe(this.searchFieldService, "keyup", function (event) {
            that.showSuggestions(this.value);
        });
    
        this.queryStr = this.parseQueryString(window.location.href); // check for query string
        var loc = (this.queryStr.loc) ? decodeURI(this.queryStr.loc) : "";
        var svc = (this.queryStr.svc) ? decodeURI(this.queryStr.svc) : "";
    
        if (loc != "" || svc != "") {
            this.projectSearch(loc, svc);
            this.searchFieldLocation.value = (loc !== "") ? loc : this.searchFieldLocationInitialValue;
            this.searchFieldService.value = (svc !== "") ? svc : this.searchFieldServiceInitialValue;
            this.queryStr = null;
        }
        else {
            if (this.projects !== null) {
                if (this.filterNoMarks) {
                    this.projects = this.removeNoMarks(this.projects); // remove all projects that have no marks
                }
                this.projectPaging(this.projects); // initial paging
            }
        }
    
        this.searchFieldLocation.style.color = (this.searchFieldLocation.value === this.searchFieldLocationInitialValue) ? this.fieldFontColor : this.fieldFontColorActive;
        this.searchFieldService.style.color = (this.searchFieldService.value === this.searchFieldServiceInitialValue) ? this.fieldFontColor : this.fieldFontColorActive;
        
        if (this.selectedGroup.innerHTML !== this.groupLabel) {
            this.selectedGroup.style.color = this.fieldFontColorActive;
        }
    
        return;
    }; // constructor
  
    Ascribe.Search.prototype = {
        removeNoMarks: function (aProjects) {
            ///////////////////////////////////////////
            //  removes all projects from the projects collection that have no marks.
            //  returns new collection/array.
            aProjects.each(function (item) {
                if (item.projectParticipants.length === 0) {
                    aProjects = aProjects.without(item);
                }
            });
            
            return aProjects;
        } // removeNoMarks()
    }; // Ascribe.Search.prototype
  
    String.prototype.supplant = function (o) {
        return this.replace(/{([^{}]*)}/g, function (match, submatch, offset, originalStr) {
            var r = o[submatch];
            return typeof r === 'string' ? (r.blank()) ? "" : r : match;
        });
    }; // String.prototype.supplant
  
    String.prototype.trim = function () {
        return this.replace(/^\s+|\s+$/, "");
    }; // String.prototype.trim
