///////////////////////////////////////////////////////////////
//
//  Script: Service Search
//  Version: 1.0
//  Author: Craig Nelson / Classic Labs
//
//  Notes: all in one script that can accept query strings.
//  can be used witin an integaration and an external domain
//

  var Ascribe = Ascribe || {};
  
  Ascribe.Search = function (config) {
		// scope adjustment
		var that = this;
    
    // data
    this.projects = config.projects || null;
    
    // elements
    this.searchSubmit = config.searchSubmit || "search-submit";
    this.searchFields = config.searchFields || $$(".search-field");
    this.searchFieldGroup = config.searchFieldGroup || "search-group";
    this.searchFieldGeneral = config.searchFieldGeneral || "search-general";
    this.searchFieldLocation = config.searchFieldLocation || "search-location";
    this.generalSearchSelector = config.generalSearchSelector || "general-search";
    this.companySearchSelector = config.companySearchSelector || "company-search";
    this.serviceSearchSelector = config.serviceSearchSelector || "service-search";
    this.searchLocationContainer = config.searchLocationContainer || "search-location-container";
    this.searchTypeElements = config.searchTypeElements || $$(".search-type"); // so we can iterate to get search type
    this.helpToggleElements = config.helpToggleElements || $$(".help-toggle"); // so multiple elements can toggle the help container
    this.helpContainer = config.helpContainer || "help-container";
    this.companiesContainer = config.companiesContainer || "companies-container";
    this.companiesSearchResults = config.companiesSearchResults || "companies-search-results";
    this.projectsContainer = config.projectsContainer || "projects-container";
    this.pagesContainer = config.pagesContainer || "pages-container";
    this.pagesList = config.pagesList || "pages";
    
    // classnames
    this.activeSearchClassname = config.activeSearchClassname || "search-active";
    this.activePageClassname = config.activePageClassname || "active";
    
    // projectSearch() configs
    this.generalSearchStr; // holds the general search string
    this.locationSearchStr; // holds the location search string for the service search
    this.activeSearch = false; // is there an active search being performed?
    this.searchType = "general"; // search type. default is general. set from event listener
    this.lastSearchTypePerformed; // what type of search was last performed? used to control paging output. is set within projectSearch() so we know a search was performed
    this.searchKeys = config.searchKeys || ["projectTitle", "projectCity", "projectZip", "projectParticipants"];
    this.companyClicked = false; // so if we have two companies like company 1 and company 10, company 1 can be clicked without rewriting the output for the companies search results. otherwise we would never be able to click company 1 because it would always return two companies
    this.projectGroupText = config.projectGroupText || "projects.";
    this.queryStr; // query string
    this.portfolioURL = config.portfolioURL || null;
    
    // projectPaging() configs
    this.projectsPerPage = config.projectsPerPage || 12; // number of results to display per page
    
    // misc configs
    this.companyWebsiteLinkText = config.companyWebsiteLinkText || "View Website";
    this.companyPortfolioLinkText = config.companyPortfolioLinkText || "View Portfolio";
    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} {projectState} {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 -->';
    
    // company banner html
    this.companyBannerTemplate = config.companyBannerTemplate ||
    '<h4>{companyName}</h4>' +
    '<img id="company-logo" src="{companyLogoPath}" alt="Logo for {companyName}" />' +
    '<div class="adr">' +
      '<span id="company-street">{companyStreet}</span> ' +
      '<span>{companyCity}</span> ' +
      '<abbr title="{companyFullState}">{companyState}</abbr> ' +
      '<span>{companyZip}</span> ' +
      '<span id="company-phone">{companyPhone}</span>' +
    '</div>' +
    '<p>{companyName} is featured on {noOfProjects} {projectGroupText}</p>' +
    '<div id="company-links">' +
      '<span><a href="{companyWebsitePath}" target="_blank">{companyWebsiteLinkText}</a></span>&nbsp;&nbsp;|&nbsp;&nbsp;' +
      '<span><a href="{companyPortfolioPath}" target="_blank">{companyPortfolioLinkText}</a></span>' +
    '</div>';
    
    // methods
    this.projectSearch = function (general, location) {
      var results = []; // search results
      var participants = []; // search results/data for participants so we can check if there is more than one match for a company name.
      var roles = ""; // holds primary and/or additional roles
      var primaryRoleMatch = false; // so we can add a comma to our roles string between primary and additional roles
      var matchFound = false; // so we can output no match message
      this.generalSearchStr = general.toLowerCase().stripTags().escapeHTML(); // convert literal characters to entities so search string will match entities in raw project data. this is because our raw data has already been escaped
      this.locationSearchStr = (location) ? location.toLowerCase().stripTags().escapeHTML() : ""; // location is optional param
      var noMatchFoundStr = this.generalSearchStr; // so we can show dynamic no match messages
      this.lastSearchTypePerformed = this.searchType;

      this.projects.each(function (project) {
        if (!that.generalSearchStr.blank()) {
          that.searchKeys.each(function (key) {
            if (that.searchType === "general" && typeof(project[key]) === "string" && project[key].toLowerCase().search(that.generalSearchStr) !== -1) {
              matchFound = true;
              results.push(project);
            } // if (that.searchType === "project"
            if (that.searchType === "company" && project[key] instanceof Array) {
              project[key].each(function (participant) {
                if (participant.companyName.toLowerCase().search(that.generalSearchStr) !== -1) {
                  matchFound = true;
                  participants.push(participant);
                  results.push(project);
                }
              });
            } // if (that.searchType === "company"
            if (that.searchType === "service" && project[key] instanceof Array) {
              project.serviceHits = []; // create on first search. clear on each following search
              project[key].each(function (participant) {
                var roleMatchFound = false; // because our push below is outside of each search condition below. so we don't do a push on each iteration
                if (participant.companyRole.toLowerCase().search(that.generalSearchStr) !== -1) {
                  matchFound = true;
                  roleMatchFound = true;
                  primaryRoleMatch = true;
                  roles = participant.companyRole;
                }
                if (participant.companyAdditionalRole.toLowerCase().search(that.generalSearchStr) !== -1) {
                  matchFound = true;
                  roleMatchFound = true;
                  (primaryRoleMatch) ? roles += ", " + participant.companyAdditionalRole : roles = participant.companyAdditionalRole;
                }
                if (roleMatchFound) {
                  project.serviceHits.push({"companyName": participant.companyName, "services": roles, "companyPortfolioPath": participant.companyPortfolioPath});
                  results.push(project);
                }
              });
              if (!that.locationSearchStr.blank() && project.projectCity.toLowerCase().search(that.locationSearchStr) === -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
                }
              }
            } // if (that.searchType === "service"
          }); // that.searchKeys.each
        }
      }); // this.projects.each

      results = this.removeDuplicates(results, "projectPath"); // remove duplicate projects

      // hide the companies container in case the search is a project or service search and the companies container is already visible
      if ($(this.companiesContainer).visible()) {
        $(this.companiesContainer).style.display = "none";
      }

      // company search only
      if (this.searchType === "company") {
        participants = this.removeDuplicates(participants, "companyName"); // remove duplicate companies
        if (this.showMultipleCompanyResults(results, participants) == "multiple companies found") {
          return;
        }
      }

      if (matchFound) {
        this.activeSearch = true;
        this.projectPaging(results);
      }
      else {
        $(this.pagesContainer).style.display = "none";
        if (this.searchType === "service" && !this.locationSearchStr.blank()) {
          noMatchFoundStr += ' <span style="font-weight: normal;">in</span> ' + that.locationSearchStr;
        }
        $(this.projectsContainer).update('<p>No match found for <strong>' + noMatchFoundStr + '</strong></p><p><strong>Search Tips:</strong> Try a more generic search. Instead of searching for ABC &amp; Company, try searching just for ABC.');
      }
      return;
    }; // projectSearch()
    
    this.generateProjectsHTML = function (offset, set, aProjects) {
      var output = this.generalProjectsOpeningTag;
      for (var i=offset; i<set; i++) {
        output += this.generalProjectsTemplate.supplant(aProjects[i]);
      }
      output += this.generalProjectsClosingTag;
      return output;
    }; // generateProjectsHTML()
    
    this.generateServicesProjectsHTML = function (offset, set, aProjects) {
      var output = this.servicesProjectsOpeningTag;
      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;
      return output;
    }; // generateServicesProjectsHTML()
    
    this.showMultipleCompanyResults = function (results, companies) {
      var companiesMultipleResultsOutput = ''; // clear html for companies output on each call
      if (companies.length > 1 && !this.companyClicked) {
        companiesMultipleResultsOutput += '<p>We found multiple companies that matched your search. Please click the specific company you were looking for.</p>';
        companiesMultipleResultsOutput += '<ul id="' + this.companiesSearchResults + '">';
        companies.each(function (company) {
          companiesMultipleResultsOutput += '<li><a href="#">' + company.companyName + '</a></li>';
        });
        companiesMultipleResultsOutput += '</ul>';

        $(this.projectsContainer).update(companiesMultipleResultsOutput);
        $(this.pagesContainer).style.display = "none";

        // onclick events for companies results links
        var elements = $$("#" + this.companiesSearchResults + " li a");
        elements.each(function (e) {
          Event.observe(e, "click", function (event) {
            that.companyClicked = true;
            that.projectSearch(e.innerHTML.unescapeHTML()); // convert entities back to literal characters for search string
            event.preventDefault();
          });
        });
        return "multiple companies found";
      }
      this.showCompanyBanner(results, companies);
    }; // showMultipleCompanyResults()
    
    this.showCompanyBanner = function (results, companies) {
      var companiesOutput = ''; // clear html for companies output on each call
      if (this.companyClicked || companies.length === 1) {
        // modify/add a properties to the first company object before html output
        companies[0].companyLogoPath = companies[0].companyLogoPath || "http://assets.ascribehq.com/integrations/images/misc/blank_mark_logo.jpg";
        companies[0].companyPhone = (companies[0].companyPhone === "") ? "" : (companies[0].companyPhone.match(/^ph:/)) ? companies[0].companyPhone : "ph: " + companies[0].companyPhone;
        companies[0].noOfProjects = String(results.length);
        companies[0].projectGroupText = this.projectGroupText
        companies[0].companyWebsiteLinkText = this.companyWebsiteLinkText;
        companies[0].companyPortfolioLinkText = this.companyPortfolioLinkText;

        // generate html for company banner
        companiesOutput += this.companyBannerTemplate.supplant(companies[0]);

        // output html for company banner
        $(this.companiesContainer).update(companiesOutput);
        $(this.companiesContainer).appear({ duration: .3 });

        this.companyClicked = false;
      }
      return;
    }; // showCompanyBanner()
    
    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 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.pagesList + '">';
          for (var i=1; i<pages + 1; i++) {
            pagesOutput += '<li><a href="#">' + i + '</a></li>';
          }
          pagesOutput += '</ul><!-- #pages -->';
          $(this.pagesContainer).style.display = "block";
          $(this.pagesContainer).update(pagesOutput);

          // onclick events for pages list
          var pageLinks = $$("#" + this.pagesList + " li a");
          for (var j=0; j<pageLinks.length; j++) {
            if (j === 0) {
              pageLinks[j].className = this.activePageClassname; // set first page to active when script is first run
            }
            Event.observe(pageLinks[j], "click", function (event) {
              for (var k=0; k<pageLinks.length; k++) {
                pageLinks[k].className = "";
              }
              this.className = that.activePageClassname;
              this.blur();
              that.activeSearch = false;
              that.projectPaging(aProjects, this.innerHTML, true);
              event.preventDefault();
            });
          }
        }
      }
      else {
        $(this.pagesContainer).style.display = "none";
      }

      // output scenarios
      if (this.searchType === "service" && this.lastSearchTypePerformed === "service") {
        projectsHTML = this.generateServicesProjectsHTML(offset, set, aProjects); // build services project HTML
      }
      if (this.searchType !== "service" && this.lastSearchTypePerformed === "service") {
        projectsHTML = this.generateServicesProjectsHTML(offset, set, aProjects); // build services project HTML
      }
       if (this.searchType !== "service"  && this.lastSearchTypePerformed !== "service") {
        projectsHTML = this.generateProjectsHTML(offset, set, aProjects); // build general project HTML
      }
      // --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;

      if (this.searchType == "general" || this.searchType == "service") {
        // sort by projectPath to ensure duplicates are removed properly
        collection = (key) ? collection.sortBy(function (item) { return item.projectPath }) : collection.sort();
      }
      if (this.searchType == "company") {
        // sort by companyName to ensure duplicates are removed properly
        collection = (key) ? collection.sortBy(function (item) { return item.companyName }) : 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;
      }
      if (this.searchType != "company") {
        // sort array again by projectTitle to provide a more intuitive/alphabetized set of results
        uniqueArray = (key) ? uniqueArray.sortBy(function (item) { return item.projectTitle }) : uniqueArray.sort();
      }
      return uniqueArray;
    }; // removeDuplicates()
    
    this.toggleLocationField = function () {
      if (this.searchType === "service") {
        Effect.Appear(this.searchLocationContainer, "appear", { duration: 0.1 });
      }
      else if (this.searchType !== "service") {
        if ($(this.searchLocationContainer).visible()) {
          Effect.Fade(this.searchLocationContainer, "appear", { duration: 0.1 });
        }
      }
      return;
    }; // toggleLocationField()
    
    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 generalSearchStr = $(that.searchFieldGeneral).value;
      var locationSearchStr = $(that.searchFieldLocation).value;
      if ($(that.searchFieldGroup)) { // is this search coming from a parent domain (home page)
        var searchGroup = $(that.searchFieldGroup);
        if (searchGroup.value === "") {
          alert(searchGroup.previous().innerHTML + " is required");
          return;
        }
        else {
          // backup regex for project group urls
          //searchGroup = searchGroup.value.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, "-");
          window.location = that.portfolioURL + searchGroup.value + "/?sType=" + that.searchType + "&gen=" + generalSearchStr + "&loc=" + locationSearchStr;
          return;
        }
      }
      that.projectSearch(generalSearchStr, locationSearchStr);
      return;
    }; // initiateSearch()
    
    /*
    this.showSuggestions = function (element) {
      var suggestType = element.id.replace("search-", "");
      if (this.searchType == "service") {
        if (suggestType == "general") {
          this.projects.each(function (project) {
            project.projectParticipants.each(function (participant) {
              //console.log(participant.companyRole);
            });
          });
        }
        else {
          
        }
      }
      return;
    }; // showSuggestions()
    */
    
    // events
    this.searchTypeElements.each(function (e) { // get search type
      Event.observe(e, "click", function (event) {
        this.blur();
        that.searchType = e.id.replace("-search", "");
        that.searchTypeElements.each(function (el) {
          el.removeClassName(that.activeSearchClassname);
        });
        e.addClassName(that.activeSearchClassname)
        that.toggleLocationField(that.searchType);
        event.preventDefault();
      });
    });
    Event.observe(this.searchSubmit, "click", this.initiateSearch); // set search
    Event.observe(this.searchFieldGeneral, "keypress", function (event) { // set search on pressing enter key
      if (event.keyCode === 13) {
        that.initiateSearch();
        // prevent form submissions in case our search field is within a form, such as a form wrapper around content
        // this is because the default action when pressing enter in a text field is to submit the parent form
        event.preventDefault();
        return;
      }
    });
    Event.observe(this.searchFieldLocation, "keypress", function (event) { // set search on pressing enter key
      if (event.keyCode === 13) {
        that.initiateSearch();
        // prevent form submissions in case our search field is within a form, such as a form wrapper around content
        // this is because the default action when pressing enter in a text field is to submit the parent form
        event.preventDefault();
        return;
      }
    });
    this.helpToggleElements.each(function (e) {
      Event.observe(e, "click", function (event) {
        this.blur();
        Effect.toggle(that.helpContainer, "blind", { duration: 0.2 });
        event.preventDefault();
      });
    });
    /*
    this.searchFields.each(function (e) {
      Event.observe(e, "keyup", function (event) {
        that.showSuggestions(e);
      });
    });
    */
    
    this.queryStr = this.parseQueryString(window.location.href); // check for query string
    
    if (this.queryStr) {
      this.searchType = this.queryStr.sType;
      this.projectSearch(this.queryStr.gen, this.queryStr.loc);
      $(that.searchFieldGeneral).value = (this.queryStr.gen !== "undefined") ? this.queryStr.gen : "";
      $(that.searchFieldLocation).value = (this.queryStr.loc !== "undefined") ? this.queryStr.loc : "";
      this.queryStr = null;
      this.searchTypeElements.each(function (el) {
        el.removeClassName(that.activeSearchClassname);
        if (el.id.replace("-search", "") === that.searchType) {
          el.addClassName(that.activeSearchClassname);
        }
      });
      this.toggleLocationField(this.searchType);
    }
    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
      }
    }
    
    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()
  }; // prototype
  
  String.prototype.supplant = function (o) {
    return this.replace(/{([^{}]*)}/g, function (match, submatch, offset, orig) {
      var r = o[submatch];
      return typeof r === 'string' ? r : match;
    });
  };
