///////////////////////////////////////////////////////////////
//
//  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 replace inner white space with single spaces
      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
    
    if (this.queryStr) {
      var loc = (this.queryStr.loc) ? decodeURI(this.queryStr.loc) : "";
      var svc = (this.queryStr.svc) ? decodeURI(this.queryStr.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