/**
     * load a playback file for playing back
     * returns true if successfully loaded
     **/
    public bool LoadPlaybackFile(string file)
    {
        List<PlayableLine> lines = new List<PlayableLine>();

        try
        {
            // create reader for file
            this.reader = new StreamReader(file);
            String line = ""; // to hold each line
            // while we have a line
            while((line = this.reader.ReadLine()) != null)
            {
                if (line.Contains("Object")) continue; // skip lines that have the header
                if (line.Contains ("Finger")) continue; // skip FingerDown/Up lines

                String[] tokens = line.Split('\t'); // split on tabs
                if (tokens.Length < 2) continue; // make sure we only process lines with stuff

                // obj tag posn time
                // tokens[2] is posn vector (1,1,1)
                string v = tokens[2];
                // vector string has parentheses; remove them before splitting into numbers
                string[] vs = v.Replace("(", "").Replace (")","").Split(',');
                // format the time string
                // throws formatexception if the format string doesn't exactly match
                // but sometimes milliseconds are one digit, two digits, or three
                // (e.g., 19:58:11.7 or 19:58:11.23 or 19:58:11.813)
                // so here we account for that by checking the length of the time string
                // and setting the number of ms digits accordingly
                string format = "HH:mm:ss.f" + ((tokens[3].Length == 10) ? "" :
                    (tokens[3].Length == 11 ? "f" : "ff"));

                DateTime t = new DateTime();
                try
                {
                    t = DateTime.ParseExact(tokens[3], format,
                        System.Globalization.CultureInfo.InvariantCulture);
                }
                catch (System.FormatException fe)
                {
                    Debug.Log ("ERROR! Line was " + line
                        + "\nException: " + fe.ToString());
                }

                Vector3 vec = new Vector3(
                    float.Parse(vs[0]), float.Parse(vs[1]), float.Parse(vs[2]));

                PlayableLine pl = new PlayableLine(tokens[0], tokens[1],
                    vec, t);
                lines.Add(pl);
            }
        }
        catch (Exception e)
        {
            Debug.Log ("Exception: " + e.ToString());
        }

        // okay now the text file is in a list of playback lines
        // go through and find the actions
        List<PlayableLine> actions = new List<PlayableLine>();
        DateTime start = DateTime.MinValue;
        DateTime prevActionEnd = DateTime.MinValue;
        List<Vector3> movePosns = new List<Vector3>();
        List<Vector3> lightPosns = new List<Vector3>();

        TimeSpan dif = new TimeSpan(0);
        float fdif = 0f;

        Debug.Log (".....converting to actions");
        // go through list of playback lines and condense into actions
        for (int i = 0; i < lines.Count; i++)
        {
            switch (lines[i].GetTag())
            {
                case "Tap":
                    // add tap action
                    // get difference between start time and end of last action
                    // this is how long to wait before starting this action
                    if ( prevActionEnd != DateTime.MinValue )
                    {
                        dif = lines[i].GetStartTime().Subtract(prevActionEnd);
                        fdif = (float) dif.Seconds + (float) (dif.Milliseconds / 1000f);
                    }
                    actions.Add(new PlayableTapAction(lines[i], fdif));
                    prevActionEnd = lines[i].GetStartTime(); // set action time
                break;

                case "DragBegin":
                    start = lines[i].GetStartTime(); // save drag start time for later

                break;

                case "DragMove":
                    movePosns.Add(lines[i].GetPosn()); // add position to move positions
                    lightPosns.Add(new Vector3(lines[i].GetPosn().x, lines[i].GetPosn().y, -3));
                    // and also need the highlight's move positions, which are at a different z
                break;

                case "DragEnd":
                    // get difference between end time and start time
                    // this is the total time the object moves (i.e., move time)
                    // start time should never be the minvalue?? TODO
                    if ( start != DateTime.MinValue ) // if we don't have a start time yet
                    {
                        dif = lines[i].GetStartTime().Subtract(start);
                        fdif = (float) dif.Seconds + (float) (dif.Milliseconds / 1000f);
                    }

                    // get difference between start time and end of last action
                    // this is how long to wait before starting this action
                    float delay = 0f;
                    if ( prevActionEnd != DateTime.MinValue )
                    {
                        TimeSpan delayDif = start.Subtract(prevActionEnd);
                        delay = (float) delayDif.Seconds + (float) (delayDif.Milliseconds / 1000f);
                    }
                    // add move action with object name, tag, end position, and drag time
                    actions.Add(new PlayableMoveAction(lines[i].GetObject(), "Drag",
                        lines[i].GetPosn(), lines[i].GetStartTime(), fdif, delay, movePosns.ToArray(),
                        lightPosns.ToArray()));
                    movePosns.Clear(); // reset move positions list
                    lightPosns.Clear(); // reset light list
                    prevActionEnd = lines[i].GetStartTime(); // set action time
                break;
            }	// end switch
        } // end for

        // add this playback file name and its associated list of actions to master list
        this.playbacks.Add(Path.GetFileNameWithoutExtension(file), actions);
        Debug.Log (".....done creating actions for " + file);
        return true;
    }
        public PlayableMoveAction(PlayableLine line, float moveTime, float delayTime, 
			Vector3[] posns, Vector3[] lightposns)
            : base(line)
        {
            this.moveTime = moveTime;
            this.delayTime = delayTime;
            this.posns = posns;
            this.lightposns = lightposns;
        }
 public PlayableTapAction(PlayableLine line, float delayTime)
     : base(line)
 {
     this.delayTime = delayTime;
 }
 public PlayableLine(PlayableLine line)
 {
     this.obj = line.GetObject();
     this.tag = line.GetTag();
     this.posn = line.GetPosn();
     this.time = line.GetStartTime();
 }