public AliceResponse HandleRequest(AliceRequest request)
        {
            // start
            if (request.IsEnter())
            {
                var currentTime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
                var resp        = request.State.User.LastEnter < currentTime - 30 * 24 * 3600000L
                    ? Phrases.FirstRun.Generate(request)
                    : Phrases.Hi.Generate(request);

                resp.UserStateUpdate.LastEnter = currentTime;
                return(resp);
            }

            // help command
            if (request.HasIntent(Intents.YandexHelp1) || request.HasIntent(Intents.YandexHelp2))
            {
                return(Phrases.Help.Generate(request));
            }

            // exit
            if (request.HasIntent(Intents.Exit))
            {
                var exit = Phrases.Exit.Generate(request);
                exit.Response.EndSession = true;
                return(exit);
            }

            // by letters
            if (request.HasIntent(Intents.ByLetters))
            {
                var w      = request.GetSlot(Intents.ByLetters, Slots.Word);
                var accent = -1;

                if (w.IsNullOrEmpty() && !request.State.Session.LastForm.IsNullOrEmpty())
                {
                    w      = request.State.Session.LastForm;
                    accent = request.State.Session.LastFormAccent;
                }
                else if (!w.IsNullOrEmpty() && _nMorph.WordExists(w))
                {
                    request.State.Session.Clear();
                    request.State.Session.LastWord = w;
                }

                return(ReadByLetters(w, accent).Generate(request));
            }

            // has word
            var filler = new [] { "слово", "слова", "слов", "словом" };
            // workaround
            var startsFromFiller = request.Request.Nlu.Tokens.Count == 2 &&
                                   filler.Contains(request.Request.Nlu.Tokens[0]);

            var hasWord = request.HasSlot(Intents.Main, Slots.Word) ||
                          request.Request.Nlu.Tokens.Count == 1 ||
                          startsFromFiller;

            var changeForm = Slots.GrammemeSlots.Any(s => request.HasSlot(Intents.Main, s));

            // no word / unknown command
            if (
                !hasWord &&
                (
                    request.State.Session.LastWord.IsNullOrEmpty() ||
                    (!changeForm && !request.HasIntent(Intents.ByLetters))
                )
                )
            {
                return(Phrases.UnknownCommand.Generate(request));
            }

            // word command
            if (hasWord)
            {
                request.State.Session.LastWord = request.HasSlot(Intents.Main, Slots.Word)
                    ? request.GetSlot(Intents.Main, Slots.Word)
                    : startsFromFiller
                        ? request.Request.Nlu.Tokens[1]
                        : request.Request.Nlu.Tokens.First();
            }

            // word not exists
            if (!_nMorph.WordExists(request.State.Session.LastWord))
            {
                var resp = Phrases.UnknownWord(request.State.Session.LastWord).Generate(request);
                request.State.Session.Clear();
                return(resp);
            }

            // word exists, find it
            var words = _nMorph.WordInfo(request.State.Session.LastWord);

            // get all slots
            var pos    = ParseEnum <Pos>(request, Slots.Pos);
            var number = ParseEnum <Number>(request, Slots.Number);
            var gender = ParseEnum <Gender>(request, Slots.Gender);
            var @case  = ParseEnum <Case>(request, Slots.Case);
            var tense  = ParseEnum <Tense>(request, Slots.Tense);
            var person = ParseEnum <Person>(request, Slots.Person);

            // filter by pos if possible
            if (pos != Pos.None && words.Any(w => w.Tag.Pos == pos))
            {
                words = words.Where(w => w.Tag.Pos == pos).ToArray();
            }

            // find forms
            var exactForms    = new List <(Word, WordForm)>();
            var nonExactForms = new List <(Word, WordForm)>();

            foreach (var w in words)
            {
                if (!changeForm)
                {
                    // get form from input word
                    var formsFound = w.ExactForms(request.State.Session.LastWord);
                    exactForms.AddRange(formsFound.Select(f => (w, f)));
                }
                else
                {
                    // get form from input data
                    var form = w.ClosestForm(gender, @case, number, tense, person, true);
                    if (form != null)
                    {
                        exactForms.Add((w, form));
                    }
                    else
                    {
                        form = w.ClosestForm(gender, @case, number, tense, person, false);
                        nonExactForms.Add((w, form));
                    }
                }
            }

            // no forms found
            if (exactForms.Count == 0 && nonExactForms.Count == 0)
            {
                return(Phrases.UnknownForm.Generate(request));
            }

            // read all found forms
            var response    = Phrases.StandardButtons;
            var formsToRead = exactForms;

            if (exactForms.Count == 0)
            {
                formsToRead = nonExactForms;
                response   += Phrases.NonExactForms;
            }

            request.State.Session.LastForm       = formsToRead.First().Item2.Word;
            request.State.Session.LastFormAccent = formsToRead.First().Item2.GetAccentIndex();

            for (var i = 0; i < formsToRead.Count; i++)
            {
                var(w, f) = formsToRead[i];
                response += ReadSingleForm(w, f, i == formsToRead.Count - 1);
            }

            return(response.Generate(request));
        }