double evaluateFingerObstacleCost(FingerState state, float minPreparation, NoteChord obsNote, float startPosition, int startHeight, int pitch)
            {
                float  deltaX   = startPosition - Piano.KeyPositions[pitch];
                float  deltaY   = startHeight - Piano.getKeyHeight(pitch);
                double distance = Math.Min(Math.Sqrt(deltaX * deltaX + deltaY * deltaY), 5) + 1;

                float prepareTime = startTime - minPreparation;

                float importance = NotationUtils.getNoteImportanceInChord(obsNote, state.Pitch);

                debug += string.Format("O{0},{1},{2},{3};", state.Pitch, prepareTime, state.Release, state.Press);

                if (prepareTime >= state.Release)
                {
                    double coeff = CostCoeff.FINGER_MOVE_SPEED_PUNISH * Math.Pow(0.1, (startTime - state.Release) / minPreparation);
                    return(coeff * distance);
                }
                else if (prepareTime >= state.Press)
                {
                    double speedCost  = CostCoeff.FINGER_MOVE_SPEED_PUNISH * distance;
                    double cutoffCost = CostCoeff.NOTE_CUTOFF_PUNISH * importance * (state.Release - prepareTime) / (state.Release - state.Press);

                    return(speedCost + cutoffCost);
                }
                else
                {
                    return(CostCoeff.OMIT_KEY_PUNISH * importance);
                }
            }
            double evaluateSingleArmCost(HandConfig.Range lastWrist, HandConfig.Range currentWrist, FingerState[] lastFs, FingerState[] currentFs, int hand)
            {
                double cost = 0;

                // wrist offset punish

                //		by middle
                cost  += Math.Abs(currentWrist.middle - lastWrist.middle) * CostCoeff.WRIST_OFFSET_MIDDLE_PUNISH / timeUnit;
                debug += string.Format("FM: {0}\\n", Math.Abs(currentWrist.middle - lastWrist.middle) / timeUnit);

                //		by range
                if (!(lastWrist.low < currentWrist.high && lastWrist.high > currentWrist.low))
                {
                    cost  += Math.Min(Math.Abs(currentWrist.low - lastWrist.high), Math.Abs(lastWrist.low - currentWrist.high)) * CostCoeff.WRIST_OFFSET_RANGE_PUNISH / timeUnit;
                    debug += string.Format("FR: {0}\\n", Math.Min(Math.Abs(currentWrist.low - lastWrist.high), Math.Abs(lastWrist.low - currentWrist.high)) / timeUnit);
                }

                // finger speed punish
                if (lastFs != null)
                {
                    List <int> fingers = new List <int>();
                    List <int> pitches = new List <int>();

                    foreach (var pair in fingerChord)
                    {
                        int finger = (int)pair.Value * hand;
                        if (finger > 0)
                        {
                            int first  = (int)Math.Floor(finger / 10f) - 1;
                            int second = finger % 10 - 1;

                            if (first > 0)
                            {
                                fingers.Add(first);
                                pitches.Add(pair.Key);
                            }
                            fingers.Add(second);
                            pitches.Add(pair.Key);
                        }
                    }

                    for (int i = 0; i < fingers.Count; ++i)
                    {
                        int finger = fingers[i];
                        int pitch  = pitches[i];
                        //float position = Piano.KeyPositions[pitch];

                        FingerState state = lastFs[finger];

                        float minimumPreparationTime = s_BenchmarkDuration * 2 * HandConfig.MinimumPreparationRate;

                        // self obstacle
                        if (state.Index >= 0)
                        {
                            NoteChord obsNote = s_NoteSeq[state.Index];
                            cost += evaluateFingerObstacleCost(state, minimumPreparationTime * 2, obsNote, state.Position, state.Height, pitch);
                        }

                        // other obstacles
                        if (finger > 0)
                        {
                            for (int of = 1; of < 5; ++of)
                            {
                                // ignore self finger
                                if (of == finger)
                                {
                                    continue;
                                }

                                FingerState obsState = lastFs[of];
                                if (obsState.Index >= 0)
                                {
                                    int height = Piano.getKeyHeight(pitch);

                                    // allow ring finger on black cross pinky finger on white
                                    if (finger == 3 && of == 4 && height > obsState.Height)
                                    {
                                        continue;
                                    }

                                    float startPosition = obsState.Position + (finger - of) * hand;
                                    int   startHeight   = obsState.Height;

                                    float targetPosition = currentFs[finger].Position;

                                    if (state.Index >= 0)
                                    {
                                        startHeight = state.Height;

                                        if (finger * hand < of * hand)
                                        {
                                            startPosition = Math.Min(startPosition, state.Position);
                                        }
                                        else
                                        {
                                            startPosition = Math.Max(startPosition, state.Position);
                                        }
                                    }

                                    //debug += string.Format("of: {0}, {1}, {2}, {3}\\n", of, startPosition, obsState.Position, targetPosition);

                                    if ((targetPosition - obsState.Position) * (obsState.Position - startPosition) <= 0)
                                    {
                                        continue;
                                    }

                                    NoteChord obsNote = s_NoteSeq[obsState.Index];
                                    cost += evaluateFingerObstacleCost(obsState, minimumPreparationTime, obsNote, startPosition, startHeight, pitch);

                                    //debug += "O" + ((of + 1) * hand).ToString() + ","/* + (startTime - minimumPreparationTime - obsState.Release).ToString() + ";"*/;
                                }
                            }
                        }
                    }
                }

                // TODO:

                return(cost);
            }
            static FingerState[] generateFingerStates(FingerState[] parentStates, int hand, FingerChord chord, NoteChord nc, int index)
            {
                FingerState[] states = null;

                if (parentStates != null)
                {
                    states = new FingerState[parentStates.Length];
                    Array.Copy(parentStates, states, states.Length);
                }

                foreach (var pair in chord)
                {
                    int finger = (int)pair.Value * hand;
                    if (finger > 0)
                    {
                        if (states == null)
                        {
                            states = Enumerable.Repeat(new FingerState {
                                Press = -10000f, Release = -10000f, Index = -1, Height = 0
                            }, 5).ToArray();
                            for (int i = 0; i < states.Length; ++i)
                            {
                                states[i].Position = (HandConfig.WristNaturePosition + i - 2) * hand;
                            }
                        }

                        Note note = nc.notes[pair.Key];
                        UnityEngine.Debug.Assert(note != null, "note and finger chord mismatch");

                        float position = Piano.KeyPositions[pair.Key];
                        int   height   = Piano.getKeyHeight(pair.Key);

                        int first  = (int)Math.Floor(finger / 10f) - 1;
                        int second = finger % 10 - 1;

                        if (first >= 0)
                        {
                            states[first].Press    = note.start;
                            states[first].Release  = note.start;
                            states[first].Pitch    = pair.Key;
                            states[first].Position = position;
                            states[first].Height   = height;
                            states[first].Index    = index;
                        }

                        UnityEngine.Debug.Assert(second >= 0 && second < 5, "invalid finger value");
                        {
                            states[second].Press    = note.start;
                            states[second].Release  = note.start + note.duration;
                            states[second].Pitch    = pair.Key;
                            states[second].Position = position;
                            states[second].Height   = height;
                            states[second].Index    = index;

                            //fixFingerStatesObstacle(ref states, hand, second);
                        }
                    }
                }

                return(states);
            }