package com.alibaba.druid.sql.parser;

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import com.alibaba.druid.util.FnvHash;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class SQLSelectListCache {
   private static final Log LOG = LogFactory.getLog(SQLSelectListCache.class);
   private final DbType dbType;
   private final List<Entry> entries = new CopyOnWriteArrayList();

   public SQLSelectListCache(DbType dbType) {
      this.dbType = dbType;
   }

   public void add(String select) {
      if (select != null && select.length() != 0) {
         SQLSelectParser selectParser = SQLParserUtils.createSQLStatementParser(select, this.dbType).createSQLSelectParser();
         SQLSelectQueryBlock queryBlock = SQLParserUtils.createSelectQueryBlock(this.dbType);
         selectParser.accept(Token.SELECT);
         selectParser.parseSelectList(queryBlock);
         selectParser.accept(Token.FROM);
         selectParser.accept(Token.EOF);
         String printSql = queryBlock.toString();
         long printSqlHash = FnvHash.fnv1a_64_lower(printSql);
         this.entries.add(new Entry(select.substring(6), queryBlock, printSql, printSqlHash));
         if (this.entries.size() > 5) {
            LOG.warn("SelectListCache is too large.");
         }

      }
   }

   public int getSize() {
      return this.entries.size();
   }

   public void clear() {
      this.entries.clear();
   }

   public boolean match(Lexer lexer, SQLSelectQueryBlock queryBlock) {
      if (lexer.token != Token.SELECT) {
         return false;
      } else {
         int pos = lexer.pos;
         String text = lexer.text;

         for(int i = 0; i < this.entries.size(); ++i) {
            Entry entry = (Entry)this.entries.get(i);
            String block = entry.sql;
            if (text.startsWith(block, pos)) {
               queryBlock.setCachedSelectList(entry.printSql, entry.printSqlHash);
               int len = pos + block.length();
               lexer.reset(len, text.charAt(len), Token.FROM);
               return true;
            }
         }

         return false;
      }
   }

   private static class Entry {
      public final String sql;
      public final SQLSelectQueryBlock queryBlock;
      public final String printSql;
      public final long printSqlHash;

      public Entry(String sql, SQLSelectQueryBlock queryBlock, String printSql, long printSqlHash) {
         this.sql = sql;
         this.queryBlock = queryBlock;
         this.printSql = printSql;
         this.printSqlHash = printSqlHash;
      }
   }
}
