using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ModelData.BusinessModel.RDF
{
    /// \addtogroup RDF
    /// @{

    /// \class  Queries
    ///
    /// \brief  Класс, содержащий текст запросов
    /// Методы класса возвращают строку с запросом, при необходимости подставляют параметры
    /// \author Denis
    /// \date   31.03.2019

    public class Queries
    {
        #region Prefixes

        /// \brief  Доступное для чтения поле, содержащее краткий префикс (Key) и полное значение (Value) используемого ресурса                
        public static readonly Dictionary<string, string> Prefixes = new Dictionary<string, string>
        {
            ["rdf"] = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#>",
            ["rdfs"] = "<http://www.w3.org/2000/01/rdf-schema#>",
            ["owl"] = "<http://www.w3.org/2002/07/owl#>",
            ["xsd"] = "<http://www.w3.org/2001/XMLSchema#>",
            ["geo"] = "<http://www.w3.org/2003/01/geo/wgs84_pos:>",
            ["geonames"] = "<http://www.geonames.org/ontology#>",
            ["time"] = "<https://www.w3.org/2006/time#>",
            ["map"] = "<http://jena.denhome.keenetic.link/map#>",
            ["foaf"] = "<http://xmlns.com/foaf/0.1/>",
            ["dc"] = "<http://purl.org/dc/elements/1.1/>",
            ["dcterms"] = "<http://purl.org/dc/terms/>"
        };

        #endregion

        #region Queries

        /// \fn public static string AllSettlementsInInterval(string from, string to)
        ///
        /// \brief  Выполняет поиск поселений и изменных поселений в определенном временном интервале.
        /// \code
        ///SELECT DISTINCT (?settlement as ?url) ?title ?lat ?long ?settlementType ?typeName ?region ?regionName (?maxc as ?maxPop)
        ///WHERE
        ///{
        ///		?settlement a map:Settlement;
        ///					geonames:name ?title;
        ///					geo:lat ?lat; 
        ///					geo:long ?long.
        ///		OPTIONAL {?settlement time:hasBeginning ?wasFound. ?wasFound time:year ?wasFoundYear}
        ///		FILTER (!bound(?wasFound) || ?wasFoundYear <= "to"^^xsd:gYear )
        ///		{
        ///			SELECT ?settlement (MAX(?populationNotNULL) AS ?maxc)
        ///			WHERE 
        ///			{ 
        ///				?editedSettlement a map:EditedSettlement; time:before ?settlement; time:hasBeginning ?dateBegin.
        ///				?dateBegin time:year ?yearBegin.
        ///				OPTIONAL {?editedSettlement time:hasEnd ?dateEnd. ?dateEnd time:year ?yearEnd.}
        ///				##	датаНачало должна попадать в промежуток до верхней временной границы
        ///				##	а датаКонец либо не указана, либо попадает в промежуток после нижней временно границы
        ///				FILTER  (?yearBegin <= "to"^^xsd:gYear && (!bound(?dateEnd) || ?yearEnd > "from"^^xsd:gYear))
        ///				OPTIONAL {?editedSettlement geonames:population ?population.}
        ///				OPTIONAL {?editedSettlement map:settlementType ?settlementType. ?settlementType geonames:name ?typeName}
        ///		
        ///				## заполни переменную ?populationNotNULL так: если ?population НЕ задан (т.е. NULL), тогда 0, иначе (т.е. задан) ?population
        ///				BIND (IF (!BOUND(?population), "0"^^xsd:integer, ?population) as ?populationNotNULL)
        ///			}
        ///			Group by ?settlement
        ///		}
        ///		{
        ///			SELECT ?settlement ?settlementType ?typeName ?region ?regionName
        ///			WHERE 
        ///			{ 
        ///				?editedSettlement a map:EditedSettlement; time:before ?settlement; time:hasBeginning ?dateBegin.
        ///				?dateBegin time:year ?yearBegin.
        ///				OPTIONAL {?editedSettlement time:hasEnd ?dateEnd. ?dateEnd time:year ?yearEnd.}
        ///				##	датаНачало должна попадать в промежуток до верхней временной границы
        ///				##	а датаКонец либо не указана, либо попадает в промежуток после нижней временно границы
        ///				FILTER  (?yearBegin <= "to"^^xsd:gYear && (!bound(?dateEnd) || ?yearEnd > "from"^^xsd:gYear))
        ///				OPTIONAL {?editedSettlement map:settlementType ?settlementType. ?settlementType geonames:name ?typeName}
        ///				OPTIONAL {?editedSettlement rdfs:partOf ?region. ?region geonames:name ?regionName}
        ///			}
        ///		}
        ///}                                                                                                                                   
        /// \endcode
        /// \author Denis
        /// \date   31.03.2019
        ///
        /// \param  from    С какого года выполнять поиск.
        /// \param  to      По какой год выполнять поиск.
        ///
        /// \returns    A string.

        public static string AllSettlementsInInterval(string from, string to)
        {
            return GetAllPrefixes() +
@"
SELECT DISTINCT (?settlement as ?url) ?title ?lat ?long ?settlementType ?typeName ?region ?regionName (?maxc as ?maxPop)
WHERE
{
        ?settlement a map:Settlement;
                    geonames:name ?title;
                    geo:lat ?lat; 
                    geo:long ?long.
        OPTIONAL {?settlement time:hasBeginning ?wasFound. ?wasFound time:year ?wasFoundYear}
        FILTER (!bound(?wasFound) || ?wasFoundYear <= " + '"' + to + '"' + @"^^xsd:gYear )
        {
            SELECT ?settlement (MAX(?populationNotNULL) AS ?maxc)
            WHERE 
            { 
                ?editedSettlement a map:EditedSettlement; time:before ?settlement; time:hasBeginning ?dateBegin.
                ?dateBegin time:year ?yearBegin.
      			OPTIONAL {?editedSettlement time:hasEnd ?dateEnd. ?dateEnd time:year ?yearEnd.}
      			##	датаНачало должна попадать в промежуток до верхней временной границы
      			##	а датаКонец либо не указана, либо попадает в промежуток после нижней временно границы
      			FILTER  (?yearBegin <= " + '"' + to + '"' + @"^^xsd:gYear && (!bound(?dateEnd) || ?yearEnd > " + '"' + from + '"' + @"^^xsd:gYear))
                OPTIONAL {?editedSettlement geonames:population ?population.}
                OPTIONAL {?editedSettlement map:settlementType ?settlementType. ?settlementType geonames:name ?typeName}
        
                ## заполни переменную ?populationNotNULL так: если ?population НЕ задан (т.е. NULL), тогда 0, иначе (т.е. задан) ?population
                BIND (IF (!BOUND(?population), " + "\"0\"" + @"^^xsd:integer, ?population) as ?populationNotNULL)
            }
            Group by ?settlement
        }
        {
            SELECT ?settlement ?settlementType ?typeName ?region ?regionName
            WHERE 
            { 
                ?editedSettlement a map:EditedSettlement; time:before ?settlement; time:hasBeginning ?dateBegin.
                ?dateBegin time:year ?yearBegin.
      			OPTIONAL {?editedSettlement time:hasEnd ?dateEnd. ?dateEnd time:year ?yearEnd.}
      			##	датаНачало должна попадать в промежуток до верхней временной границы
      			##	а датаКонец либо не указана, либо попадает в промежуток после нижней временно границы
      			FILTER  (?yearBegin <= " + '"' + to + '"' + @"^^xsd:gYear && (!bound(?dateEnd) || ?yearEnd > " + '"' + from + '"' + @"^^xsd:gYear))
                OPTIONAL {?editedSettlement map:settlementType ?settlementType. ?settlementType geonames:name ?typeName}
                OPTIONAL {?editedSettlement rdfs:partOf ?region. ?region geonames:name ?regionName}
            }
        }
}
";
        }



        /// \fn public static string InfoAboutSettlement(string URL)
        ///
        /// \brief  Полная информация о поселении
        /// \code       
        ///SELECT DISTINCT ?title ?lat ?long ?wasFoundYear ?legend ?founder ?person ?foundersRegion ?editedSettlement ?sourceURL ?sourceTitle ?sourceCreator ?year ?settlementType ?typeName ?region ?regionName ?editedName ?alternateName ?population ?populationFemales ?populationMales
        ///WHERE
        ///{
        ///		{ 
        ///			SELECT * WHERE
        ///			{
        ///				url_ geonames:name ?title;
        ///						  geo:lat ?lat; geo:long ?long.
        ///		
        ///				OPTIONAL {url_ time:hasBeginning ?wasFound. ?wasFound time:year ?wasFoundYear}
        ///				OPTIONAL {url_ map:legend ?legend.}
        ///				OPTIONAL 
        ///				{
        ///					url_ map:founder ?founder. 
        ///					OPTIONAL {?founder foaf:Person ?person}
        ///					OPTIONAL {?founder map:foundersRegion ?foundersRegion}
        ///				}
        ///			}
        ///		}	
        ///		UNION
        ///		{
        ///			SELECT * WHERE 
        ///			{ 
        ///				?editedSettlement a map:EditedSettlement; 
        ///								time:before url_; 
        ///								dc:source ?source; 
        ///								time:hasBeginning ?date.
        ///				?date time:year ?year.
        ///				?source a dcterms:bibliographicResource; dc:identifier ?sourceURL; dc:title ?sourceTitle. 
        ///             OPTIONAL {?source dc:creator? sourceCreator.}
        ///				OPTIONAL {?editedSettlement map:settlementType ?settlementType. ?settlementType geonames:name ?typeName}
        ///				OPTIONAL {?editedSettlement rdfs:partOf ?region. ?region geonames:name ?regionName}
        ///				OPTIONAL {?editedSettlement geonames:name ?editedName.}
        ///				OPTIONAL {?editedSettlement geonames:alternateName ?alternateName.}
        ///				OPTIONAL {?editedSettlement geonames:population ?population.}
        ///				OPTIONAL {?editedSettlement map:populationFemalesCount ?populationFemales.}
        ///				OPTIONAL {?editedSettlement map:populationMalesCount ?populationMales.}
        ///				OPTIONAL {?editedSettlement map:settlementType ?settlementType. ?settlementType geonames:name ?typeName}
        ///			}
        ///		}
        ///}
        /// \endcode
        /// \author Denis
        /// \date   31.03.2019
        ///
        /// \param  URL URL искомого поселения.
        ///
        /// \returns    A string.

        public static string InfoAboutSettlement(string URL)
        {
            string url_ = GetObjFromURL(URL);

            return GetAllPrefixes() +
@"SELECT DISTINCT ?title ?lat ?long ?wasFoundYear ?legend ?founder ?person ?foundersRegion ?editedSettlement ?sourceURL ?sourceTitle ?sourceCreator ?year ?settlementType ?typeName ?region ?regionName ?editedName ?alternateName ?population ?populationFemales ?populationMales
WHERE
{
	{ 
		SELECT * WHERE
		{
			" + url_ + @" geonames:name ?title;
					  geo:lat ?lat; geo:long ?long.

			OPTIONAL {" + url_ + @" time:hasBeginning ?wasFound. ?wasFound time:year ?wasFoundYear}
			OPTIONAL {" + url_ + @" map:legend ?legend.}
			OPTIONAL 
			{
				" + url_ + @" map:founder ?founder. 
				OPTIONAL {?founder foaf:Person ?person}
				OPTIONAL {?founder map:foundersRegion ?foundersRegion}
			}
		}
	}	
	UNION
	{
		SELECT * WHERE 
		{ 
			?editedSettlement a map:EditedSettlement; 
							time:before " + url_ + @"; 
							dc:source ?source; 
							time:hasBeginning ?date.
			?date time:year ?year.
            ?source a dcterms:bibliographicResource; dc:identifier ?sourceURL; dc:title ?sourceTitle. 
      		OPTIONAL {?source dc:creator ?sourceCreator.}
			OPTIONAL {?editedSettlement map:settlementType ?settlementType. ?settlementType geonames:name ?typeName}
			OPTIONAL {?editedSettlement rdfs:partOf ?region. ?region geonames:name ?regionName}
            OPTIONAL {?editedSettlement geonames:name ?editedName.}
			OPTIONAL {?editedSettlement geonames:alternateName ?alternateName.}
			OPTIONAL {?editedSettlement geonames:population ?population.}
			OPTIONAL {?editedSettlement map:populationFemalesCount ?populationFemales.}
			OPTIONAL {?editedSettlement map:populationMalesCount ?populationMales.}
			OPTIONAL {?editedSettlement map:settlementType ?settlementType. ?settlementType geonames:name ?typeName}
		}
	}
}";
        }



        /// \fn public static string Years()
        ///
        /// \brief  Запрос наименьшего и наибольшего упоминаего годов
        /// \code       
        ///SELECT ?min ?max
        ///WHERE
        ///{
        ///		{
        ///			SELECT (MIN(?year) AS ?minEditedYear) (MAX(?year) AS ?maxEditedYear)
        ///			WHERE 
        ///			{ 
        ///			  ?editedSettlement a map:EditedSettlement; time:hasBeginning ?date.
        ///			  ?date time:year ?year.  	  
        ///			}        
        ///		}
        ///		{
        ///			SELECT (MIN(?minIndSetYear) AS ?minSetYear) (MAX(?maxIndSetYear) AS ?maxSetYear)
        ///			WHERE 
        ///			{ 
        ///			  ?settlement a map:Settlement.
        ///			  OPTIONAL {?settlement time:hasBeginning ?wasFound. ?wasFound time:year ?wasFoundYear} 
        ///		 
        ///			  ## костыль
        ///		  	  ## если поля не существовали, MIN возвращал 1 вместо NULL, поэтому нарочно кастую к невозможным значениям, чтобы потом сравнить с существующими (см. другие два BIND) 
        ///			  BIND (IF (!BOUND(?wasFound), "3000"^^xsd:gYear, ?wasFoundYear) as ?minIndSetYear)
        ///			  BIND (IF (!BOUND(?wasFound), "-3000"^^xsd:gYear, ?wasFoundYear) as ?maxIndSetYear)
        ///			}
        ///		}
        ///		## выбрать наименьшую упоминаемую дату среди *наименьшей даты основания* и *наименьшей даты изменения*
        ///		BIND (IF (?minSetYear < ?minEditedYear, ?minSetYear, ?minEditedYear) as ?min)
        ///		BIND (IF (?maxSetYear > ?maxEditedYear, ?maxSetYear, ?maxEditedYear) as ?max)
        ///}
        /// \endcode
        /// \author Denis
        /// \date   31.03.2019
        ///
        /// \returns    A string.

        public static string Years()
        {
            return GetAllPrefixes() +
@"SELECT ?min ?max
WHERE
{
    {
        SELECT (MIN(?year) AS ?minEditedYear) (MAX(?year) AS ?maxEditedYear)
        WHERE 
    	{ 
          ?editedSettlement a map:EditedSettlement; time:hasBeginning ?date.
      	  ?date time:year ?year.  	  
        }        
  	}
	{
        SELECT (MIN(?minIndSetYear) AS ?minSetYear) (MAX(?maxIndSetYear) AS ?maxSetYear)
        WHERE 
    	{ 
          ?settlement a map:Settlement.
    	  OPTIONAL {?settlement time:hasBeginning ?wasFound. ?wasFound time:year ?wasFoundYear} 
     
      	## костыль
      ## если поля не существовали, MIN возвращал 1 вместо NULL, поэтому нарочно кастую к невозможным значениям, чтобы потом сравнить с существующими (см. другие два BIND) 
		  BIND (IF (!BOUND(?wasFound), " + "\"3000\"" + @"^^xsd:gYear, ?wasFoundYear) as ?minIndSetYear)
		  BIND (IF (!BOUND(?wasFound), " + "\"-3000\"" + @"^^xsd:gYear, ?wasFoundYear) as ?maxIndSetYear)
        }
	}
    ## выбрать наименьшую упоминаемую дату среди *наименьшей даты основания* и *наименьшей даты изменения*
    BIND (IF (?minSetYear < ?minEditedYear, ?minSetYear, ?minEditedYear) as ?min)
	BIND (IF (?maxSetYear > ?maxEditedYear, ?maxSetYear, ?maxEditedYear) as ?max)
}
";
        }



        /// \fn public static string AllSettlementTypes()
        ///
        /// \brief  Запрос всех возможных типов поселений за все периоды
        /// \code       
        /// SELECT ?settlementType ?typeName  WHERE
        /// {
        ///   ?settlementType a map:SettlementTypes; geonames:name ?typeName.
        /// }                                                                                                                                                                                       
        /// \endcode
        /// \author Denis
        /// \date   17.05.2019
        ///
        /// \returns    A string.

        public static string AllSettlementTypes()
        {
            return GetAllPrefixes() +
@"SELECT ?settlementType ?typeName  WHERE
{
    ?settlementType a map:SettlementTypes; geonames:name ?typeName.
}
";
        }



        /// \fn public static string AllRegions()
        ///
        /// \brief  Запрос всех возможных административных единиц (регионов) за все периоды
        /// \code       
        /// SELECT ?region ?regionName  WHERE
        /// {
        ///   ?region a map:Region; geonames:name ?regionName.
        /// }                                                                                                                                                                               
        /// \endcode
        /// \author Denis
        /// \date   17.05.2019
        ///
        /// \returns    A string.

        public static string AllRegions()
        {
            return GetAllPrefixes() +
@"SELECT ?region ?regionName  WHERE
{
    ?region a map:Region; geonames:name ?regionName.
}
";
        }



        /// \fn public static string AllSettlementsName(string subName)
        ///
        /// \brief  Поиск всех поселений, чьи названия включают переданную подстроку
        /// \code       
        ///select distinct ?name
        ///where
        ///{
        ///  ?settlement a map:Settlement; geonames:name ?name.
        ///  FILTER regex(?name, "subName", "i")
        ///}
        /// \endcode
        /// \author Denis
        /// \date   31.03.2019
        ///
        /// \param  subName Подстрока для поиска.
        ///
        /// \returns    A string.

        public static string AllSettlementsName(string subName)
        {
            return GetPrefix("map") + GetPrefix("geonames") +
@"
select distinct ?name
where
{
  ?settlement a map:Settlement; geonames:name ?name.
  FILTER regex(?name, " + '"' + subName + "\", \"i\"" + @")
}
";
        }

        #endregion

        #region Tools

        /// \fn private static string GetObjFromURL(string URL)
        ///
        /// \brief  Получение URL, отделенного от префикса
        /// Выполняет отделение значимой части полного URL от постоянного префикса
        /// Например, полный URL: "http://jena.denhome.keenetic.link/map#Saratov_btjzkyfa_origin", где "http://jena.denhome.keenetic.link/map#" - префикс, а "Saratov_btjzkyfa_origin" - значимая часть.
        /// \warning Если префикса нет в списке всех префиксов, отделить части не получится и будет возвращен переданный полный URL.
        /// \author Denis
        /// \date   31.03.2019
        ///
        /// \param  URL Полный URL сущности.
        ///
        /// \returns    The object from URL.

        private static string GetObjFromURL(string URL)
        {
            string[] input = URL.Split('#');
            string url_ = "";
            foreach (var item in Prefixes)
                if (item.Value.Contains(input[0]))
                {
                    url_ = item.Key + ":" + input[1];
                    break;
                }
            return (url_ != "") ? url_ : URL;
        }



        /// \fn private static string GetPrefix(string key)
        ///
        /// \brief  Составляет строку из префикса и его значения
        ///
        /// \author Denis
        /// \date   31.03.2019
        ///
        /// \param  key Ключ - краткое наименование префикса.
        ///
        /// \returns    The prefix.

        private static string GetPrefix(string key)
        {
            return "PREFIX " + key + ": " + Prefixes[key] + " ";
        }



        /// \fn private static string GetAllPrefixes()
        ///
        /// \brief  Составляет строку из всех префиксов
        ///
        /// \author Denis
        /// \date   31.03.2019
        ///
        /// \returns    all prefixes.

        private static string GetAllPrefixes()
        {
            string t = "";
            foreach (var item in Prefixes)
            {
                t += "PREFIX " + item.Key + ": " + Prefixes[item.Key] + " ";
            }
            return t;
        }

        #endregion
    }

    /// @}
}

/*
 insert data
{
  map:TatishchevskijRegion a map:Region; geonames:name "Татищевский район".
}
     
     
     */

/*
FILTER regex(?name, "^ali", "i")
^ дает указаение, что искомое стоит в начале слова == "Alice"
FILTER regex(str(?mbox), "@work.example")
@ дает указаение, что искомое стоит в конце слова == <mailto:alice@work.example>
i - параметр, указывающий что регистр поиска не важен
ПОИСК (по regex) здесь: https://www.w3.org/TR/rdf-sparql-query/
 */

