private void Cell_Click(object sender, EventArgs e)
        {
            TableLayoutPanelCellPosition cell = calendar.GetPositionFromControl((Control)sender);

            if (cell.Column == 0 && cell.Row == 0)
            {
                return;
            }
            Training            training;
            TrainingDetailsMode mode = TrainingDetailsMode.View;
            Button btn = (Button)sender;

            if (btn.Text == "")
            {
                if (!user.isCoach)
                {
                    return;
                }
                training         = new Training();
                mode             = TrainingDetailsMode.New;
                training.coachId = user.id;
                training.date    = date.AddDays(cell.Column - 1).AddHours(cell.Row - 1 + startHour);
            }
            else
            {
                training = localTrainings.Find(t =>
                                               DateTime.Compare(t.date, date.AddDays(cell.Column - 1).AddHours(cell.Row - 1 + startHour)) == 0 &&
                                               t.syncState != SyncState.Deleted);
                if (training == null)
                {
                    return;
                }
                if (user.id == training.coachId)
                {
                    mode = TrainingDetailsMode.Edit;
                }
            }
            Debug.WriteLine("{0}:{1}, mode {2}, coach {3}", cell.Row, cell.Column, mode, training.coachId);
            TrainingDetailsForm details = new TrainingDetailsForm(user, users, training, mode);
            DialogResult        result  = details.ShowDialog();

            if (result == DialogResult.Abort)
            {
                return;
            }
            Cursor = Cursors.WaitCursor;
            trainingsMutex.WaitOne();
            if (result == DialogResult.OK)
            {
                training.name = details.nameEdit.Text;
                localTrainings.Add(training);
                AddTrainingToCell(btn, training);
            }
            else if (result == DialogResult.Ignore)
            {
                training.syncState = SyncState.Deleted;
                if (training.id == -1)
                {
                    localTrainings.Remove(training);
                }
                ClearCell(btn);
            }
            else if (result == DialogResult.Yes)
            {
                training.traineesId.Remove(user.id);
                training.syncState = SyncState.Updated;
            }
            else if (result == DialogResult.No)
            {
                training.traineesId.Add(user.id);
                training.syncState = SyncState.Updated;
            }
            trainingsMutex.Release();
            Cursor = Cursors.Default;
        }
        public async Task <bool> SyncTrainings(User user, List <Training> trainings, Semaphore mutex)
        {
            command.Parameters.Clear();
            command.CommandText = "";
            mutex.WaitOne();
            for (int i = 0; i < trainings.Count; i++)
            {
                switch (trainings[i].syncState)
                {
                case SyncState.Unsynced:
                    AddSyncTrainingQuery(trainings[i], command);
                    break;

                case SyncState.Deleted:
                    AddDeleteTrainingQuery(trainings[i], command);
                    break;

                case SyncState.Updated:
                    AddUpdateTrainingQuery(user, trainings[i], command);
                    break;
                }
            }
            command.CommandText += "SELECT id, name, coach_id, date " +
                                   "FROM Trainings " +
                                   "ORDER BY id;";
            command.CommandType = CommandType.Text;
            await db.FetchDb();

            List <object[]> table = db.ExecuteReader(command);

            command.CommandText = "SELECT * FROM Trainees ORDER BY training_id;";
            List <object[]> trainees = db.ExecuteReader(command);
            await db.CommitDb();

            if (table.Count <= 0)
            {
                Debug.WriteLine("No trainings to sync");
                trainings.Clear();
                return(false);
            }
            trainings.RemoveAll(x => table.FindIndex(t => (long)t[0] == x.id) < 0);
            for (int i = 0; i < table.Count; i++)
            {
                int      idx = trainings.FindIndex(x => x.id == (long)table[i][0]);
                Training t   = idx >= 0 ? trainings[idx] : new Training();
                t.id      = (long)table[i][0];
                t.name    = (string)table[i][1];
                t.coachId = (long)table[i][2];
                t.date    = UnixTimestampToDateTime((long)table[i][3]);
                bool isUserSubLocal  = t.traineesId.FindIndex(x => x == user.id) >= 0;
                int  ti              = trainees.FindIndex(x => (long)x[0] == t.id);
                bool isUserSubRemote = false;
                t.traineesId.Clear();
                if (ti >= 0)
                {
                    for (int j = ti; j < trainees.Count; j++)
                    {
                        if ((long)trainees[j][0] != t.id)
                        {
                            break;
                        }
                        t.traineesId.Add((long)trainees[j][1]);
                        if (user.id == (long)trainees[j][1])
                        {
                            isUserSubRemote = true;
                        }
                    }
                }
                t.syncState = isUserSubRemote == isUserSubLocal ? SyncState.Synced : SyncState.Updated;
                Debug.WriteLine("Training fetched {0}, {1}, {2}, {3}", t.id, t.name, t.coachId, t.date.ToString());
                if (idx < 0)
                {
                    trainings.Add(t);
                }
            }
            Debug.WriteLine("Load trainings");
            mutex.Release();
            return(true);
        }