package Opals::SolrIndex;

require Exporter;
@ISA       = qw(Exporter);
# Symbols to be exported by default
#@EXPORT    = qw(
#    opl_
#);
# Symbols to be exported on request
@EXPORT_OK = qw(
    slr_search
    slr_getSuggestion
    slr_getSimilarItem
    slr_buildSearchQuery
    slr_buildSearchQuery_notContain
    slr_buildSearchQuery_ARL
    slr_buildSNewItemQuery
    slr_buildSReviewItemQuery
    slr_updateIndexDir
    slr_updateIndexFile
    slr_delIndex

    slr_browseSubject_keyword
    slr_browseSubject_beginWith

    slr_sbjAuthority_search
    slr_browseAuthor_keyword
    slr_browseTitle_keyword

    slr_getARLMinMax_fieldVal
    slr_getLexileMinMax

    slr_getShelfList

    slr_updateIndex
    slr_eb_search
    slr_eb_buildSearchQuery

    slr_getHiliTems

    srl_buildQuery_datafield

    slr_getACList
);
# Version number
$VERSION   = 0.01;

use strict;
use Encode;

use Opals::Circulation qw(
    circ_getRecCircStatus_book
    circ_getRecCircStatus_ebook
);
use LWP::UserAgent;
use HTTP::Request::Common;
use URI::Escape;
use utf8;
use POSIX qw(
    ceil
);
use Opals::Utility qw(
    util_restoreLiteral
);
use XML::SAX;
use base qw( XML::SAX::Base );
use Opals::MarcXmlParser;

use Opals::Rating qw(
    cmntRating_getAvgRating
    cmntRating_numOfRating
);

use JSON;
my $dbh = Opals::Context->dbh();
my $barcodeType =Opals::Context->preference("barcodeType");

$| = 1;

my $srchFielMap={
      4=>'title',
   9004=>'title_exct',
      5=>'titleSeries',
      6=>'titleUniform',
      7=>'isbn',
      8=>'issn',
      9=>'controlNumberLC',
      12=>'rid',
      13=>'dewey',
      16=>'classificationLC',
      21=>'subject',
    9021=>'subject_exct',
      31=>'datePubSort',
      59=>'placePublication',
      63=>'note',
    9063=>'chapter',
    1003=>'author',
    1004=>'author_personal',
    9016=>'anywhere_exct',
    1016=>'anywhere',
    1018=>'namePublisher',
    5000=>'barcode',
    5001=>'callnumber',
    5002=>'medium',
    5006=>['title_sort','title_main'],
    #8008=>'location',
    8008=>'subfield_852_c',
    8001=>'subfield_852_2',
    8002=>'subfield_852_3',
    8003=>'subfield_852_6',
    8004=>'subfield_852_8',
    8005=>'subfield_852_9',
    8006=>'subfield_852_a',
    8007=>'subfield_852_b',
    8009=>'subfield_852_e',
    8010=>'subfield_852_f',
    8011=>'subfield_852_g',
    8012=>'subfield_852_h',
    8013=>'subfield_852_i',
    8014=>'subfield_852_j',
    8015=>'subfield_852_k',
    8016=>'subfield_852_l',
    8017=>'subfield_852_m',
    8018=>'subfield_852_n',
    8019=>'subfield_852_p',
    8020=>'subfield_852_q',
    8021=>'subfield_852_s',
    8022=>'subfield_852_t',
    8023=>'subfield_852_x',
    8024=>'subfield_852_z',
    8025=>'subfield_852_u',
    8026=>'sf_586a'

};
my $sortFielMap={
    1   =>'',
    4   =>'title_sort+<sortDir>,title_sort+<sortDir>,author_main+<sortDir>,callnumber_first+<sortDir>,datePubSort+<sortDir>,',
    1003=>'author_main+<sortDir>,title_sort+<sortDir>,callnumber_first+<sortDir>,datePubSort+<sortDir>,',
    5001=>'callnumber_first+<sortDir>,author_main+<sortDir>,title_sort+<sortDir>,datePubSort+<sortDir>,',
    '5001_lcc'=>'callNumSort_lcc+<sortDir>,author_main+<sortDir>,title_sort+<sortDir>,datePubSort+<sortDir>,',
    31  =>'datePubSort+<sortDir>,author_main+<sortDir>,title_sort+<sortDir>,callnumber_first+<sortDir>,',
    8008=>'location_first+<sortDir>,',
    9001=>'avgRating+desc,',
    9002=>'dateImport+<sortDir>,datePubSort+desc,',
    9003=>'lastReviewed+desc,datePubSort+desc,'

};
my @facetListOrder=qw(location language format callnumberPrefix dewey lccGrp subject author era genre bisac lexileMeasure fountasPinnell);
my $facetFieldMap={
                location=>"location",
                language=>"language",
                author  =>"author_facet",
                format  =>"format_facet",
                subject =>"subject_facet",
                callnumberPrefix  =>"callnumberPrefix",
                dewey   =>"dewey_facet",
                lccGrp  =>"lccGrp",
                era     =>"era",
                genre   =>"genre",
                bisac   =>"bisac",
                lexileMeasure  =>"lexileMeasure",
                fountasPinnell =>"fountasPinnell"
                };
#////////////////////////////////////////////////////////////////////////////
sub _buildQuery{
    my ($input,$filter) =@_;
    my $luceneQuery="";;
    my $iCount = 0;
    my ($sfOrder, $kw, $boolop,$sTerm);

    foreach my $cgiInput (keys %$input) {
        $iCount++ if ($cgiInput =~ m/^sf/);
    }
    for (my $i = 0; $i < $iCount; $i++) {
        $sfOrder= 'sf' . $i;
        $kw     = 'kw' . $i;
        $boolop = 'boolop' . $i;
            
        if ($input->{$kw}) {
            
            $sTerm=$input->{$kw};
            #$sTerm =lc($sTerm);

            # escapse lucene query special characters
            $sTerm =~ s/^\s*|\s*$//g;
            if($srchFielMap->{$input->{$sfOrder}} eq 'isbn'){
                    $sTerm =~ s/-//g;
            }
            elsif($srchFielMap->{$input->{$sfOrder}} !~ m/_exct$|^subject$|^barcode$/ && $input->{$sfOrder} ne '5006'){
                $sTerm =~ s/([\-\(\)\{\}\[\]\^\~\?\.\:\!\\])/ /g;
            }
            else{
                $sTerm =~ s/([\-\(\)\{\}\[\]\^\~\?\.\:\!\\])/\\$1/g;
            }
            #$sTerm =~ s/^\\"|\\"$/\"/g;
            
            $sTerm =~ s/%/%25/g;
            $sTerm =~ s/&/%26/g;
            $sTerm =~ s/[\+]/%2B/g;
            #/escape...

            $sTerm = _fixUnmatchQuot($sTerm);
            if ($sTerm ne '') {
                if($luceneQuery ne '') {
                    $luceneQuery .= " " . uc($input->{$boolop}) . " ";
                }
                if($input->{$sfOrder} eq '5006'){# seach title begin with
                    $sTerm =lc($sTerm);
                    $sTerm =~ s/[][(){},.:;!?<>]/ /g ;
                    $sTerm =~ s/ +/\ /g ;
                    $sTerm =~ s/ /\\ /g ;
                    if($sTerm !~ m/\*$/){
                        $sTerm .="*";
                    }
                }
                if($input->{$sfOrder} eq '5000'){
                    $sTerm =lc($sTerm);
                    $sTerm =~ s/ or / OR /g;
                }

                #$sTerm .="*";

                if(defined $srchFielMap->{$input->{$sfOrder}}  && ref($srchFielMap->{$input->{$sfOrder}}) eq 'ARRAY'){
                    
                    foreach my $fname(@{$srchFielMap->{$input->{$sfOrder}}}){
                        $luceneQuery .=" OR " if($luceneQuery ne '');
                        $luceneQuery .=  "$fname:($sTerm)";
                    }
                }
                else{
                    $luceneQuery .= $srchFielMap->{$input->{$sfOrder}} . ":($sTerm)";
                }
            }
        }
    }
    if(ref($filter->{'recType'}) eq "ARRAY" && scalar(@{$filter->{'recType'}})>0){
        my $rtq= "\"" . join("\" OR \"",@{$filter->{'recType'}}) . "\"";
        $luceneQuery = "($luceneQuery) AND format:($rtq)";
    }
    if(ref($filter->{'location'}) eq "ARRAY" && scalar(@{$filter->{'location'}})>0){
        my $rtq= "\"" . join("\" OR \"",@{$filter->{'location'}}) . "\"";
        $luceneQuery = "($luceneQuery) AND location:($rtq)";
    }

    if(defined $filter->{'prefix'} && $filter->{'prefix'} ne''){
        $luceneQuery = "($luceneQuery) AND callnumberPrefix:$filter->{'prefix'}";
    }
    return $luceneQuery;

}

#////////////////////////////////////////////////////////////////////////////
sub slr_buildSearchQuery{
    my ($input,$recTypeFilter) =@_;
    my $luceneQuery=_buildQuery($input,$recTypeFilter);
    my $filter="fq=db:main";

#add search filter
    foreach my $f( keys %{$facetFieldMap}){
        my $filterVal= $input->{$f . "Filter"};
           $filterVal =~ s/"|&quot;/\\"/g;
           $filterVal =~ s/&/%26/g;
        if(defined $filterVal && $filterVal ne ""){
            $filter .= encode("utf8","&fq=$f:\"$filterVal\"");
        }
    }
    my $arlFilter =_build_ARL_filter($input);
    if($arlFilter ne ''){
        $luceneQuery .= " AND " if($luceneQuery ne '');
        $luceneQuery .= "$arlFilter" ;
    }

#add facet search
#facet.limit : maximum number of constraint counts that should be returned for the facet fields. 
#A negative value means unlimited. Default=100
    #$luceneQuery .="&facet=true&facet.limit=10&facet.mincount=1";
    $luceneQuery .="&facet=true&facet.mincount=1";
    foreach my $f( keys %{$facetFieldMap}){
        my $filterVal= $input->{$f . "Filter"};
        if(!defined $filterVal || $filterVal eq ""){
            $luceneQuery .="&facet.field=" . $facetFieldMap->{$f};
        }
    }

   return "$filter&q=$luceneQuery";


}

#////////////////////////////////////////////////////////////////////////////
sub slr_eb_buildSearchQuery{
    my ($input,$recTypeFilter) =@_;
    my $luceneQuery=_buildQuery($input,$recTypeFilter);
    return "fq=bid:[1 TO *]&q=$luceneQuery";

}

#////////////////////////////////////////////////////////////////////////////
sub _build_ARL_filter{
    my ($input) =@_;
    my $ARL_filter="";
    my $lexileFrom      = toDecimal($input->{'lexileFrom'});
    my $lexileTo        = toDecimal($input->{'lexileTo'});
    my $lexilecode      = $input->{'lexileCode'};
    my $FPLevelFrom     = $input->{'FPLevelFrom'};
    my $FPLevelTo       = $input->{'FPLevelTo'};

    if($lexilecode ne ''){
        $ARL_filter .="lexileCode:$lexilecode";
    }
    $lexileFrom ="*" if($lexileFrom eq 'NaN');
    $lexileTo   ="*" if($lexileTo eq 'NaN');
    if($lexileFrom ne '*' || $lexileTo ne '*'){
        $ARL_filter .=" AND " if($ARL_filter ne '');
        $ARL_filter .= "lexileMeasure:[$lexileFrom TO $lexileTo]";
    }

    $FPLevelFrom ="*" if(!defined $FPLevelFrom || $FPLevelFrom eq '');
    $FPLevelFrom =~ s/\+/%2B/g ;
    $FPLevelTo   ="*" if(!defined $FPLevelTo   || $FPLevelTo eq '');
    $FPLevelTo   =~ s/\+/%2B/g ;

    if($FPLevelFrom ne '*' || $FPLevelTo ne '*'){
        $ARL_filter .=" AND " if($ARL_filter ne '');
        $ARL_filter .= "fountasPinnell:[$FPLevelFrom TO $FPLevelTo]";
    }
    my $program         = $input->{'program'};
    if($program ne ''){
        my $fPrefix="AR_";
        $fPrefix ="RC_" if($program =~ m/reading counts/i);
        $ARL_filter .=" AND " if($ARL_filter ne '');
        my $readingFrom     = toDecimal($input->{'readingLevelFrom'});
        my $readingTo       = toDecimal($input->{'readingLevelTo'}) ;
        my $pointValueFrom  = toDecimal($input->{'pointvalueFrom'});
        my $pointValueTo    = toDecimal($input->{'pointvalueTo'} );
        my $interestLevel   = $input->{'interestLevel'};
        
        $readingFrom        ="*" if($readingFrom eq 'NaN');
        $readingTo          ="*" if($readingTo   eq 'NaN');
        $pointValueFrom     ="*" if($pointValueFrom   eq 'NaN');
        $pointValueTo       ="*" if($pointValueTo   eq 'NaN');
         
        $ARL_filter .= "  (studyPrgm:\"$program\")";
        
        if($readingFrom ne '*' || $readingTo ne '*'){
            $ARL_filter .= " AND $fPrefix" . "readingLevel:[$readingFrom TO $readingTo]";
        }
        if($pointValueFrom ne '*' || $pointValueTo ne '*'){
            $ARL_filter .= " AND $fPrefix" . "pointValue:[$pointValueFrom TO $pointValueTo]";
        }
        $ARL_filter .= " AND $fPrefix" . "interestLevel:$interestLevel " if($interestLevel ne '');
    }
    return $ARL_filter;
}
#////////////////////////////////////////////////////////////////////////////
sub slr_buildSearchQuery_ARL{
    my ($input) =@_;

    #return "" if(!defined $input->{'program'} || $input->{'program'} eq '');
    my $luceneQuery=_buildQuery($input);
    #$luceneQuery .=" AND " if($luceneQuery ne '');
    my $arlFilter =_build_ARL_filter($input);
    if($arlFilter ne ''){
        $luceneQuery .= " AND " if($luceneQuery ne '');
        $luceneQuery .= "$arlFilter" ;
    }
   #add facet search
    #$luceneQuery .="&facet=true&facet.limit=10&facet.mincount=1";
    $luceneQuery .="&facet=true&facet.mincount=1";
    foreach my $f( keys %{$facetFieldMap}){
        my $filterVal= $input->{$f . "Filter"};
        if(!defined $filterVal || $filterVal eq ""){
            $luceneQuery .="&facet.field=" . $facetFieldMap->{$f};
        }
    }
    return "fq=db:main&q=$luceneQuery";

}
#////////////////////////////////////////////////////////////////////////////
sub slr_buildSearchQuery_notContain{
    my ($input) =@_;

    my $luceneQuery ="q=db:main AND subfield_852_p:[* TO *] AND  NOT " . $srchFielMap->{$input->{'sf0'}} . ":[* TO *]";
    return $luceneQuery;

}
#////////////////////////////////////////////////////////////////////////////
sub srl_buildQuery_datafield{
    my ($searchParam,$filter) =@_;
    my ($query,$queryNotCont)=("","");
    my $boolOp="AND"; 
    my @indMap=qw(C D E F G H I J K L);
    foreach my $p(@$searchParam){
        my $q ="";
        my $contain ="";
        if(defined $p->{"tag"} && $p->{"tag"} =~ /^\d\d\d$/ && $p->{'term'} ne ""){
            $contain =(defined $p->{"contain"} && $p->{"contain"} eq '0')?"-":"";
            my $arg="";
            my $fName="df_" . $p->{"tag"};
            if(defined $p->{"code"} && $p->{"code"} =~ /^\w$/){
                $fName="sf_" . $p->{"tag"} . $p->{"code"} ;
            }
            if(defined $p->{"ind1"} && $p->{"ind1"} =~ /^\d$/){
                $arg="INDA" . $indMap[$p->{"ind1"}];
            }
            if(defined $p->{"ind2"} && $p->{"ind2"} =~ /^\d$/){
                $arg .=" INDB" . $indMap[$p->{"ind2"}];
            }
            if(defined $p->{"term"} && $p->{"term"} ne ''){
                $arg .= " " . $p->{"term"};
            }
            #$arg="*" if($arg eq "");
            $arg =~ s/^\s+|\s+$//g;
            $arg = "*" if($arg eq '');
            if($arg =~ m/\*/){
                $q =$fName . ":($arg)";
            }
            else{
                $q =$fName . ":(\"$arg\"~400)";
            }
          }
        elsif(defined $p->{"field"} && $p->{"term"} ne ''){
            $q =$p->{"field"} . ":($p->{'term'})";
        }
        else{
            next;
        }
        if($contain eq '-'){
            $boolOp =$boolOp eq "AND"?"OR":"AND";
           if($queryNotCont ne ""){
                $queryNotCont = "($queryNotCont) $boolOp $q";
            }
            else{
                $queryNotCont =$q;
            }

        }
        else{
            if($query ne ""){
                $query = "($query) $boolOp $q";
            }
            else{
                $query =$q;
            }
        }
        $boolOp=$p->{"bool"};
    }
    my $retQuery="db:main";
    $retQuery .= " AND ($query)" if($query ne "");
    $retQuery .= " AND -($queryNotCont)" if($queryNotCont ne "");
    
    if(ref($filter->{'recType'}) eq "ARRAY" && scalar(@{$filter->{'recType'}})>0){
        my $rtq= "\"" . join("\" OR \"",@{$filter->{'recType'}}) . "\"";
        $retQuery = "($retQuery) AND format:($rtq)";
    }
    if(defined $filter->{'location'} && $filter->{'location'} ne''){
        $retQuery = "($retQuery) AND location:\"$filter->{'location'}\"";
    }

    if(defined $filter->{'prefix'} && $filter->{'prefix'} ne''){
        $retQuery = "($retQuery) AND callnumberPrefix:$filter->{'prefix'}";
    }
    if(defined $filter->{'dateImport'} && ($filter->{'dateImport'}->{'from'} ne '' || $filter->{'dateImport'}->{'to'} ne '')){
        my $from =$filter->{'dateImport'}->{'from'};
        my $to   =$filter->{'dateImport'}->{'to'};
        
        $from =($from =~ m/^\d\d\d\d-\d\d\-\d\d$/)?"$from"."T00:00:00Z":"*";
        $to   =($to   =~ m/^\d\d\d\d-\d\d\-\d\d$/)?"$to"."T23:59:59Z":"*";
        if($from ne "*" || $to ne "*"){
            $retQuery = "($retQuery) AND dateImport:[$from TO $to]";
        }
    }
   
   return "q=$retQuery";
}
#////////////////////////////////////////////////////////////////////////////
sub _fixUnmatchQuot{
    my ($str)=@_;
    my $tmpStr =$str;
    $tmpStr =~ s/"([^"]+)"//g;
    if($tmpStr =~ m/"/){
        $str =~ s/"//g;
    }
    return $str;
}
#////////////////////////////////////////////////////////////////////////////
sub toDecimal{
    my ($str)=@_;
    $str =~ s/^[\D]|[\D]$//g;
    $str =~ s/^[0]+/0/g;
    if($str =~ m/^([+-]?(?:\d+(?:\.\d*)?|\.\d+))$/){
        return $1;
    }
    return "NaN";
}

#////////////////////////////////////////////////////////////////////////////
sub getSolrSrvInfo{
    my $sHost   = Opals::Context->config('sHost');
    my $sPort   = Opals::Context->config('sPort');
    my $sDatabase = Opals::Context->config('zDatabase');

    return ($sHost,$sPort,$sDatabase);

}
#////////////////////////////////////////////////////////////////////////////
sub slr_search{
    my ($param)=@_;
#    $param={
#        lQuery=>'lucene query',
#        recFormat=>' brief|detail|marcXml|marcJsonObj',
#        sortAttr=>'4|1003|5001|31|8008' #see sortFieldMap'
#        sortDir=>'0|1',
#        offset=>'0 base index',
#        size=>'size',
#        incCircStatus=>'1|0',
#        incRating=>'1|0',
#        isLccSystem>'1|0', //Tue, Jul 24, 2012 @ 10:39:18 EDT
#        }

    if(!defined $param->{'lQuery'} || $param->{'lQuery'} eq ''){
        return { hits=>0,recordList=>undef,facet=>undef};
    }
    if( $param->{'isLccSystem'} && $param->{'isLccSystem'}==1){
        $sortFielMap->{'5001'}=$sortFielMap->{'5001_lcc'}
    }
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my $retFields="rid,bid,cid,isbn_first,callnumber_first";
    my $retFields_basic="rid,bid,cid,format,location,title_main,subtitle,titleSeries,author_main,callnumber_first,datePublication,placePublication,namePublisher,isbn,studyPrgm,AR_interestLevel,AR_readingLevel,AR_pointValue,AR_quizNumber,RC_interestLevel,RC_readingLevel,RC_pointValue,RC_quizNumber,lexileMeasure,lexileCode,fountasPinnell,isbn_first,subfield_852_b,callnumberPrefix_first,callnumberSubfix_first,itemPart_first,classificationPart_first,numOfPart,nameOfPart,titleSort,pubDateSort";
    my $hiliFields = "title_main,subtitle,author_main,callnumber_first,isbn,isbn_first,subfield_852_b";

 
    if($param->{'recFormat'} eq 'brief'){
        $retFields=$retFields_basic;
    }
    elsif($param->{'recFormat'} eq 'briefNewItem'){
        $retFields=$retFields_basic . ",dateImport";

    }
    elsif($param->{'recFormat'} eq 'briefReviewItem'){
        $retFields=$retFields_basic . ",lastReviewed";
    }
    if($param->{'recFormat'} eq 'briefReviewItem' || $param->{'incRating'} ==1){
        $retFields .= ",avgRating,totalReview,numOfStarsRating"   ;
    }


    my $url="http://$sHost:$sPort/solr/$sDatabase/select?" . $param->{'lQuery'} ;
    if(defined $param->{'offset'} && $param->{'offset'} >=0 && 
       defined $param->{'size'}    && $param->{'size'}>0){
        $url .="&start=". $param->{'offset'} . "&rows=" . $param->{'size'};
    }
    if(defined $param->{'sortAttr'} && defined $sortFielMap->{$param->{'sortAttr'}}){
        my $sortDir="asc";
        if(defined $param->{'sortDir'} && $param->{'sortDir'} eq '1'){
            $sortDir="desc";
        }
        elsif(!defined $param->{'sortDir'} && $param->{'sortAttr'} eq '31'){
            $sortDir="desc";
        }
        my $sortCond=$sortFielMap->{$param->{'sortAttr'}};
        $sortCond =~ s/<sortDir>/$sortDir/g;
        $url .="&sort=$sortCond" ;
    }
    $url .="&fl=$retFields&hl=true&hl.fl=".$hiliFields ."&hl.snippets=3";

    my $xmlStr=_querySolr($url);
    my $hits=getSearchHits($xmlStr);
    my $highlightTerms =getHilightTerm($xmlStr);
 
    my $result=[];
    my $srchFacet=[];
    if($hits>0){
        if($param->{'recFormat'} =~ m/brief|briefNewItem|briefReviewItem/){
            $result =getRecBrief($xmlStr);
        }
        elsif($param->{'recFormat'} eq 'detail'){
            $result =getRecInfoGeneral($xmlStr);
        }
        elsif($param->{'recFormat'} eq 'marcXml'){
            $result =getRecXml($xmlStr);
        }
        elsif($param->{'recFormat'} eq 'marcJsonObj'){
            $result =getMarcJsonOBj($xmlStr);
        }
#       get Circ status
#

        if($param->{'incCircStatus'} ==1 ){
            foreach my $r(@$result){
                my $circStatus=(defined $r->{'bid'} && $r->{'bid'}>0)?circ_getRecCircStatus_ebook($dbh,$r->{'bid'}):
                                                                      circ_getRecCircStatus_book($dbh,$r->{'rid'});
                for my $s(qw(totalHolding onLoan onHold available countLost countMissing countDamaged countInRepair countOnOrder countInProcessing ebookLicType)){    
                    $r->{$s}=$circStatus->{$s};
                }
                $r->{'onReserve'}   =($circStatus->{'onReserve'})?$circStatus->{'onReserve'} :0 ;
                $r->{'onShelvingCart'} =(defined $circStatus->{'onShelvingCart'})?$circStatus->{'onShelvingCart'}:0;
                $r->{'isOPALSEbook'}= $circStatus->{'bid'}>0?1:0;
                $r->{'bid'}         =$circStatus->{'bid'}>0?$circStatus->{'bid'}:0;
                foreach my $fmt (@{$r->{'format'}}){
                    $r->{'isEbook'}=1  if($fmt->{'item'} eq 'ebook');
                }

            }

        }
        $srchFacet =getRsFacet($xmlStr);    
    }
    _logSearch($param->{'lQuery'},$hits);
return  { hits=>$hits,recordList=>$result,facet=>$srchFacet,highlightTerms=>$highlightTerms};

}

#////////////////////////////////////////////////////////////////////////////
sub slr_search_simple{
my ($param)=@_;

#    $param={
#        lQuery=>'lucene query',
#        sortAttr=>'4|1003|5001|31|8008' #see sortFieldMap'
#        sortDir=>'0|1',
#        offset=>'0 base index',
#        size=>'size',
#        incCircStatus=>'1|0',
#        incRating=>'1|0',
#        }

if(!defined $param->{'lQuery'} || $param->{'lQuery'} eq ''){
    return { hits=>0,recordList=>undef,facet=>undef};
    }

    if( $param->{'isLccSystem'} && $param->{'isLccSystem'}==1){
        $sortFielMap->{'5001'}=$sortFielMap->{'5001_lcc'}
    }
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my $retFields="rid,isbn_first,callnumber_first";
    $retFields="rid,cid,format,location,title_main,subtitle,author_main,callnumber_first,datePublication,placePublication,isbn_first,isbn";
    my $url="http://$sHost:$sPort/solr/$sDatabase/select?" . $param->{'lQuery'} ;
   
    if(defined $param->{'offset'} && $param->{'offset'} >=0 && 
       defined $param->{'size'}    && $param->{'size'}>0){
        $url .="&start=". $param->{'offset'} . "&rows=" . $param->{'size'};
    }
    if(defined $param->{'sortAttr'} && defined $sortFielMap->{$param->{'sortAttr'}}){
        my $sortDir="asc";
        if(defined $param->{'sortDir'} && $param->{'sortDir'} eq '1'){
            $sortDir="desc";
        }
        elsif(!defined $param->{'sortDir'} && $param->{'sortAttr'} eq '31'){
            $sortDir="desc";
        }
        my $sortCond=$sortFielMap->{$param->{'sortAttr'}};
        $sortCond =~ s/<sortDir>/$sortDir/g;
        $url .="&sort=$sortCond" ;
    }
    $url .="&fl=$retFields";
    #open debug,">/tmp/cc";print debug $url;close debug;
    my $xmlStr=_querySolr($url);
    my $hits=getSearchHits($xmlStr);
 
    my $result=[];
    my $srchFacet=[];
    if($hits>0){
       $result =getRecBrief($xmlStr);
    }
    return  { hits=>$hits,recordList=>$result};

}

#////////////////////////////////////////////////////////////////////////////
sub slr_eb_search{
    my ($param)=@_;
#    $param={
#        lQuery=>'lucene query',
#        sortField=>'title|author|datePublication'
#        sortDir=>'0|1',
#        offset=>'0 base index',
#        size=>'size',
#        }

    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my $retFields="rid,bid,title_main,subtitle,author_main,callnumber_first,datePublication,placePublication,isbn_first,isbn";
    my $url="http://$sHost:$sPort/solr/$sDatabase/select?" . $param->{'lQuery'} ;
   
    if(defined $param->{'offset'} && $param->{'offset'} >=0 && 
       defined $param->{'size'}    && $param->{'size'}>0){
        $url .="&start=". $param->{'offset'} . "&rows=" . $param->{'size'};
    }
    if(defined $param->{'sortField'} && defined $sortFielMap->{$param->{'sortField'}}){    
        my $sortDir="asc";
        if(defined $param->{'sortDir'} && $param->{'sortDir'} eq '1'){
            $sortDir="desc";
        }
        my $sortCond=$sortFielMap->{$param->{'sortField'}};
        $sortCond =~ s/<sortDir>/$sortDir/g;
        $url .="&sort=$sortCond" ;

    }
    $url .="&fl=$retFields";


    my $xmlStr=_querySolr($url);
    my $hits=getSearchHits($xmlStr);
    my $result=[];
    if($hits>0){
       $result =getRecBrief_eb($xmlStr);
    }
   

    return  { hits=>$hits,recordList=>$result};

}



#////////////////////////////////////////////////////////////////////////////
sub getRecRating{
    my ($rsList)=@_;
    my($avgRating,$numOfStars,$fraction)=(0,0,0);
    foreach my $rec (@$rsList){
        $avgRating = cmntRating_getAvgRating($dbh,$rec->{'rid'});
        $numOfStars =floor($avgRating);
        $fraction= $avgRating - $numOfStars ;
        if($fraction >0.25 && $fraction<0.75){
             $numOfStars += 0.5;
        }
        elsif($fraction >0.75){
             $numOfStars += 1;
        }
        $rec->{'AverageRating'}   = sprintf("%.2f", $avgRating);
        $rec->{'numOfReviewRating'} = cmntRating_numOfRating($dbh,$rec->{'rid'});
        $rec->{'numOfStarsRating'}= $numOfStars;
             
     }
}

#////////////////////////////////////////////////////////////////////////////
sub slr_browseSubject_beginWith{
    my($word,$offset,$size)=@_;
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    $word =lc($word);
    $word =~ s/ /\\ /g;
    my @subjectList=();

    if(!defined $offset || $offset <0){
        $offset=0;
        $size  =20;
    }
    if(!defined $size || $size<1){
        $size=20;
    }
        
    my $url=  "http://$sHost:$sPort/solr/$sDatabase/select?" 
            . "group.ngroups=true&group=true&group.field=subject_grp&"
            . "fq=db:subject&q=subject_br:$word*&sort=subject_grp+asc&start=$offset&rows=$size";

    my $xmlStr=_querySolr($url);
    my $hits=getGroupFound($xmlStr);
    if($hits>0){
        while($xmlStr =~ m/<doc>(.*?)<\/doc>(.*)/s){
            $xmlStr=$2;
            my $tmpXml=$1;
            if($tmpXml =~ m/<str name="subject_grp">(.*?)<\/str>/s){
                push @subjectList,$1 ;
            }
        }
    }
    return ($hits,\@subjectList);

}
#////////////////////////////////////////////////////////////////////////////
sub slr_browseSubject_keyword{
    my($keyword,$offset,$size)=@_;
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my @subjectList=();

    if(!defined $offset || $offset <0){
        $offset=0;
        $size  =20;
    }
    if(!defined $size || $size<1){
        $size=20;
    }
    
    my $url=  "http://$sHost:$sPort/solr/$sDatabase/select?" 
            . "group.ngroups=true&group=true&group.field=subject_grp&"
            . "fq=db:subject&q=subject:($keyword)&start=$offset&rows=$size&sort=subject_grp+asc";
    my $xmlStr=_querySolr($url);
    my $hits=getGroupFound($xmlStr);
    if($hits>0){
         while($xmlStr =~ m/<doc>(.*?)<\/doc>(.*)/s){
            $xmlStr=$2;
            my $tmpXml=$1;
            if($tmpXml =~ m/<str name="subject_grp">(.*?)<\/str>/s){
                push @subjectList,$1 ;
            }
        }
    }
    return ($hits,\@subjectList);


}
#////////////////////////////////////////////////////////////////////////////
sub _getLocationQryFilter{
    my($locations)=@_;
    my $fq="";
    if(defined $locations && ref($locations) eq "ARRAY" ){
        my @locs=();
        foreach my$loc(@$locations){
            push @locs,$loc if($loc ne '');
        }
        if(scalar(@locs)>0){
            $fq="location:(\"" . join("\" OR \"",@locs) . "\")";
        }
    }
    return $fq;

}
#////////////////////////////////////////////////////////////////////////////
sub slr_browseTitle_keyword{
    my($keyword,$offset,$size,$locations)=@_;
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my @titleList=();

    if(!defined $offset || $offset <0){
        $offset=0;
        $size  =20;
    }
    if(!defined $size || $size<1){
        $size=20;
    }
    
    my $url=  "http://$sHost:$sPort/solr/$sDatabase/select?fq=db:main&" 
            . "group.ngroups=true&group=true&group.field=title_main&"
            . "q=title_ac:($keyword)&start=$offset&rows=$size&sort=title_sort+asc";

    my $locQf=_getLocationQryFilter($locations);
    $url .="&fq=$locQf" if($locQf ne "");

    my $xmlStr=_querySolr($url);
    my $hits=getGroupFound($xmlStr);
    if($hits>0){
         while($xmlStr =~ m/<doc>(.*?)<\/doc>(.*)/s){
            $xmlStr=$2;
            my $tmpXml=$1;
            if($tmpXml =~ m/<str name="title_main">(.*?)<\/str>/s){
                push @titleList,$1 ;
            }
        }
    }
    return ($hits,\@titleList);

}
#////////////////////////////////////////////////////////////////////////////
sub slr_browseFieldKeyword{
    my($keyword,$field,$offset,$size,$locations)=@_;
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my @rsList=();
    my $fMap={title     =>['main','title_ac','title_main','title_sort'],
              author    =>['author','author','author_grp','author_grp'],
              subject   =>['subject','subject','subject_grp','subject_grp'],
              anywhere  =>['awAc','awAc','awAc_grp','awAc_grp'],
              award     =>['awardAc','awardAc','awardAc_grp','awardAc_grp']
             };
    my($db,$f,$fGrp,$fSort)= @{$fMap->{$field}};       
    my $url= "http://$sHost:$sPort/solr/$sDatabase/select?"
            . "fq=db:$db&q=$f:($keyword)&" 
            . "group.ngroups=true&group=true&group.field=$fGrp&"
            . "start=$offset&rows=$size&sort=$fSort+asc";

    my $xmlStr=_querySolr($url);
    my $hits=getGroupFound($xmlStr);
    if($hits>0){
         while($xmlStr =~ m/<doc>(.*?)<\/doc>(.*)/s){
            $xmlStr=$2;
            my $tmpXml=$1;
            if($tmpXml =~ m/<str name="$fGrp">(.*?)<\/str>/s){
                push @rsList,$1 ;
            }
        }
    }
    return ($hits,\@rsList);
  
         
}
#////////////////////////////////////////////////////////////////////////////
# get auto complete list (suggestion list for a given term and field)
sub slr_getACList{
    my ($field,$term,$locations)=@_;
    my $sugList=[];
    my $hits=0;
    ($hits,$sugList)=slr_browseFieldKeyword($term,$field,0,30,$locations);
    if($hits==0 && $field ne 'award'){
        $term=slr_getSuggestion($field,$term);
        if($term ne ""){
            ($hits,$sugList)=slr_browseFieldKeyword($term,$field,0,30,$locations);
        }
    }
    return  $sugList; 

}
#////////////////////////////////////////////////////////////////////////////
sub slr_sbjAuthority_search{
    my($searchArg,$offset,$size)=@_;
    
#    $searchArg : hash of keyword,tag,ind1,ind2.
#    example    : {keyword=>'canada', tag=>650};

    my($fistWord,$offset,$size)=@_;
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my @subjectList=();

    if(!defined $offset || $offset <0){
        $offset=0;
        $size  =20;
    }
    if(!defined $size || $size<1){
        $size=20;
    }
    my $db=($searchArg->{"src"} eq 'LOC' || $searchArg->{"src"} eq 'BISAC')?"sbjAuthority":$sDatabase;
    my $query  = _buildSbjAuthSrchQuery($searchArg);
 
    my $url=  "http://$sHost:$sPort/solr/$db/select?" 
            . "group.ngroups=true&group=true&group.field=subject_grp&"
            . "q=($query)&rows=$size&&start=$offset&sort=subject_br+asc";


    my $xmlStr=_querySolr($url);

    my $hits=getGroupFound($xmlStr);
    if($hits>0){
        while($xmlStr =~ m/<doc>(.*?)<\/doc>(.*)/s){
            $xmlStr=$2;
            my $tmpXml=$1;
            my $rec={};
            foreach my $f (qw(id tag ind1 ind2 subject_grp sbj_subfields sbj_source)){
                if($tmpXml =~ m/<str name="$f">(.*?)<\/str>/s){
                    $rec->{$f}=$1;
                }
            }
            push @subjectList,$rec ;
        }
    }
    return ($hits,\@subjectList);


}

#////////////////////////////////////////////////////////////////////////////
sub _buildSbjAuthSrchQuery{
    my ($searchArg)=@_;
    my $sTerm=$searchArg->{'keyword'};
    #/escape...
       $sTerm =~ s/^\s*|\s*$//g;
       $sTerm =~ s/([\+\-\(\)\{\}\[\]\^\"\~\?\:\!\\])/\\$1/g;
       $sTerm =~ s/%/%25/g;
       $sTerm =~ s/&/%26/g;
    #/escape...

    my $query  = $sTerm eq ""?"subject:*":"subject:$sTerm";
    my $sTag  = $searchArg->{'tag'};
       $sTag  =~ s/x+$//gi;
       $sTag  = "" if($sTag !~ m/6[0-9]{0,2}/);  
       $sTag .= "*" if(length($sTag)>0 && length($sTag)<3);

    my $sInd1 = $searchArg->{'ind1'};
       $sInd1 = "" if($sInd1 !~ m/^[0-9]$/);
    my $sInd2 = $searchArg->{'ind2'};
       $sInd2 = "" if($sInd2 !~ m/^[0-9]$/);

       $query .= " AND tag:$sTag" if($sTag ne "");
       $query .= " AND ind1:$sInd1" if($sInd1 ne "");
       $query .= " AND ind2:$sInd2" if($sInd2 ne "");

       if($searchArg->{"src"} eq 'BISAC'){
           $query  .=" AND ind2:7 AND source:BISAC";
       }
       elsif($searchArg->{"src"} ne 'LOC'){
           $query  .=" AND db:subject";
       }
  
   return $query;
}


#////////////////////////////////////////////////////////////////////////////
sub slr_browseAuthor_keyword{
    my($keyword,$offset,$size)=@_;
    my($fistWord,$offset,$size)=@_;
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my @authorList=();

    if(!defined $offset || $offset <0){
        $offset=0;
        $size  =20;
    }
    if(!defined $size || $size<1){
        $size=20;
    }
    
    my $url=  "http://$sHost:$sPort/solr/$sDatabase/select?" 
            . "group.ngroups=true&group=true&group.field=author_grp&"
            . "fq=db:author&q=author:($keyword)&start=$offset&rows=$size&sort=author_grp+asc";
    my $xmlStr=_querySolr($url);
    my $hits=getGroupFound($xmlStr);
    if($hits>0){
        while($xmlStr =~ m/<doc>(.*?)<\/doc>(.*)/s){
            $xmlStr=$2;
            my $tmpXml=$1;
            if($tmpXml =~ m/<str name="author_grp">(.*?)<\/str>/s){
                push @authorList,$1 ;
            }
        }
    }
    return ($hits,\@authorList);
}
#////////////////////////////////////////////////////////////////////////////
sub slr_browseAnywhere_keyword{
    my($keyword,$offset,$size)=@_;
    my($fistWord,$offset,$size)=@_;
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my @awAcList=();

    if(!defined $offset || $offset <0){
        $offset=0;
        $size  =20;
    }
    if(!defined $size || $size<1){
        $size=20;
    }
    
    my $url=  "http://$sHost:$sPort/solr/$sDatabase/select?" 
            . "group.ngroups=true&group=true&group.field=awAc_grp&"
            . "fq=db:awAc&q=awAc:($keyword)&start=$offset&rows=$size&sort=awAc_grp+asc";
    my $xmlStr=_querySolr($url);
    my $hits=getGroupFound($xmlStr);
    if($hits>0){
        while($xmlStr =~ m/<doc>(.*?)<\/doc>(.*)/s){
            $xmlStr=$2;
            my $tmpXml=$1;
            if($tmpXml =~ m/<str name="awAc_grp">(.*?)<\/str>/s){
                push @awAcList,$1 ;
            }
        }
    }
    return ($hits,\@awAcList);
}

#####################################################
sub _querySolr{
    my($url)=@_;

    my $timeout = 600;
    my $userAgent = LWP::UserAgent->new(agent   => 'OPALS',
                                        timeout =>600);
    my $request = HTTP::Request->new(GET => $url); 

    my $response = $userAgent->request($request );

    return $response->content;
 
}
#####################################################
sub slr_getSimilarItem{
    my($rid,$fiction)=@_;
    my $mltList=[];
    my $found =0;
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my $qt=(defined $fiction && $fiction==1)?"morelikethis":"morelikethisNonFic";
    my $retFields="rid,cid,bid,format,location,title_main,author_main,callnumber_first,summary,datePublication,placePublication,namePublisher,isbn,isbn_first";

    if($rid){
        my $url= "http://$sHost:$sPort/solr/$sDatabase/select?q=rid:$rid&fq=db:main&qt=$qt&fl=$retFields" ;
        my $xmlStr=_querySolr($url);
        my $hits=getSearchHits($xmlStr);
        if($hits>0){
            $mltList =getRecBrief_sim($xmlStr);
       }
    }
    return $mltList;

}
#####################################################
sub slr_getSuggestion{
    my($field,$term)=@_;
    my $suggestion="";    
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my $url="http://$sHost:$sPort/solr/$sDatabase/" ;

    if($field eq 'title' || $field eq '4'){
        $url .="spellTitle?spellcheck=true";
    }
    elsif($field eq 'author'  || $field eq '1003'){
        $url .="spellAuthor?spellcheck=true";
    }
    elsif($field eq 'subject'  || $field eq '21'){
        $url .="spellSubject?spellcheck=true";
    }
    elsif($field eq 'keyword'  || $field eq '1016'){
        $url .="spell?spellcheck=true";
    }
    else{
       ## $url .="spell?spellcheck=true";
        $url .="spell?spellcheck=true";
    }
    $url .= "&q=$term&spellcheck.collate=true";
    my $xmlStr=_querySolr($url);
    if($xmlStr =~ m/<str name="collation">(.*)<\/str>/s){
         $suggestion=$1;
    }
    return $suggestion;

}
#####################################################
sub recordPath {
    my ($rid) = @_;
    
    my $zRoot   = Opals::Context->config('zRoot');
    my $zPort   = Opals::Context->config('zPort');
    my $zDatabase = Opals::Context->config('zDatabase');
    my $dir     = "$zRoot/$zPort/record/$zDatabase/" . ceil($rid/1000);

    return $dir;
}

my $marcXmlParser=undef;

#////////////////////////////////////////////////////////////////////////////
sub slr_updateIndexDir{
    my ($dirName)=@_; # fName : absolute filename path 
    my $indexConfFile  = Opals::Context->config('sIndexConfig');
    $marcXmlParser=Opals::MarcXmlParser->new($indexConfFile) if (!defined $marcXmlParser);
    my $numRecPerUpdate=0;
    my $updateXml ="<add>\n";
    if(-d $dirName){
        foreach my $fName(<$dirName/*>){
            if(-f $fName){
               $updateXml .=$marcXmlParser->getSolrRecXml_file($fName);
                $numRecPerUpdate++;
                if($numRecPerUpdate==40){
                    $updateXml .="</add>\n";
                    postRequest(encode("utf8",$updateXml));
                    $updateXml="<add>\n";
                    $numRecPerUpdate=0;
                }
            }
            elsif(-d $fName){
                slr_updateIndexDir($fName);
            }
        }
   }
   $updateXml .="</add>\n";
   postRequest($updateXml) if($numRecPerUpdate>0);
   postRequest("<commit/>");
 }
#////////////////////////////////////////////////////////////////////////////

#////////////////////////////////////////////////////////////////////////////
sub slr_updateIndexFile{
   my ($fName)=@_; # fName : absolute filename path 
    my $indexConfFile  = Opals::Context->config('sIndexConfig');
   if(-f $fName){
       $marcXmlParser=Opals::MarcXmlParser->new($indexConfFile) if (!defined $marcXmlParser);
        my $updateXml = "<add>" . $marcXmlParser->getSolrRecXml_file($fName) ."</add>" ;
        postRequest(encode("utf8",$updateXml));
        postRequest("<commit/>");
    }
}

#////////////////////////////////////////////////////////////////////////////
sub slr_delIndex{
    my ($rid)=@_;  
    postRequest("<delete><query>db:(main OR subject OR author) AND rid:$rid</query></delete>");
    postRequest("<commit/>");
}
#////////////////////////////////////////////////////////////////////////////
sub slr_updateIndex{
    my($bid,$xmlDoc)=@_;
    #postRequest("<delete><query>bid:$bid</query></delete>");
    postRequest("<add>$xmlDoc</add>") ;
    postRequest("<commit/>");


}

#////////////////////////////////////////////////////////////////////////////
sub postRequest{
    my ($xml)=@_;
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();

    my $url="http://$sHost:$sPort/solr/$sDatabase/update" ;
    #my $url="http://$sHost:$sPort/solr/update" ;

    my $req = HTTP::Request->new( POST => $url );
    my $timeout = 600;
    my $ua        = LWP::UserAgent->new(agent => 'OPALS');
    $ua->timeout($timeout);
    $ua->agent("SolrHTTPUpdateHandlerAgent");
    $req->content_type('Content-type:text/xml; charset=utf-8');
    $req->content($xml);
    my $res = $ua->request($req);

    
}
#////////////////////////////////////////////////////////////////////////////
sub getRsFacet{
    my ($xml)=@_;
    my $tmpXml;
    my $facetList=[];
    foreach my $f(@facetListOrder){
        $tmpXml=  $xml;
        if($tmpXml =~ /<lst name="$facetFieldMap->{$f}">(.*?)<\/lst>/){
            $tmpXml=$1;
            my $fList=[];
            while($tmpXml =~ m/<int name="(.*?)">(.*?)<\/int>(.*)/s){
                my $fc =$1;
                my $c=$2;
                $fc =~ s/&amp;/&/g;
                $tmpXml=$3;
                push @$fList,{facet=>$fc,found=>$c} if($fc ne '' && $fc !~ m/^#1/ );

            }
            push @$facetList,{field=>$f,facetList=>$fList};
        }
    }
    return $facetList;
}
#////////////////////////////////////////////////////////////////////////////
sub getSearchHits{
    my ($xml)=@_;
    my $hits=0;
    if($xml =~ m/<result name="response" numFound="([\d]+)" start="([\d]+)"\/*>/){
        $hits=$1;
    }
    return $hits;
}


#////////////////////////////////////////////////////////////////////////////
sub getGroupFound{
    my ($xml)=@_;
    my $hits=0;
    if($xml =~ m/<int name="ngroups">([\d]+)<\/int>/){
        $hits=$1;
    }
    return $hits;
}

#////////////////////////////////////////////////////////////////////////////
sub getRecBrief{
    my ($xml)=@_;
    my $fieldMap={
                rid                 =>'rid',
                bid                 =>'bid',
                cid                 =>'cid',
                format              =>'format',
                title_main          =>'title',
                titleSort           =>'titleSort',
                subtitle            =>'subtitle',
                titleSeries         =>'titleSeries',
                nameOfPart          =>'nameOfPart',
                numOfPart           =>'numOfPart',
                author_main         =>'author',
                location            =>'branchLoc',
                callnumber_first    =>'callNum1St',
                datePublication     =>'pubDate',
                pubDateSort         =>'pubDateSort',
                namePublisher       =>'pubName',
                placePublication    =>'pubPlace',
                isbn                =>'isbn',
                isbn_first          =>'isbn_first',
                lexileMeasure       =>'lexileMeasure',
                lexileCode          =>'lexileCode',
                fountasPinnell      =>'fountasPinnell',
                studyPrgm           =>'studyPrgm',
                subfield_852_b      =>'subfield_852_b',

                callnumberPrefix_first      =>'callnumberPrefix_first',
                classificationPart_first    =>'classificationPart_first',
                itemPart_first              =>'itemPart_first',
                callnumberSubfix_first      =>'callnumberSubfix_first',

                avgRating           =>'AverageRating',
                totalReview         =>'numOfReviewRating',
                numOfStarsRating    =>'numOfStarsRating',
                lastReviewed        =>'lastReviewed',
                dateImport          =>'dateImport',
             };
   return  getRecordFields($xml,$fieldMap);
}
#////////////////////////////////////////////////////////////////////////////
sub getRecBrief_sim{
    my ($xml)=@_;
    my $fieldMap={
                rid                 =>'rid',
                bid                 =>'bid',
                cid                 =>'cid',
                format              =>'format',
                title_main          =>'title',
                pubDateSort         =>'pubDateSort',
                author_main         =>'author',
                callnumber_first    =>'callNum1St',
                datePublication     =>'pubDate',
                pubDateSort         =>'pubDateSort',
                namePublisher       =>'pubName',
                placePublication    =>'pubPlace',
                isbn                =>'isbn',
                isbn_first          =>'isbn_first',
                summary             =>'summarySim',
             };
   return  getRecordFields($xml,$fieldMap);
}
#////////////////////////////////////////////////////////////////////////////
sub getRecBrief_eb{
    my ($xml)=@_;
    my $fieldMap={
                rid                 =>'rid',
                cid                 =>'cid',
                bid                 =>'bid',
                title_main          =>'title',
                author_main         =>'author',
                datePublication     =>'pubDate',
                namePublisher       =>'pubName',
                placePublication    =>'pubPlace',
                isbn                =>'isbn',
                isbn_first          =>'isbn_first',
                summary             =>'summary',
             };
   return  getRecordFields($xml,$fieldMap);
}


#////////////////////////////////////////////////////////////////////////////
sub getHilightTerm{
    my($xml)=@_;
    my $hiliTermHash={};
    my $hiliTerms=[];
    if($xml =~ m/(.*)<lst name="highlighting">(.*)<\/response>/s){
        my $tmpXml=$2;
        while($tmpXml =~ m/&lt;em&gt;(.*?)&lt;\/em&gt;(.*)/s){
            $tmpXml=$2;
            my $term=$1;
            $term =~ s/^[\s]+|[\s]+$//g;
            #$term =~ s/^\W+|\W+$//g;
            $hiliTermHash->{$term}=1  if(length($term)>1 && $term =~m/^[a-zA-Z0-9]+$/g);
        }
    }
    foreach my $t(keys %$hiliTermHash){
            push @$hiliTerms,{term=>$t} if(length($t))>1;
    }
    $hiliTerms=[] if(scalar(@$hiliTerms)>30);
    return $hiliTerms;
}
#////////////////////////////////////////////////////////////////////////////
sub getHilightMap{
    my($xml)=@_;
    my $hiliTermHash={};
    my $hiliTerms=[];
    if($xml =~ m/(.*)<lst name="highlighting">(.*)<\/response>/s){
        my $tmpXml=$2;
        while($tmpXml =~ m/&lt;em&gt;(.*?)&lt;\/em&gt;(.*)/s){
            $tmpXml=$2;
            my $term=$1;
            $term =~ s/^\W+|\W+$//g;
            $hiliTermHash->{$term}=1;
        }
    }
    foreach my $t(keys %$hiliTermHash){
            push @$hiliTerms,{term=>$t} if(length($t))>1;
    }
    $hiliTerms=[] if(scalar(@$hiliTerms)>30);
    return $hiliTerms;
}

#////////////////////////////////////////////////////////////////////////////
sub highlight{
    my ($var,$hiliTerms)=@_;

    foreach my $field(sort keys %{$var}){
        if(ref($var->{$field}) eq "ARRAY"){
          foreach my $f (@{$var->{$field}}){
            $f=highlight($f,$hiliTerms);
          }
         
        }
        elsif(ref($var->{$field}) eq "HASH"){
            $var->{$field}=highlight($var->{$field},$hiliTerms);
        }
        else{
            $var->{$field}=highlightText($var->{$field},$hiliTerms);
        }
    }
    return $var;
}
#////////////////////////////////////////////////////////////////////////////
sub highlightText{
    my ($txt,$hiliTerms) =@_;
    
    foreach my $ht(@$hiliTerms){
        $txt =~ s/(^|\W+)$ht(\W+|$)/$1<b style='background:yellow;'>$ht<\/b>$2/g;
    }
    return $txt;
}
#////////////////////////////////////////////////////////////////////////////
sub getRecordFields{
    my($xml,$fieldMap)=@_;
    my @recList=();
    my $tmpXml="";  
    my $recXml=""  ;   
    my $recBrief=undef;
    my $fieldType="str|float|int|date";
    if($xml =~ m/(.*)<result name="response" numFound="([\d]+)" start="([\d]+)"\/*>(.*)<\/result>/s){
        $tmpXml=$4;
    }
    while($tmpXml =~ m/<doc>(.*?)<\/doc>(.*)/s){
        $recXml=$1;
        $tmpXml=$2;
        $recBrief=undef;
        foreach my $field(keys %{$fieldMap}){
            if($field eq 'studyPrgm'){
                my $arl =get_ARL_prg($recXml);
                if(scalar(@$arl)>0){
                    $recBrief->{'studyPrgm'}=$arl;
                }
            }
            if($recXml =~ m/<($fieldType) name="$field">(.*?)<\/($fieldType)>/s){
                if($1 eq $3){
                    if($1 ne 'date'){
                        $recBrief->{$fieldMap->{$field}}=escapeXml($2);
                    }
                    else{
                        $recBrief->{$fieldMap->{$field}} =substr($2,0,10);
                    }
                }
            }
            elsif($field eq 'location' && $recXml =~ m/<arr name="location">(.*?)<\/arr>/s){
                my $fmtXml=$1;
                my $location={};
                while($fmtXml =~ m/<($fieldType)>(.*?)<\/($fieldType)>(.*)/s){
                    if(defined $location->{$2}){
                        $location->{$2} +=1;
                    }
                    else{
                        $location->{$2}=1;
                    }
                    $fmtXml =$4;
                }
               foreach my $loc (keys %{$location}){ 
                    push @{$recBrief->{$fieldMap->{"location"}}},{item=>$loc,count=>$location->{$loc}};
               }

            }
            elsif($recXml =~ m/<arr name="$field">(.*?)<\/arr>/s){
                my $fmtXml=$1;
                while($fmtXml =~ m/<($fieldType)>(.*?)<\/($fieldType)>(.*)/s){
                    push @{$recBrief->{$fieldMap->{$field}}},{item=>$2} if($1 eq $3);
                    $fmtXml =$4;
                }
            }
        }
        push @recList,$recBrief if(defined $recBrief);
    }
   # open  debug,">/tmp/bb";print debug to_json(\@recList,{pretty=>1});close debug;
    return  \@recList;

}

#////////////////////////////////////////////////////////////////////////////
sub escapeXml{
    my ($str)=@_;
    $str =~ s/&amp;/&/g;
    $str =~ s/&quot;/'/g;
    $str =~ s/&lt;/</g;
    $str =~ s/&gt;/>/g;
    return $str;
}
#////////////////////////////////////////////////////////////////////////////
sub get_ARL_prg{
    my($recXml)=@_;
    my @arl=();
    my $fieldMap_AR={
                AR_interestLevel       =>'interestLevel',
                AR_readingLevel        =>'readingLevel',
                AR_pointValue          =>'pointValue',
                AR_quizNumber          =>'quizNumber',
             };
   my $fieldMap_RC={
                RC_interestLevel       =>'interestLevel',
                RC_readingLevel        =>'readingLevel',
                RC_pointValue          =>'pointValue',
                RC_quizNumber          =>'quizNumber',
             };
    my $fieldType="str|float|int";
    if($recXml =~ m/<arr name="studyPrgm">(.*?)<\/arr>/mi){
        my $inerXml=$1;
        if($inerXml =~ m/Accelerated Reader/si){
            my $ar=undef;          
            $ar->{'prgName'}='Accelerated Reader';
            foreach my $field(keys %{$fieldMap_AR}){
                if($recXml =~ m/<($fieldType) name="$field">(.*?)<\/($fieldType)>/s){
                    $ar->{$fieldMap_AR->{$field}}=$2 if($1 eq $3);
                }
           }
           push  @arl, $ar ;
        }
   
        if($inerXml =~ m/Reading counts/si){
            my $rc=undef;          
            $rc->{'prgName'}='Reading Counts';
            foreach my $field(keys %{$fieldMap_RC}){
                if($recXml =~ m/<($fieldType) name="$field">(.*?)<\/($fieldType)>/s){
                    $rc->{$fieldMap_RC->{$field}}=$2 if($1 eq $3);
                }
            }
            push  @arl, $rc;
        }

    } 
    return \@arl;

}
#////////////////////////////////////////////////////////////////////////////
sub getRecInfoGeneral{
    my ($xml)=@_;
    my @recList=();
    $marcXmlParser=Opals::MarcXmlParser->new() if (!defined $marcXmlParser);
    my $tmpXml=  $xml;  
    my $recXml=""  ;   
    while($tmpXml =~ m/<doc>(.*?)<\/doc>(.*)/s){
        $recXml=$1;
        $tmpXml=$2;
        if($recXml =~ m/<str name="rid">(.*?)<\/str>/s){
            my $fname=recordPath($1)."/$1.xml";
            my $rec=$marcXmlParser->getRecInfoGeneral_file($fname);
            my $callNum1St="";
            if($recXml =~ m/<str name="callnumber_first">(.*?)<\/str>/s){
                $callNum1St=$1;
            }
            $rec->{'callNum1St'}=$callNum1St ;
            if($recXml =~ m/<str name="isbn_first">(.*?)<\/str>/s){
                $rec->{'isbn_first'}=$1 ;
            }
              push @recList, $rec;
        }
    }

        return  \@recList;


}
#////////////////////////////////////////////////////////////////////////////
sub getRecXml{
    my ($xml)=@_;
    my @recList=();
    my $tmpXml=  $xml;  
    my $recXml=""  ;   
    while($tmpXml =~ m/<doc>(.*?)<\/doc>(.*)/s){
        $recXml=$1;
        $tmpXml=$2;
        if($recXml =~ m/<str name="rid">(.*?)<\/str>/s){
            my $fname=recordPath($1)."/$1.xml";
            push @recList,getXmlFile($fname);
        }
    }
    return  \@recList;


}
#////////////////////////////////////////////////////////////////////////////
sub getMarcJsonOBj{
    my ($xml)=@_;
    my @recList=();
    my $tmpXml=  $xml;  
    my $recXml=""  ;   
    while($tmpXml =~ m/<doc>(.*?)<\/doc>(.*)/s){
        $recXml=$1;
        $tmpXml=$2;
        if($recXml =~ m/<str name="rid">(.*?)<\/str>/s){
            my $fname=recordPath($1)."/$1.xml";
            push @recList,getMarcObj(getXmlFile($fname));
        }
    }
    return  \@recList;


}
#////////////////////////////////////////////////////////////////////////////
sub getMarcObj {
    my ($xml) = @_;
    my $marc={};
    my @fields=();

    if($xml =~ s/<leader>(.*?)<\/leader>//){
        $marc->{'leader'}=$1;
    }
    while($xml =~ s/<controlfield tag="(\d{3})">(.*?)<\/controlfield>//){
        push @fields,[$1,$2];
    }
    while($xml =~ s/<datafield tag="(\d{3})" ind1="(\s|\d)" ind2="(\s|\d)">(.*?)<\/datafield>//s){
        my($tag,$ind1,$ind2,$sf)=($1,$2,$3,$4);
        $ind1="#" if($ind1 eq ' ');
        $ind2="#" if($ind2 eq ' ');
        my @f=($tag,$ind1,$ind2);
        my @sfList=();
        while($sf =~s/<subfield code="(.?)">(.*?)<\/subfield>//){
            push @sfList,[$1,util_restoreLiteral($2)] if($1 ne '-');
        }
        push @f,\@sfList;
        push @fields,\@f;
    }
    $marc->{'fields'}=\@fields;
    return $marc;



}

#////////////////////////////////////////////////////////////////////////////
sub getXmlFile{
    my ($file)=@_;
    my $xml="";
    if(-f $file){
        open FILE,"<$file";
        #binmode FILE, ":utf8";
        while(<FILE>){
            $xml .=$_;
        }
        close FILE;
    }
    return $xml;
}


#////////////////////////////////////////////////////////////////////////////
sub slr_getARLMinMax_fieldVal{
    my($studyProgram,$field)=@_;
    my($min,$max)=('','');
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my $urlBase= "http://$sHost:$sPort/solr/$sDatabase/select?fq=db:main&q=studyPrgm:$studyProgram&fl=$field&start=0&rows=1&sort=$field";        
    my $urlMin = "$urlBase+asc";        
    my $urlMax = "$urlBase+desc";        
    my $xml=_querySolr($urlMin);
    if($xml =~ m/<float name="$field">(.*)<\/float>/){
        $min=$1;
    }
    $xml=_querySolr($urlMax);
    if($xml =~ m/<float name="$field">(.*)<\/float>/){
        $max=$1;
    }

    return ($min,$max);

}
#////////////////////////////////////////////////////////////////////////////
sub slr_getLexileMinMax{
    my($min,$max)=('','');
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my $urlBase= "http://$sHost:$sPort/solr/$sDatabase/select?fq=db:main&q=*:*&fl=lexileMeasure&start=0&rows=1&sort=lexileMeasure";        
    my $urlMin = "$urlBase+asc";        
    my $urlMax = "$urlBase+desc";  
    my $xml=_querySolr($urlMin);
    if($xml =~ m/<int name="lexileMeasure">(.*)<\/int>/){
        $min=$1;
    }
    $xml=_querySolr($urlMax);
    if($xml =~ m/<int name="lexileMeasure">(.*)<\/int>/){
        $max=$1;
    }

    return ($min,$max);

}

#////////////////////////////////////////////////////////////////////////////
#
sub slr_getShelfList{
    my($shelfParams)=@_;
    my ($callNumber,$location,$offset,$size,$dir,$curRid,$inclusive) =(
            $shelfParams->{'callNumber'},
            $shelfParams->{'location'},
            $shelfParams->{'offset'},
            $shelfParams->{'size'},
            $shelfParams->{'dir'},
            $shelfParams->{'curRid'},
            $shelfParams->{'inclusive'},
    );

# $dir      :left|right
# $inclusive:0 exclude; 1:include curRid; default 0
# 
# max lookup items is 500;
#
    my($sHost,$sPort,$sDatabase) =getSolrSrvInfo();
    my $rs={};
    my $sDir=0;
    my $shelfList=[];
    $callNumber =~ s/\+/%2B/g;
    $callNumber =~ s/^ +//g;
    return $shelfList if(!$curRid || $callNumber eq '');
    
    my $callNumField="callnumber_first";
    if($shelfParams->{'isLccSystem'}==1){
        $sortFielMap->{'5001'}=$sortFielMap->{'5001_lcc'};
        $callNumField="callNumSort_lcc";
        #$callNumField="lccSort";

    }
   # my $lQuery=($location ne "")?"fq=db:main&q=(location:\"$location\" AND $callNumField:":"fq=db:main&q=($callNumField:";
    my @locList=split(/,/,$location);
    my  $locQuery="";
    if(scalar(@locList>0)){
        my $rtq= "\"" . join("\" OR \"",@locList) . "\"";
        $locQuery = " location:($rtq)";
    }

    my $lQuery=($location ne "")?"fq=db:main&q=($locQuery AND $callNumField:":"fq=db:main&q=($callNumField:";
    if(lc($dir) eq 'left'){
        $lQuery .="[* TO \"$callNumber\"]  )";
        $sDir=1;
    }
    else{
        $lQuery .="[\"$callNumber\" TO *] )";
    }
    my $sParam={lQuery=>$lQuery,
                sortAttr=>'5001',
                sortDir=>$sDir,
                offset=>0,
                size=>50,
                isLccSystem=>$shelfParams->{'isLccSystem'}
                };
    my $count=0;
    my $start=0;
    my $c=0;
    while($count<$size){
        $rs =slr_search_simple($sParam);   
        if(defined $rs->{'recordList'}){
            foreach my $rec(@{$rs->{'recordList'}}){
                last if ($count==$size);
                if($start>$offset){
                    push @$shelfList,$rec ;
                    $count++;
                }
                elsif($curRid eq $rec->{'rid'}){
                    $start=1;
                    if($inclusive){
                        push @$shelfList,$rec;
                        $count++;
                    }
                }
                elsif($start>0){
                    $start++;
                }
            }
        }
        else{
            last;
        }
        $sParam->{'offset'} +=50;           
        last if($rs->{'hits'}< $sParam->{'offset'}|| $sParam->{'offset'}>500);
    }
    my $s=scalar(@$shelfList);
    while($s++<$size){
        push @$shelfList,{rid=>0,title=>'',author=>'',callnumber=>'',bookcoverURL=>'/theme/opals/image/shim.gif'};
    }
    
    @$shelfList =reverse(@$shelfList) if(lc($dir) eq 'left');
    return $shelfList;

}

#////////////////////////////////////////////////////////////////////////////
#
sub slr_buildSNewItemQuery{
    my($input)=@_;
    my ($nDays) = ($input->{'range'});
    my $lQuery="";
    if($nDays>0){
        $lQuery="(dateImport:[NOW-$nDays" ."DAYS TO *] AND dateNewItemExpire:[NOW TO *]) ";
    }
    else{
        $lQuery="dateImport:[* TO NOW]";
    }
    my $filter="fq=db:main";

#add search filter
    foreach my $f( keys %{$facetFieldMap}){
        my $filterVal= $input->{$f . "Filter"};
           $filterVal =~ s/&/%26/g;
        if(defined $filterVal && $filterVal ne ""){
            $filter .= "&fq=$f:\"$filterVal\"";
        }
    }
  $lQuery .="&facet=true&facet.mincount=1";
        foreach my $f( keys %{$facetFieldMap}){
            my $filterVal= $input->{$f . "Filter"};
            if(!defined $filterVal || $filterVal eq ""){
                $lQuery .="&facet.field=" . $facetFieldMap->{$f};
        }
    }

    return "$filter&q=$lQuery" ;
}
#////////////////////////////////////////////////////////////////////////////
#
sub slr_buildSReviewItemQuery{
    my($input)=@_;
    my ($nDays) = ($input->{'range'});
    my $lQuery="";
    if($nDays>0){
        $lQuery="lastReviewed:[NOW-$nDays" ."DAYS TO NOW%2B1DAY]";
    }
    else{
        $lQuery="lastReviewed:[* TO NOW%2B1DAYS]";
    }
    my $filter="fq=db:main";

#add search filter
    foreach my $f( keys %{$facetFieldMap}){
        my $filterVal= $input->{$f . "Filter"};
           $filterVal =~ s/&/%26/g;
        if(defined $filterVal && $filterVal ne ""){
            $filter .= "&fq=$f:\"$filterVal\"";
        }
    }
    $lQuery .="&facet=true&facet.mincount=1";
        foreach my $f( keys %{$facetFieldMap}){
            my $filterVal= $input->{$f . "Filter"};
            if(!defined $filterVal || $filterVal eq ""){
                $lQuery .="&facet.field=" . $facetFieldMap->{$f};
        }
    }

    return "$filter&q=$lQuery" ;
}
#################################################################
sub _logSearch{
    my ($lQuery,$hits)=@_;
    my $query =$lQuery;
    $query =~ s/(fq=db:main&q=)|(facet(.*)(&|$))//g;
    $query =~ s/&+$//g;
    return if($query eq'');
    my $searchType=getSearchType($ENV{'SCRIPT_NAME'},$query);
    my $sth =$dbh->prepare("insert into opl_srchLog(page,searchType,query,hits) values(?,?,?,?)");
    $sth->execute($ENV{'SCRIPT_NAME'},$searchType,$query,$hits);
}
#################################################################

sub getSearchType {
    my ($page,$query)=@_;
    my $sType='';

    if($page =~ m/\/bgnrSrch\/srchRs$|search\/searchPage$|search\/beginner$/g){
        $sType='general search';
    }
    elsif($page =~ m/\/arl$/){
        if($query =~ m/lexileMeasure:/g){
            $sType='lexile';
        }
        elsif($query =~ m/fountasPinnell:/g){
            $sType='fountas pinnell';
        }
        elsif($query =~ m/studyPrgm:"accelerated reader"/g){
            $sType='accelerated reader';
        }
        elsif($query =~ m/studyPrgm:"reading counts"/g){
            $sType='reading counts';
        }

    }
    return $sType;
}

#################################################################

sub slr_getHiliTems{
    my ($input)=@_;
    my $hiliTerms=[];
    foreach my $cgiInput (keys %$input) {
        if ($cgiInput =~ m/^kw/){
            my $sTerm=$input->{$cgiInput};
            my @tmp=split(/\s/,$sTerm);
            foreach my $term(@tmp){
                $term =~ s/\W//g;
                push @$hiliTerms,{term=>"$term"} if($term ne '')
            }
        }

    }
    return $hiliTerms;

}

1;
#################################################################
