<commit_before>//
//  ChirpRecordingView.swift
//  microjam
//
//  Created by <NAME> on 11/8/17.
//  Copyright © 2017 <NAME>. All rights reserved.
//

import UIKit

/// Storage for a single tail segment which consists of a touch location and a timer for removing it.
struct TailSegment {
    var touch: TouchRecord
    var timer: Timer
}

/// Subclass of ChirpView that enables recording and user interaction
class ChirpRecordingView: ChirpView {
    
    // Recording
    /// Storage for the date that a performance started recording for timing
    var startTime = Date()
    /// True if the view has started a recording (so touches should be recorded)
    var recording = false
    /// Colour to render touches as they are recorded.
    var recordingColour : CGColor?
    /// Animated tail segments for practice mode.
    var tailSegments = [TailSegment]()

    /// Programmatic init getting ready for recording.
    override init(frame: CGRect) {
        super.init(frame: frame)
        print("ChirpRecordingView: Loading programmatically with frame: ", self.frame)
        isMultipleTouchEnabled = true
        isUserInteractionEnabled = true
        clearForRecording() // gets view ready for recording.
    }
    
    /// Initialises view for recording, rather than playback.
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        isMultipleTouchEnabled = true
        isUserInteractionEnabled = true
        clearForRecording() // gets view ready for recording.
    }
}

//MARK: - touch interaction

/// Contains touch interaction and recording functions for ChirpView
extension ChirpRecordingView {
    
    /// Responds to taps in the ChirpView, passes on to superviews and reacts.
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        superview?.touchesBegan(touches, with: event)
        lastPoint = touches.first?.location(in: superview!)
        let size = touches.first?.majorRadius
        
        if recording {
            // draw and record touch if recording
            if (!started) {
                startTime = Date()
                started = true
            }
            swiped = false
            drawDot(at: lastPoint!, withColour: recordingColour ?? DEFAULT_RECORDING_COLOUR)
            recordTouch(at: lastPoint!, withRadius: size!, thatWasMoving:false)
        } else {
            // not recording, add disappearing touches.
            addTailSegment(at: lastPoint!, withSize: size!, thatWasMoving: false)
            draw(tailSegments: tailSegments, withColor: recordingColour!)
        }
        // always make a sound.
        makeSound(at: lastPoint!, withRadius: size!, thatWasMoving: false)
    }

    /// Clips a CGPoint to the bounds of the ChirpRecordingView.
    func clipTouchLocationToBounds(_ point: CGPoint) -> CGPoint {
        let x = max(min(point.x, bounds.width), 0)
        let y = max(min(point.y, bounds.height), 0)
        return CGPoint.init(x: x, y: y)
    }
    
    /// Responds to moving touch signals, responds with sound and recordings.
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let currentPoint = clipTouchLocationToBounds((touches.first?.location(in: superview!))!)
        let size = touches.first?.majorRadius

        if recording {
            // draw and record touch if recording
            swiped = true
            drawLine(from:self.lastPoint!, to:currentPoint, withColour:recordingColour ?? DEFAULT_RECORDING_COLOUR)
            lastPoint = currentPoint
            recordTouch(at: currentPoint, withRadius: size!, thatWasMoving: true)
        } else {
            // not recording, add disappearing touches.
            addTailSegment(at: currentPoint, withSize: size!, thatWasMoving: true)
            draw(tailSegments: tailSegments, withColor: recordingColour!)
            lastPoint = currentPoint
        }
        
        // Always make a sound.
        makeSound(at: currentPoint, withRadius: size!, thatWasMoving: true)
    }
    
    /**
     Adds a touch point to the recording data including whether it was moving
     and the current time.
     **/
    func recordTouch(at point : CGPoint, withRadius radius : CGFloat, thatWasMoving moving : Bool) {
        let time = -1.0 * startTime.timeIntervalSinceNow
        let x = Double(point.x) / Double(frame.size.width)
        let y = Double(point.y) / Double(frame.size.width)
        let z = Double(radius)
        if recording { // only record when recording.
            performance?.recordTouchAt(time: time, x: x, y: y, z: z, moving: moving)
        }
    }
    
    /// Closes the recording and returns the performance.
    func saveRecording() -> ChirpPerformance? {
        recording = false
        guard let output = self.performance,
            let image = self.image else {
                return nil
        }
        output.image = image
        output.performer = UserProfile.shared.profile.stageName
        output.instrument = SoundSchemes.namesForKeys[UserProfile.shared.profile.soundScheme]!
        output.date = Date()
        return output
    }
    
    /// Initialise the ChirpView for a new recording
    func clearForRecording() {
        print("ChirpRecordingView: Clearing for a New Performance")
        recording = false
        started = false
        lastPoint = CG_INIT_POINT
        swiped = false
        image = UIImage()
        performance = ChirpPerformance()
        recordingColour = performance?.colour.cgColor ?? DEFAULT_RECORDING_COLOUR
        playbackColour = performance?.colour.brighterColor.cgColor ?? DEFAULT_PLAYBACK_COLOUR
        openUserSoundScheme()
    }
}

// MARK: Tail segment drawing functions

extension ChirpRecordingView {
    
    /// Add animated tail segment that removes itself after a certain time.
    private func addTailSegment(at point: CGPoint, withSize size: CGFloat, thatWasMoving moving: Bool) {
        let touch = TouchRecord(time: 0, x: Double(point.x), y: Double(point.y), z: Double(size), moving: moving)
        let timer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false) { (timer) in
            if !self.tailSegments.isEmpty {
                self.tailSegments.removeFirst()
                self.draw(tailSegments: self.tailSegments, withColor: self.recordingColour!)
            }
        }
        let tailSegment = TailSegment(touch: touch, timer: timer)
        tailSegments.append(tailSegment)
    }
    
    /// Draws all present tail segments. If there is a recorded image it should draw them on top of that, if not draw from nothing.
    func draw(tailSegments: [TailSegment], withColor color: CGColor) {
        UIGraphicsBeginImageContextWithOptions(frame.size, false, (UIScreen.main).scale)
        guard let context = UIGraphicsGetCurrentContext() else {
            return
        }
        
        // Get the present recorded image.
        if let presentPerf = performance {
            presentPerf.image.draw(in: CGRect(x:0, y:0, width:frame.size.width, height:frame.size.height))
        }
<commit_msg>adapting to optional image in performances<commit_after>//
//  ChirpRecordingView.swift
//  microjam
//
//  Created by <NAME> on 11/8/17.
//  Copyright © 2017 <NAME>. All rights reserved.
//

import UIKit

/// Storage for a single tail segment which consists of a touch location and a timer for removing it.
struct TailSegment {
    var touch: TouchRecord
    var timer: Timer
}

/// Subclass of ChirpView that enables recording and user interaction
class ChirpRecordingView: ChirpView {
    
    // Recording
    /// Storage for the date that a performance started recording for timing
    var startTime = Date()
    /// True if the view has started a recording (so touches should be recorded)
    var recording = false
    /// Colour to render touches as they are recorded.
    var recordingColour : CGColor?
    /// Animated tail segments for practice mode.
    var tailSegments = [TailSegment]()

    /// Programmatic init getting ready for recording.
    override init(frame: CGRect) {
        super.init(frame: frame)
        print("ChirpRecordingView: Loading programmatically with frame: ", self.frame)
        isMultipleTouchEnabled = true
        isUserInteractionEnabled = true
        clearForRecording() // gets view ready for recording.
    }
    
    /// Initialises view for recording, rather than playback.
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        isMultipleTouchEnabled = true
        isUserInteractionEnabled = true
        clearForRecording() // gets view ready for recording.
    }
}

//MARK: - touch interaction

/// Contains touch interaction and recording functions for ChirpView
extension ChirpRecordingView {
    
    /// Responds to taps in the ChirpView, passes on to superviews and reacts.
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        superview?.touchesBegan(touches, with: event)
        lastPoint = touches.first?.location(in: superview!)
        let size = touches.first?.majorRadius
        
        if recording {
            // draw and record touch if recording
            if (!started) {
                startTime = Date()
                started = true
            }
            swiped = false
            drawDot(at: lastPoint!, withColour: recordingColour ?? DEFAULT_RECORDING_COLOUR)
            recordTouch(at: lastPoint!, withRadius: size!, thatWasMoving:false)
        } else {
            // not recording, add disappearing touches.
            addTailSegment(at: lastPoint!, withSize: size!, thatWasMoving: false)
            draw(tailSegments: tailSegments, withColor: recordingColour!)
        }
        // always make a sound.
        makeSound(at: lastPoint!, withRadius: size!, thatWasMoving: false)
    }

    /// Clips a CGPoint to the bounds of the ChirpRecordingView.
    func clipTouchLocationToBounds(_ point: CGPoint) -> CGPoint {
        let x = max(min(point.x, bounds.width), 0)
        let y = max(min(point.y, bounds.height), 0)
        return CGPoint.init(x: x, y: y)
    }
    
    /// Responds to moving touch signals, responds with sound and recordings.
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let currentPoint = clipTouchLocationToBounds((touches.first?.location(in: superview!))!)
        let size = touches.first?.majorRadius

        if recording {
            // draw and record touch if recording
            swiped = true
            drawLine(from:self.lastPoint!, to:currentPoint, withColour:recordingColour ?? DEFAULT_RECORDING_COLOUR)
            lastPoint = currentPoint
            recordTouch(at: currentPoint, withRadius: size!, thatWasMoving: true)
        } else {
            // not recording, add disappearing touches.
            addTailSegment(at: currentPoint, withSize: size!, thatWasMoving: true)
            draw(tailSegments: tailSegments, withColor: recordingColour!)
            lastPoint = currentPoint
        }
        
        // Always make a sound.
        makeSound(at: currentPoint, withRadius: size!, thatWasMoving: true)
    }
    
    /**
     Adds a touch point to the recording data including whether it was moving
     and the current time.
     **/
    func recordTouch(at point : CGPoint, withRadius radius : CGFloat, thatWasMoving moving : Bool) {
        let time = -1.0 * startTime.timeIntervalSinceNow
        let x = Double(point.x) / Double(frame.size.width)
        let y = Double(point.y) / Double(frame.size.width)
        let z = Double(radius)
        if recording { // only record when recording.
            performance?.recordTouchAt(time: time, x: x, y: y, z: z, moving: moving)
        }
    }
    
    /// Closes the recording and returns the performance.
    func saveRecording() -> ChirpPerformance? {
        recording = false
        guard let output = self.performance,
            let image = self.image else {
                return nil
        }
        output.image = image
        output.performer = UserProfile.shared.profile.stageName
        output.instrument = SoundSchemes.namesForKeys[UserProfile.shared.profile.soundScheme]!
        output.date = Date()
        return output
    }
    
    /// Initialise the ChirpView for a new recording
    func clearForRecording() {
        print("ChirpRecordingView: Clearing for a New Performance")
        recording = false
        started = false
        lastPoint = CG_INIT_POINT
        swiped = false
        image = UIImage()
        performance = ChirpPerformance()
        recordingColour = performance?.colour.cgColor ?? DEFAULT_RECORDING_COLOUR
        playbackColour = performance?.colour.brighterColor.cgColor ?? DEFAULT_PLAYBACK_COLOUR
        openUserSoundScheme()
    }
}

// MARK: Tail segment drawing functions

extension ChirpRecordingView {
    
    /// Add animated tail segment that removes itself after a certain time.
    private func addTailSegment(at point: CGPoint, withSize size: CGFloat, thatWasMoving moving: Bool) {
        let touch = TouchRecord(time: 0, x: Double(point.x), y: Double(point.y), z: Double(size), moving: moving)
        let timer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false) { (timer) in
            if !self.tailSegments.isEmpty {
                self.tailSegments.removeFirst()
                self.draw(tailSegments: self.tailSegments, withColor: self.recordingColour!)
            }
        }
        let tailSegment = TailSegment(touch: touch, timer: timer)
        tailSegments.append(tailSegment)
    }
    
    /// Draws all present tail segments. If there is a recorded image it should draw them on top of that, if not draw from nothing.
    func draw(tailSegments: [TailSegment], withColor color: CGColor) {
        UIGraphicsBeginImageContextWithOptions(frame.size, false, (UIScreen.main).scale)
        guard let context = UIGraphicsGetCurrentContext() else {
            return
        }
        
        // Get the present recorded image.
        if let presentPerf = performance,
            let image = presentPerf.image {
            image.draw(in: CGRect(x:0, y:0, width:frame.size.width, height:frame.size.height))
        }

{'fraction_non_alphanumeric': 0.08671171171171171, 'fraction_numerical': 0.002533783783783784, 'mean_word_length': 2.447113051916545, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3375411', 'n_tokens_mistral': 3973, 'n_tokens_neox': 3813, 'n_words': 1196}