Index: trunk/include/functions_search.inc.php
===================================================================
--- trunk/include/functions_search.inc.php	(revision 18634)
+++ trunk/include/functions_search.inc.php	(revision 18636)
@@ -306,5 +306,5 @@
   {
     $ch = $q[$i];
-    if ($crt_token_modifier&QST_QUOTED==0)
+    if ( ($crt_token_modifier&QST_QUOTED)==0)
     {
         if ($ch=='"')
@@ -357,5 +357,4 @@
         $crt_token = ""; $crt_token_modifier = 0;
         $state=0;
-        break;
       }
       else
@@ -363,4 +362,5 @@
     }
   }
+
   if (strlen($crt_token))
   {
@@ -390,5 +390,5 @@
 
 /**
- * returns the LIKE sql clause corresponding to the quick search query 
+ * returns the LIKE sql clause corresponding to the quick search query
  * that has been split into tokens
  * for example file LIKE '%john%' OR file LIKE '%bill%'.
@@ -410,4 +410,159 @@
 
   return count($clauses) ? '('.implode(' OR ', $clauses).')' : null;
+}
+
+/**
+*/
+function get_qsearch_tags($tokens, $token_modifiers, &$token_tag_ids, &$not_tag_ids, &$all_tags)
+{
+  $token_tag_ids = array_fill(0, count($tokens), array() );
+  $not_tag_ids = $all_tags = array();
+
+  $token_tag_scores = $token_tag_ids;
+  $transliterated_tokens = array();
+  foreach ($tokens as $token)
+  {
+    $transliterated_tokens[] = transliterate($token);
+  }
+
+  $query = '
+SELECT t.*, COUNT(image_id) AS counter
+  FROM '.TAGS_TABLE.' t
+    INNER JOIN '.IMAGE_TAG_TABLE.' ON id=tag_id
+  GROUP BY id';
+  $result = pwg_query($query);
+  while ($tag = pwg_db_fetch_assoc($result))
+  {
+    $transliterated_tag = transliterate($tag['name']);
+
+    // find how this tag matches query tokens
+    for ($i=0; $i<count($tokens); $i++)
+    {
+      $transliterated_token = $transliterated_tokens[$i];
+
+      $match = false;
+      $pos = 0;
+      while ( ($pos = strpos($transliterated_tag, $transliterated_token, $pos)) !== false)
+      {
+        if ( ($token_modifiers[$i]&QST_WILDCARD)==QST_WILDCARD )
+        {// wildcard in this token
+          $match = 1;
+          break;
+        }
+        $token_len = strlen($transliterated_token);
+
+        // search begin of word
+        $wbegin_len=0; $wbegin_char=' ';
+        while ($pos-$wbegin_len > 0)
+        {
+          if (! is_word_char($transliterated_tag[$pos-$wbegin_len-1]) )
+          {
+            $wbegin_char = $transliterated_tag[$pos-$wbegin_len-1];
+            break;
+          }
+          $wbegin_len++;
+        }
+
+        // search end of word
+        $wend_len=0; $wend_char=' ';
+        while ($pos+$token_len+$wend_len < strlen($transliterated_tag))
+        {
+          if (! is_word_char($transliterated_tag[$pos+$token_len+$wend_len]) )
+          {
+            $wend_char = $transliterated_tag[$pos+$token_len+$wend_len];
+            break;
+          }
+          $wend_len++;
+        }
+
+        $this_score = 0;
+        if ( ($token_modifiers[$i]&QST_WILDCARD)==0 )
+        {// no wildcard begin or end
+          if ($token_len <= 2)
+          {// search for 1 or 2 characters must match exactly to avoid retrieving too much data
+            if ($wbegin_len==0 && $wend_len==0 && !is_odd_wbreak_begin($wbegin_char) && !is_odd_wbreak_end($wend_char) )
+              $this_score = 1;
+          }
+          elseif ($token_len == 3)
+          {
+            if ($wbegin_len==0)
+              $this_score = $token_len / ($token_len + $wend_len);
+          }
+          else
+          {
+            $this_score = $token_len / ($token_len + 1.1 * $wbegin_len + 0.9 * $wend_len);
+          }
+        }
+
+        if ($this_score>0)
+          $match = max($match, $this_score );
+        $pos++;
+      }
+
+      if ($match)
+      {
+        $tag_id = (int)$tag['id'];
+        $all_tags[$tag_id] = $tag;
+        $token_tag_ids[$i][] = $tag_id;
+        $token_tag_scores[$i][] = $match;
+      }
+    }
+  }
+
+  // process not tags
+  for ($i=0; $i<count($tokens); $i++)
+  {
+    if ( ! ($token_modifiers[$i]&QST_NOT) )
+      continue;
+
+    array_multisort($token_tag_scores[$i], SORT_DESC|SORT_NUMERIC, $token_tag_ids[$i]);
+
+    for ($j=0; $j<count($token_tag_scores[$i]); $j++)
+    {
+      if ($token_tag_scores[$i][$j] < 0.8)
+        break;
+      if ($j>0 && $token_tag_scores[$i][$j] < $token_tag_scores[$i][0])
+        break;
+      $tag_id = $token_tag_ids[$i][$j];
+      if ( isset($all_tags[$tag_id]) )
+      {
+        unset($all_tags[$tag_id]);
+        $not_tag_ids[] = $tag_id;
+      }
+    }
+    $token_tag_ids[$i] = array();
+  }
+
+  // process regular tags
+  for ($i=0; $i<count($tokens); $i++)
+  {
+    if ( $token_modifiers[$i]&QST_NOT )
+      continue;
+
+    array_multisort($token_tag_scores[$i], SORT_DESC|SORT_NUMERIC, $token_tag_ids[$i]);
+
+    $counter = 0;
+    for ($j=0; $j<count($token_tag_scores[$i]); $j++)
+    {
+      $tag_id = $token_tag_ids[$i][$j];
+      if ( ! isset($all_tags[$tag_id]) )
+      {
+        array_splice($token_tag_ids[$i], $j, 1);
+        array_splice($token_tag_scores[$i], $j, 1);
+      }
+
+      $counter += $all_tags[$tag_id]['counter'];
+      if ($counter > 200 && $j>0 && $token_tag_scores[$i][0] > $token_tag_scores[$i][$j] )
+      {// "many" images in previous tags and starting from this tag is less relevent
+        array_splice($token_tag_ids[$i], $j);
+        array_splice($token_tag_scores[$i], $j);
+        break;
+      }
+    }
+  }
+  
+  usort($all_tags, 'tag_alpha_compare');
+  foreach ( $all_tags as &$tag )
+    $tag['name'] = trigger_event('render_tag_name', $tag['name']);
 }
 
@@ -445,5 +600,5 @@
   }
   $debug[] = '<!--'.count($tokens).' tokens';
-  
+
   $q_like_field = '@@__db_field__@@'; //something never in a search
   $q_like_clause = get_qsearch_like_clause($tokens, $token_modifiers, $q_like_field );
@@ -486,131 +641,17 @@
   }
   $debug[] = count($by_weights).' fulltext';
-  $debug[] = 'ft score min:'.min($by_weights).' max:'.max($by_weights);
-
-
-  // Step 2 - search tags corresponding to the query $q ========================
-  $transliterated_tokens = array();
-  $token_tags = array();
-  foreach ($tokens as $token)
-  {
-    $transliterated_tokens[] = transliterate($token);
-    $token_tags[] = array();
-  }
-
-  // Step 2.1 - find match tags for every token in the query search
-  $all_tags = array();
-  $query = '
-SELECT id, name, url_name, COUNT(image_id) AS nb_images
-  FROM '.TAGS_TABLE.'
-    INNER JOIN '.IMAGE_TAG_TABLE.' ON id=tag_id
-  GROUP BY id';
-  $result = pwg_query($query);
-  while ($tag = pwg_db_fetch_assoc($result))
-  {
-    $transliterated_tag = transliterate($tag['name']);
-
-    // find how this tag matches query tokens
-    for ($i=0; $i<count($tokens); $i++)
-    {
-      if ($token_modifiers[$i]&QST_NOT)
-        continue;// ignore this NOT token
-      $transliterated_token = $transliterated_tokens[$i];
-
-      $match = false;
-      $pos = 0;
-      while ( ($pos = strpos($transliterated_tag, $transliterated_token, $pos)) !== false)
-      {
-        if ( ($token_modifiers[$i]&QST_WILDCARD)==QST_WILDCARD )
-        {// wildcard in this token
-          $match = 1;
-          break;
-        }
-        $token_len = strlen($transliterated_token);
-
-        // search begin of word
-        $wbegin_len=0; $wbegin_char=' ';
-        while ($pos-$wbegin_len > 0)
-        {
-          if (! is_word_char($transliterated_tag[$pos-$wbegin_len-1]) )
-          {
-            $wbegin_char = $transliterated_tag[$pos-$wbegin_len-1];
-            break;
-          }
-          $wbegin_len++;
-        }
-
-        // search end of word
-        $wend_len=0; $wend_char=' ';
-        while ($pos+$token_len+$wend_len < strlen($transliterated_tag))
-        {
-          if (! is_word_char($transliterated_tag[$pos+$token_len+$wend_len]) )
-          {
-            $wend_char = $transliterated_tag[$pos+$token_len+$wend_len];
-            break;
-          }
-          $wend_len++;
-        }
-
-        $this_score = 0;
-        if ( ($token_modifiers[$i]&QST_WILDCARD)==0 )
-        {// no wildcard begin or end
-          if ($token_len <= 2)
-          {// search for 1 or 2 characters must match exactly to avoid retrieving too much data
-            if ($wbegin_len==0 && $wend_len==0 && !is_odd_wbreak_begin($wbegin_char) && !is_odd_wbreak_end($wend_char) )
-              $this_score = 1;
-          }
-          elseif ($token_len == 3)
-          {
-            if ($wbegin_len==0)
-              $this_score = $token_len / ($token_len + $wend_len);
-          }
-          else
-          {
-            $this_score = $token_len / ($token_len + 1.1 * $wbegin_len + 0.9 * $wend_len);
-          }
-        }
-
-        if ($this_score>0)
-          $match = max($match, $this_score );
-        $pos++;
-      }
-
-      if ($match)
-      {
-        $tag_id = (int)$tag['id'];
-        $all_tags[$tag_id] = $tag;
-        $token_tags[$i][] = array('tag_id'=>$tag_id, 'score'=>$match);
-      }
-    }
-  }
-  $search_results['qs']['matching_tags']=$all_tags;
-  $debug[] = count($all_tags).' tags';
-
-  // Step 2.2 - reduce matching tags for every token in the query search
-  $score_cmp_fn = create_function('$a,$b', 'return 100*($b["score"]-$a["score"]);');
-  foreach ($token_tags as &$tt)
-  {
-    usort($tt, $score_cmp_fn);
-    $nb_images = 0;
-    $prev_score = 0;
-    for ($j=0; $j<count($tt); $j++)
-    {
-      if ($nb_images > 200 && $prev_score > $tt[$j]['score'] )
-      {// "many" images in previous tags and starting from this tag is less relevent
-        $tt = array_slice( $tt, 0, $j);
-        break;
-      }
-      $nb_images += $all_tags[ $tt[$j]['tag_id'] ]['nb_images'];
-      $prev_score = $tt[$j]['score'];
-    }
-  }
-
-  // Step 2.3 - get the images for tags
-  for ($i=0; $i<count($token_tags); $i++)
-  {
-    $tag_ids = array();
-    foreach($token_tags[$i] as $arr)
-      $tag_ids[] = $arr['tag_id'];
-    $tag_ids = array_unique($tag_ids);
+  if (!empty($by_weights))
+  {
+    $debug[] = 'ft score min:'.min($by_weights).' max:'.max($by_weights);
+  }
+
+
+  // Step 2 - get the tags and the images for tags
+  get_qsearch_tags($tokens, $token_modifiers, $token_tag_ids, $not_tag_ids, $search_results['qs']['matching_tags']);
+  $debug[] = count($search_results['qs']['matching_tags']).' tags';
+
+  for ($i=0; $i<count($token_tag_ids); $i++)
+  {
+    $tag_ids = $token_tag_ids[$i];
     $debug[] = count($tag_ids).' unique tags';
 
@@ -619,6 +660,5 @@
       $tag_photo_count=0;
       $query = '
-SELECT image_id
-  FROM '.IMAGE_TAG_TABLE.'
+SELECT image_id FROM '.IMAGE_TAG_TABLE.'
   WHERE tag_id IN ('.implode(',',$tag_ids).')
   GROUP BY image_id';
@@ -630,6 +670,6 @@
         $tag_photo_count++;
       }
-      $debug[] = $tag_photo_count.' photos for tags';
-      $debug[] = count($by_weights).' photos after tags';
+      $debug[] = $tag_photo_count.' photos for tag';
+      $debug[] = count($by_weights).' photos after';
     }
   }
@@ -664,4 +704,18 @@
   }
 
+  if (!empty($not_tag_ids))
+  {
+    $query = '
+SELECT image_id FROM '.IMAGE_TAG_TABLE.'
+  WHERE tag_id IN ('.implode(',',$not_tag_ids).')
+  GROUP BY image_id';
+      $result = pwg_query($query);
+      while ($row = pwg_db_fetch_row($result))
+      {
+        $id = $row[0];
+        unset($by_weights[$id]);
+      }
+      $debug[] = count($by_weights).' after not tags';
+  }
   // Step 4 - now we have $by_weights ( array image id => weight ) that need
   // permission checks and/or matching categories to get images from
Index: trunk/index.php
===================================================================
--- trunk/index.php	(revision 18634)
+++ trunk/index.php	(revision 18636)
@@ -227,16 +227,8 @@
 
   $tags = (array)@$page['qsearch_details']['matching_tags'];
-  if (count($tags))
-  {
-    usort($tags, 'name_compare');
-    $hints = array();
-    foreach ( $tags as $tag )
-    {
-      $hints[] =
-        '<a href="' . make_index_url(array('tags'=>array($tag))) . '">'
-        .trigger_event('render_tag_name', $tag['name'])
-        .'</a>';
-    }
-    $template->assign( 'tag_search_results', $hints);
+  foreach ( $tags as $tag )
+  {
+    $tag['URL'] = make_index_url(array('tags'=>array($tag)));
+    $template->append( 'tag_search_results', $tag);
   }
 }
Index: trunk/themes/default/template/index.tpl
===================================================================
--- trunk/themes/default/template/index.tpl	(revision 18634)
+++ trunk/themes/default/template/index.tpl	(revision 18636)
@@ -145,7 +145,6 @@
 <div style="font-size:16px;margin:10px 16px">{'Tag results for'|@translate} <strong>{$QUERY_SEARCH}</strong> :
 	<em><strong>
-	{foreach from=$tag_search_results item=res name=res_loop}
-	{if !$smarty.foreach.res_loop.first} &mdash; {/if}
-	{$res}
+	{foreach from=$tag_search_results item=tag name=res_loop}
+	{if !$smarty.foreach.res_loop.first} &mdash; {/if} <a href="{$tag.URL}">{$tag.name}</a>
 	{/foreach}
 	</strong></em>
