/*
    Copyright 2015-2016 Amebis

    This file is part of ZRCola.

    ZRCola 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.

    ZRCola 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 ZRCola. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include <zrcola/character.h>
#include <zrcola/common.h>

#include <WinStd/COM.h>
#include <WinStd/Win.h>

#include <adoint.h>
#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>


namespace ZRCola {
    ///
    /// Source database
    ///
    class DBSource
    {
    public:
        ///
        /// Character sequence
        ///
        class charseq {
        public:
            int rank;         ///< Sequence rank
            std::wstring str; ///< Sequence string

            inline charseq()
            {
            }

            inline charseq(_In_ int _rank, _In_z_ const wchar_t *_str) :
                rank(_rank),
                str (_str)
            {
            }

            ///
            /// Functor to compare two sequences by `rank`, and `str` members respectively
            ///
            struct less_rank_str {
                inline bool operator()(_In_ const charseq& a, _In_ const charseq& b) const
                {
                         if (a.rank < b.rank) return true;
                    else if (a.rank > b.rank) return false;
                    else if (a.str  < b.str ) return true;
                    else                      return false;
                }
            };
        };


        ///
        /// Translation
        ///
        class translation {
        public:
            wchar_t chr;                ///< Composed character
            charseq decomp;             ///< Decomposed sequence
        };


        ///
        /// Key sequence
        ///
        class keyseq {
        public:
            ///
            /// Key code
            ///
            struct keycode {
                wchar_t key;            ///< Key
                bool shift;             ///< Shift modifier
                bool ctrl;              ///< Ctrl modifier
                bool alt;               ///< Alt modifier

                ///
                /// Translates keycode from Slovenian to English keyboard
                ///
                inline static wchar_t translate_slen(_In_ wchar_t key)
                {
                    switch (key) {
                    case L'Z': return L'Y';
                    case L'Y': return L'Z';
                    case  191: return  189;
                    case  189: return  191;
                    default  : return key;
                    }
                }
            };

        public:
            wchar_t chr;                ///< Character
            std::vector<keycode> seq;   ///< Key sequence
        };


        ///
        /// Language
        ///
        class language {
        public:
            ZRCola::langid_t id;        ///< Language ID
            std::wstring name;          ///< Language name
        };


        ///
        /// Language Character
        ///
        class langchar {
        public:
            wchar_t chr;                ///> Character
            ZRCola::langid_t lang;      ///< Language ID
        };


        ///
        /// Character group
        ///
        class chrgrp {
        public:
            int id;                             ///< Character group ID
            int rank;                           ///< Character group rank
            std::wstring name;                  ///< Character group name
            std::wstring chars;                 ///< Character group characters
            std::vector<unsigned __int16> show; ///< Bit vector if particular character is displayed initially
        };


        ///
        /// Character
        ///
        class character {
        public:
            inline character()
            {
                chr = 0;
                cat.data[0] = 0;
                cat.data[1] = 0;
            }

            inline character(_In_ const character &othr) :
                chr  (othr.chr),
                cat  (othr.cat),
                desc (othr.desc),
                terms(othr.terms),
                rel  (othr.rel)
            {
            }

            inline bool operator==(_In_ const character &othr) const
            {
                return
                    chr   == othr.chr   &&
                    cat   == othr.cat   &&
                    desc  == othr.desc  &&
                    terms == othr.terms &&
                    rel   == othr.rel;
            }

            inline bool operator!=(_In_ const character &othr) const
            {
                return !operator==(othr);
            }

            wchar_t chr;                    ///< Character
            ZRCola::chrcatid_t cat;         ///< Category ID
            std::wstring desc;              ///< Character description
            std::set<std::wstring> terms;   ///< Search terms
            std::wstring rel;               ///< Related characters
        };


        ///
        /// Character bank
        ///
        class character_bank : public std::vector<std::unique_ptr<character> >
        {
        public:
            character_bank();
            void build_related();

        protected:
            class build_related_worker : public winstd::win_handle
            {
            public:
                build_related_worker(_In_ const character_bank *cb, _In_ size_type from, _In_ size_type to);

                inline void join()
                {
                    if (m_h)
                        WaitForSingleObject(m_h, INFINITE);
                }

            private:
                // This class is non-copyable AND non-movable
                build_related_worker(_Inout_ build_related_worker &othr);
                build_related_worker(_Inout_ build_related_worker &&othr);
                build_related_worker& operator=(_Inout_ build_related_worker &othr);
                build_related_worker& operator=(_Inout_ build_related_worker &&othr);

            protected:
                unsigned int process();
                static unsigned int __stdcall process(_In_ void *param);

            protected:
                const character_bank *m_cb;
                size_type m_from, m_to;
                winstd::heap m_heap;
            };

        protected:
            std::set<std::wstring> m_ignore;
        };


        ///
        /// Character description index key comparator
        ///
        struct character_desc_idx_less : public std::binary_function<std::wstring, std::wstring, bool>
        {
            inline bool operator()(const std::wstring& _Left, const std::wstring& _Right) const
            {
                size_t
                    _Left_len  = _Left .size(),
                    _Right_len = _Right.size();

                int r = _wcsncoll(_Left.c_str(), _Right.c_str(), std::min<size_t>(_Left_len, _Right_len));
                     if (r         != 0         ) return r < 0;
                else if (_Left_len <  _Right_len) return true;

                return false;
            }
        };


        ///
        /// Character description index
        ///
        class character_desc_idx : public std::map<std::wstring, std::vector<wchar_t>, character_desc_idx_less>
        {
        public:
            static void parse_keywords(const wchar_t *str, std::set<std::wstring> &terms);
            void add_keywords(const std::set<std::wstring> &terms, wchar_t chr, size_t sub = 0);
            inline void add_keywords(const wchar_t *str, wchar_t chr, size_t sub = 0)
            {
                std::set<std::wstring> terms;
                parse_keywords(str, terms);
                add_keywords(terms, chr, sub);
            }

            void save(ZRCola::textindex<wchar_t, wchar_t, unsigned __int32> &idx) const;

        protected:
            inline void add_keyword(const std::wstring &term, wchar_t chr)
            {
                iterator idx = find(term);
                if (idx == end()) {
                    // New keyword.
                    insert(std::make_pair(term, std::vector<wchar_t>(1, chr)));
                } else {
                    // Append to existing keyword.
                    std::vector<wchar_t> &val = idx->second;
                    for (auto i = val.cbegin(), i_end = val.cend(); ; ++i) {
                        if (i == i_end) {
                            // End-of-values reached. Append character.
                            val.push_back(chr);
                            break;
                        } else if (*i == chr) {
                            // Character already among the values.
                            break;
                        }
                    }
                }
            }
        };


        ///
        /// Character category
        ///
        class chrcat {
        public:
            ZRCola::chrcatid_t id;      ///> Category ID
            int rank;                   ///< Character category rank
            std::wstring name;          ///< Character category name
        };


        ///
        /// Character tag
        ///
        class chrtag {
        public:
            wchar_t chr;                ///> Character
            int tag;                    ///< Tag ID
        };


        ///
        /// Tag name
        ///
        class tagname {
        public:
            int tag;                    ///< Tag ID
            std::map<LCID, std::list<std::wstring> > names; ///< Names
        };


    public:
        DBSource();
        virtual ~DBSource();

        ///
        /// Opens the database
        ///
        /// \param[in] filename  File name of the MDB database.
        ///
        /// \returns
        /// - true when open succeeds
        /// - false otherwise
        ///
        bool Open(LPCTSTR filename);

        ///
        /// Logs errors in database connections
        ///
        void LogErrors() const;

        ///
        /// Is recordset at end
        ///
        /// \param[out] rs  Recordset with results
        ///
        /// \returns
        /// - true when at end
        /// - false otherwise
        ///
        static inline bool IsEOF(const winstd::com_obj<ADORecordset>& rs)
        {
            VARIANT_BOOL eof = VARIANT_TRUE;
            return FAILED(rs->get_EOF(&eof)) || eof ? true : false;
        }

        ///
        /// Gets number of records in a recordset
        ///
        /// \param[out] rs  Recordset with results
        ///
        /// \returns Number of records
        ///
        static inline size_t GetRecordsetCount(const winstd::com_obj<ADORecordset>& rs)
        {
            ADO_LONGPTR count;
            return SUCCEEDED(rs->get_RecordCount(&count)) ? count : (size_t)-1;
        }

        ///
        /// Splits string to individual keywords
        ///
        /// \param[in ] str       String
        /// \param[out] keywords  Array of keywords
        ///
        /// \returns
        /// - true when successful
        /// - false otherwise
        ///
        static bool GetKeywords(const wchar_t *str, std::vector< std::wstring > &keywords);

        ///
        /// Gets boolean from ZRCola.zrc database
        ///
        /// \param[in]  f    Data field
        /// \param[out] val  Output boolean value
        ///
        /// \returns
        /// - true when successful
        /// - false otherwise
        ///
        bool GetValue(const winstd::com_obj<ADOField>& f, bool& val) const;

        ///
        /// Gets integer from ZRCola.zrc database
        ///
        /// \param[in]  f    Data field
        /// \param[out] val  Output integer value
        ///
        /// \returns
        /// - true when successful
        /// - false otherwise
        ///
        bool GetValue(const winstd::com_obj<ADOField>& f, int& val) const;

        ///
        /// Gets string from ZRCola.zrc database
        ///
        /// \param[in]  f    Data field
        /// \param[out] val  Output string value
        ///
        /// \returns
        /// - true when successful
        /// - false otherwise
        ///
        bool GetValue(const winstd::com_obj<ADOField>& f, std::wstring& val) const;

        ///
        /// Gets encoded Unicode character from ZRCola.zrc database
        ///
        /// \param[in]  f    Data field
        /// \param[out] chr  Output character
        ///
        /// \returns
        /// - true when successful
        /// - false otherwise
        ///
        bool GetUnicodeCharacter(const winstd::com_obj<ADOField>& f, wchar_t& chr) const;

        ///
        /// Gets encoded Unicode string from ZRCola.zrc database
        ///
        /// \param[in]  f    Data field
        /// \param[out] str  Output string
        ///
        /// \returns
        /// - true when successful
        /// - false otherwise
        ///
        bool GetUnicodeString(const winstd::com_obj<ADOField>& f, std::wstring& str) const;

        ///
        /// Gets language ID from ZRCola.zrc database
        ///
        /// \param[in]  f     Data field
        /// \param[out] lang  Language
        ///
        /// \returns
        /// - true when successful
        /// - false otherwise
        ///
        bool GetLanguage(const winstd::com_obj<ADOField>& f, langid_t& lang) const;

        ///
        /// Gets character category ID from ZRCola.zrc database
        ///
        /// \param[in]  f   Data field
        /// \param[out] cc  Character category
        ///
        /// \returns
        /// - true when successful
        /// - false otherwise
        ///
        bool GetChrCat(const winstd::com_obj<ADOField>& f, chrcatid_t& cc) const;

        ///
        /// Gets tag names from ZRCola.zrc database
        ///
        /// \param[in]  f      Data field
        /// \param[out] names  Output names
        ///
        /// \returns
        /// - true when successful
        /// - false otherwise
        ///
        bool GetTagNames(const winstd::com_obj<ADOField>& f, LCID lcid, std::list<std::wstring>& names) const;

        ///
        /// Returns character translations
        ///
        /// \param[out] rs  Recordset with results
        ///
        /// \returns
        /// - true when query succeeds
        /// - false otherwise
        ///
        bool SelectTranslations(winstd::com_obj<ADORecordset>& rs) const;

        ///
        /// Returns translation data
        ///
        /// \param[in]  rs  Recordset with results
        /// \param[out] t   Translation
        ///
        /// \returns
        /// - true when succeeded
        /// - false otherwise
        ///
        bool GetTranslation(const winstd::com_obj<ADORecordset>& rs, translation& t) const;

        ///
        /// Returns key sequences
        ///
        /// \param[out] rs  Recordset with results
        ///
        /// \returns
        /// - true when query succeeds
        /// - false otherwise
        ///
        bool SelectKeySequences(winstd::com_obj<ADORecordset>& rs) const;

        ///
        /// Returns key sequence data
        ///
        /// \param[in]  rs  Recordset with results
        /// \param[out] ks  Key sequence
        ///
        /// \returns
        /// - true when succeeded
        /// - false otherwise
        ///
        bool GetKeySequence(const winstd::com_obj<ADORecordset>& rs, keyseq& ks) const;

        ///
        /// Returns languages
        ///
        /// \param[out] rs  Recordset with results
        ///
        /// \returns
        /// - true when query succeeds
        /// - false otherwise
        ///
        bool SelectLanguages(winstd::com_obj<ADORecordset>& rs) const;

        ///
        /// Returns language data
        ///
        /// \param[in]  rs    Recordset with results
        /// \param[out] lang  Language
        ///
        /// \returns
        /// - true when succeeded
        /// - false otherwise
        ///
        bool GetLanguage(const winstd::com_obj<ADORecordset>& rs, language& lang) const;

        ///
        /// Returns language character
        ///
        /// \param[out] rs  Recordset with results
        ///
        /// \returns
        /// - true when query succeeds
        /// - false otherwise
        ///
        bool SelectLanguageCharacters(winstd::com_obj<ADORecordset>& rs) const;

        ///
        /// Returns language character data
        ///
        /// \param[in]  rs    Recordset with results
        /// \param[out] lang  Language character data
        ///
        /// \returns
        /// - true when succeeded
        /// - false otherwise
        ///
        bool GetLanguageCharacter(const winstd::com_obj<ADORecordset>& rs, langchar& lc) const;

        ///
        /// Returns character groups
        ///
        /// \param[out] rs  Recordset with results
        ///
        /// \returns
        /// - true when query succeeds
        /// - false otherwise
        ///
        bool SelectCharacterGroups(winstd::com_obj<ADORecordset>& rs) const;

        ///
        /// Returns character group data
        ///
        /// \param[in]  rs    Recordset with results
        /// \param[out] cg    Character group
        ///
        /// \returns
        /// - true when succeeded
        /// - false otherwise
        ///
        bool GetCharacterGroup(const winstd::com_obj<ADORecordset>& rs, chrgrp& cg) const;

        ///
        /// Returns characters
        ///
        /// \param[out] rs  Recordset with results
        ///
        /// \returns
        /// - true when query succeeds
        /// - false otherwise
        ///
        bool SelectCharacters(winstd::com_obj<ADORecordset>& rs) const;

        ///
        /// Returns character data
        ///
        /// \param[in]  rs    Recordset with results
        /// \param[out] chr   Character
        ///
        /// \returns
        /// - true when succeeded
        /// - false otherwise
        ///
        bool GetCharacter(const winstd::com_obj<ADORecordset>& rs, character& chr) const;

        ///
        /// Returns character categories
        ///
        /// \param[out] rs  Recordset with results
        ///
        /// \returns
        /// - true when query succeeds
        /// - false otherwise
        ///
        bool SelectCharacterCategories(winstd::com_obj<ADORecordset>& rs) const;

        ///
        /// Returns character category data
        ///
        /// \param[in]  rs    Recordset with results
        /// \param[out] cc    Character category
        ///
        /// \returns
        /// - true when succeeded
        /// - false otherwise
        ///
        bool GetCharacterCategory(const winstd::com_obj<ADORecordset>& rs, chrcat& cc) const;

        ///
        /// Returns character tags
        ///
        /// \param[out] rs  Recordset with results
        ///
        /// \returns
        /// - true when query succeeds
        /// - false otherwise
        ///
        bool SelectCharacterTags(winstd::com_obj<ADORecordset>& rs) const;

        ///
        /// Returns character tag data
        ///
        /// \param[in]  rs    Recordset with results
        /// \param[out] cc    Character tag
        ///
        /// \returns
        /// - true when succeeded
        /// - false otherwise
        ///
        bool GetCharacterTag(const winstd::com_obj<ADORecordset>& rs, chrtag& tc) const;

        ///
        /// Returns tag names
        ///
        /// \param[out] rs  Recordset with results
        ///
        /// \returns
        /// - true when query succeeds
        /// - false otherwise
        ///
        bool SelectTagNames(winstd::com_obj<ADORecordset>& rs) const;

        ///
        /// Returns tag name data
        ///
        /// \param[in]  rs    Recordset with results
        /// \param[out] tn    Tag name
        ///
        /// \returns
        /// - true when succeeded
        /// - false otherwise
        ///
        bool GetTagName(const winstd::com_obj<ADORecordset>& rs, tagname& tn) const;

    protected:
        std::basic_string<TCHAR> m_filename;    ///< Database filename
        winstd::com_obj<ADOConnection> m_db;       ///< Database
        _locale_t m_locale;                     ///< Database locale

        winstd::com_obj<ADOCommand> m_comCharacterGroup;   ///< ADO Command for GetCharacterGroup subquery
        winstd::com_obj<ADOParameter> m_pCharacterGroup1;  ///< \c m_comCharacterGroup parameter
    };
};
