#************************************************************************
# Codelet Tuning Infrastructure
# Copyright (C) 2010-2015 Intel Corporation, CEA, GENCI, and UVSQ
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#************************************************************************

# Authors: Franck Talbart, Mathieu Bordet, Nicolas Petit

import cti, ctr

from cti_hapi import util_uid, cti_json, util, alias, database, entry
from cti_hapi.main import HapiPlugin, hapi_command
import query, query_parser

import sys, math, os, time

#---------------------------------------------------------------------------

def get_repository_path(repository):
    repository = str(repository)
    if repository not in [cti.COMMON_REPOSITORY, cti.TEMP_REPOSITORY, cti.ALL_REPOSITORY]:
        repository = str(util_uid.CTI_UID(repository, cti.CTR_ENTRY_REPOSITORY))
    if repository is None:
        util.cti_plugin_print_error("Unknown repository '{0}'.".format(repository))
        exit(cti.CTI_ERROR_INVALID_ARGUMENT)
    
    place = ""
    if util_uid.is_valid_uid(repository):
        place = ctr.ctr_plugin_global_index_file_get_ctr_by_uid(util_uid.CTI_UID(str(repository), cti.CTR_ENTRY_REPOSITORY))
        if not place:
           util.cti_plugin_print_error("Local repository not found.")
           exit(cti.CTI_PLUGIN_ERROR_LOCAL_REP_DOESNT_EXISTS)
    elif repository == cti.COMMON_REPOSITORY:
        place = ctr.ctr_plugin_get_common_dir()
    elif repository == cti.TEMP_REPOSITORY:
        place = ctr.ctr_plugin_get_temp_dir()
    elif repository == cti.LOCAL_REPOSITORY:
        dot_ctr = cti.cti_plugin_config_get_value(cti.DOT_CTR)
        path = os.path.join(os.getcwd(), dot_ctr)
        if not os.path.exists(path):
           util.cti_plugin_print_error("Local repository not found.")
           exit(cti.CTI_PLUGIN_ERROR_LOCAL_REP_DOESNT_EXISTS)
        place = path
    elif repository != cti.ALL_REPOSITORY:
        util.cti_plugin_print_error("The repository type is wrong!")
        return cti.CTI_PLUGIN_ERROR_INVALID_ARGUMENTS    
    return (place, repository)

#---------------------------------------------------------------------------

def line_number(page_number, size):
    """ Return the first and last line numbers for the given page_number.
    """
    if not size:
        return (None, None)
    if page_number >= 0:
       first_line = size * page_number
    else:
       first_line = 0
    last_line = first_line + size
    return (first_line, last_line)

#---------------------------------------------------------------------------

def query_general(params):
    """ Prepare queries. """
    
    query_q = params["query"].strip()
    type_q = params["type"]
    date_start = params["date_start"]
    date_end = params["date_end"]
    fields = params["fields"]
    
    #Verifying that there are fields to display
    if not fields:
        fields = ["entry_info.entry_uid"]
    
    #Protection against SQL insertion in fields:
    for i in range(len(fields)):
        fields[i] = fields[i].replace('\r', '').replace('\n', '').replace('\t', '').replace(';', '').replace(' ', '')
    
    query_constraints = {}
    join_dict = {}
    if query_q != "*":
        #Parsing the query string
        parser = query_parser.CtiQueryParser(type_q)
        query_constraints = parser.parse(query_q)
        join_dict = parser.get_join_dict()
        #Checking for empty query: the parser would return an empty list
        if not query_constraints:
            if query_q:
                util.cti_plugin_print_error("Invalid query : '   {0}   '\n".format(query_q))
            else:
                util.cti_plugin_print_error("The query is empty.")
            return cti.CTI_PLUGIN_QUERY_NO_RESULT
    
    query_q = {}
    query_q["constraint"] = query_constraints
    query_q["attr_dict"] = join_dict
    query_q["date_start"] = date_start
    query_q["date_end"] = date_end
    query_q["fields"] = fields
    
    return query_q
    
#---------------------------------------------------------------------------

def query_select(params, query_q):
    """ Queries repositories and returns found uids. """
    
    db = database.Database()
    
    type_q = params["type"]
    format_o = params["format"]
    size = params["size"]
    pagination = params["pagination"]
    fields = params["fields"]
    sort = params["sorted"]
    pagenum = params["page"]
    
    (place, repository) = get_repository_path(params["repository"])
    
    start_time = time.time()
    
    if format_o == "json":
        first_line, last_line = line_number(pagenum-1, size)
        dict_param = query.get_query_constraints(place,
                                                 query_q,
                                                 type_q,
                                                 sort)
        result = query.db_query(dict_param, 
                                db, 
                                start_time,
                                first_line,
                                last_line)
        # Improve the readability
        result['header'] = []
        for f in fields:
            result['header'].append(f.split('.')[-1].capitalize().replace('_',' '))
        result['fields'] = fields
        result['config_table'] = {
                'current': pagenum,
                'size': size,
                'sorted': sort,
            }
        print cti_json.cti_json_encode(result)
    elif format_o == "txt":
        input_key = ''
        page_number = 0
        total_page = 0
        
        # get the terminal size
        term_width = os.popen('stty size', 'r').read()
        if not term_width:
            term_width = 150
        else:
            _,term_width = term_width.split()
            term_width = term_width.rstrip('\n')
        while input_key != 'q':
            if input_key == 'p' and page_number > 0:
                page_number -= 1
            elif input_key == 'n' and (page_number+1) < total_page:
                page_number += 1
            elif input_key == 'e':
                page_number = total_page - 1
            elif input_key == 'b':
                page_number = 0
            
            if pagination:
                os.system("clear")
            
            print "Repository: ",
            if util_uid.is_valid_uid(repository): 
                print alias.cti.cti_plugin_alias_repository_get_key(cti.CTI_UID(str(repository)))
            else:
                print str(repository)
            
            if pagination:
                first_line, last_line = line_number(page_number, size)
            else:
                first_line, last_line = None, None
            dict_param = query.get_query_constraints(place,
                                                     query_q,
                                                     type_q,
                                                     sort)
            result = query.db_query(dict_param, 
                                    db, 
                                    start_time,
                                    first_line,
                                    last_line)
            total_page = 0 
            if size and size != 0:
                total_page = int(math.ceil(float(result['total']) / size))
            if total_page == 0:
                total_page=1
            
            print "Number of results: " + str(result['total'])
            print "Time: %0.3fs" % (result['time'])
            
            
            data_table = [[] for x in fields]
            cnames = [f.capitalize().replace('.', ':').replace('_',' ') for f in fields]
            
            if result['total'] > 0:
                for line in result['data']:
                    i=0
                    for field in fields:
                        data_table[i].append(line[field])
                        i+=1
                
                #Printing the table
                print util.cti_pretty_table(data_table, cnames)
                if pagination:
                    print "\np: previous b: beginning n: next e: end q: quit"
                    if total_page != 1 and pagination:
                        print page_number + 1, "/", total_page
                        print "\nHint: for detailed info call $ cti view data <Alias | UID>"
                        input_key = raw_input("Choice (p/b/n/e/q): ")
                    else:
                        print '1/1'
                        input_key = 'q'
                
            else:
                print "No data matches your query."
                input_key = 'q'
            if not pagination:
               input_key = 'q'
        
    else:
        util.cti_plugin_print_error("Unknown output format. Please check passed arguments.")
        return cti.CTI_PLUGIN_ERROR_UNKNOWN_FORMAT
    
    return 0

#---------------------------------------------------------------------------

# The new interface
class QueryPlugin(HapiPlugin):
    @hapi_command("select")
    def select_cmd(self, params):
        """ Queries all repositories"""
        query_q = query_general(params)
        return query_select(params, query_q)
    
    #---------------------------------------------------------------------------
    
    @hapi_command("select_couple")
    def select_couple_cmd(self, params):
        """ Select couple of entries
        
        Args:
            self: class of the plugin
            params: working parameters
            
        Returns:
            a json
        """
        
        db = database.Database()
        
        fields = ["entry_info.entry_uid"]
        
        type_q = str(params["type"])
        link_params = params["link_params"]
        margins = params["margins"]
        size = params["size"]
        pagenum = params["page"]
        
        # check parameters
        if link_params is None or not link_params:
            util.cti_plugin_print_error("The link_params parameter is empty!")
            return cti.CTI_PLUGIN_ERROR_INVALID_ARGUMENTS
        
        # check comparison parameters
        (_, plugin_output_default) = entry.load_defaults(params["type"])
        plugin_output_default_params = plugin_output_default["init"].params
        for param in link_params:
            if param not in plugin_output_default_params:
                util.cti_plugin_print_error("ERROR : comparison parameter %s not found in plugin %s." % (param, type_q))
                return cti.CTI_PLUGIN_ERROR_INVALID_ARGUMENTS
        
        if len(margins) != 0 and len(margins) != len(link_params):
            util.cti_plugin_print_error("The margins list must have the same size as the link_params list!")
            return cti.CTI_PLUGIN_ERROR_INVALID_ARGUMENTS
        if margins is None or not margins:
            margins = []
            index = 0
            for link_param in link_params:
                param_type = plugin_output_default_params[link_param][cti.META_ATTRIBUTE_TYPE]
                if param_type in [cti.META_CONTENT_ATTRIBUTE_TYPE_INTEGER, cti.META_CONTENT_ATTRIBUTE_TYPE_FLOAT]:
                    margins.append(0)
                else:
                    margins.append(None)
                index += 1
        else:
            # check the margins values
            index = 0
            for margin in margins:
                param_type = plugin_output_default_params[link_params[index]][cti.META_ATTRIBUTE_TYPE]
                if param_type not in [cti.META_CONTENT_ATTRIBUTE_TYPE_INTEGER, cti.META_CONTENT_ATTRIBUTE_TYPE_FLOAT] and \
                   margin is not None:
                    util.cti_plugin_print_error("The margin for the parameter %s (type: %s) must be 'None'!" % (link_params[index], param_type))
                    return cti.CTI_PLUGIN_ERROR_INVALID_ARGUMENTS
                index += 1
        
        start_time = time.time()
        
        list_dict_param = []
        
        (first_line, last_line) = line_number(pagenum-1, size)
        # process the 2 query
        for i in [1, 2]:
            params["query"] = params["query_%s" % i]
            (place, repository) = get_repository_path(params["repository_%s" % i])
            
            params["type"] = type_q     # convert 'type' from PLUGIN_UID to string
            params["fields"] = fields
            query_q = query_general(params)
            
            dict_param = query.get_query_constraints(place,
                                                     query_q,
                                                     type_q,
                                                     0)
            list_dict_param.append(dict_param)
        
        result = query.db_query_couple(list_dict_param,
                                       link_params,
                                       margins,
                                       db,
                                       start_time,
                                       first_line,
                                       last_line)
        # Improve the readability
        result['header'] = []
        for f in fields:
            result['header'].append(f.split('.')[-1].capitalize().replace('_',' '))
        result['fields'] = fields
        result['config_table'] = {
                'current': pagenum,
                'size': size
            }
        print cti_json.cti_json_encode(result)
        
        return 0

#---------------------------------------------------------------------------

# main function
if __name__ == "__main__":
    p = QueryPlugin()
    exit(p.main(sys.argv))
