/*: some text
##  ⚖️バランス
　バランスゲームをしましょう！\
　このゲームは、iPadを横向きにして、全画状態で実行してください。\
　スタート位置のQRコードを読み取って、iPadを水平に保ちながらゴール位置のQRコードを読み取れればゴールです！\
　水平状態が保てない状態で２秒経ったら、「ゲームオーバーになります」
 * Note:
二人以上でiPadを持ってゲームをするとより楽しめます！
 ---
 */
//#-hidden-code
//
//  See LICENSE folder for this template’s licensing information.
//
//  Abstract:
//  The Swift file containing the source code edited by the user of this playground book.
//
//#-code-completion(everything, hide)
//#-end-hidden-code
import SwiftUI
import CoreMotion
import PlaygroundSupport
import AudioToolbox
import Foundation
import AVFoundation

// 難易度（初期角度2.5度）
//#-editable-code
let angleValue: Double = 2.5
//#-end-editable-code
// 角度（度）をラジアンに変換
let degreeValue = angleValue * .pi / 180.0

struct ContentView: View {
    @ObservedObject var motion = MotionManager()
    @State private var scannedCode: String?
    @State private var gameStarted = false
    @State private var gameEnded = false
    @State private var gameMessage = "スタートコードを読み取ってください"
    
    var body: some View {
        GeometryReader { geometry in
            let screenHeight = geometry.size.height
            let screenWidth = geometry.size.width
            let pitchRange = CGFloat.pi / 2
            let rollRange = CGFloat.pi

            // 🔧 ここでスケーリング倍率を調整（デフォルト1.0 → 2.0に強化）
            let motionScale: CGFloat = 2.0

            // スケール適用してオフセット計算
            let yOffset = (CGFloat(motion.pitch) / pitchRange) * (screenHeight / 2) * motionScale
            let xOffset = (CGFloat(motion.roll) / rollRange) * (screenWidth / 2) * motionScale
            
            // バブルの中央位置を計算
            let bubbleX = screenWidth / 2 + xOffset
            let bubbleY = screenHeight / 2 + yOffset
            
            ZStack {
                // QRコードスキャナー表示
                QRCodeScannerView { result in
                    if result == "START" && !gameStarted {
                        gameStarted = true
                        gameMessage = "ゴールを目指して！"
                        motion.playStartSound() // スタート音
                    } else if result == "GOAL" && gameStarted && !gameEnded {
                        gameEnded = true
                        gameMessage = "🎉おめでとう！ゴールできました！🎉"
                        motion.playGoalSound() // ゴール音
                        motion.stopMotionUpdates() // ←ここでモーション更新を停止
                        motion.gameClear = true
                    }
                }
                .edgesIgnoringSafeArea(.all)
                // 背景色を変更（傾いている場合は赤）
                //Color(motion.isTilted ? .red : .black)
                //    .ignoresSafeArea()
                // 中央の縦線
                Rectangle()
                    .fill(Color.gray.opacity(0.5))
                    .frame(width: 2, height: screenHeight)
                    .position(x: screenWidth / 2, y: screenHeight / 2)

                // 中央の横線
                Rectangle()
                    .fill(Color.gray.opacity(0.5))
                    .frame(width: screenWidth, height: 2)
                    .position(x: screenWidth / 2, y: screenHeight / 2)
                
                Circle()
                    .frame(width: 71, height: 71)
                    .foregroundColor(.white)
                    .position(x: screenWidth / 2, y: screenHeight / 2)
                
                Circle()
                    .frame(width: 70, height: 70)
                    .foregroundColor(.black)
                    .position(x: screenWidth / 2, y: screenHeight / 2)
                
                // バブルの位置と色を設定
                Circle()
                    .frame(width: 50, height: 50)
                    .foregroundColor(motion.isTilted ? .red : .blue) // バブルの色を青/赤に設定
                    .position(x: bubbleX, y: bubbleY)
                    .animation(.easeInOut, value: motion.pitch + motion.roll)
                

                // ゲームメッセージ
                VStack {
                    if !motion.gameOver {
                        Text(gameMessage)
                            .font(.largeTitle)
                            .foregroundColor(gameEnded ? .green : .white)
                            .padding()
                    } else {
                        Text("😭ゲームオーバー!プログラムを再実行しよう")
                            .font(.largeTitle)
                            .foregroundColor(.red)
                            .padding()
                    }
                }
                
                // pitch と roll の値を表示
                VStack {
                    // ピッチ（上下方向）の表示
                    Text(String(format: "Y方向 (Pitch): %.2f度", motion.pitch * 180 / .pi))
                        .font(.largeTitle)
                        .foregroundColor(.white)
                    
                    // ロール（左右方向）の表示
                    Text(String(format: "X方向 (Roll): %.2f度", motion.roll * 180 / .pi))
                        .font(.largeTitle)
                        .foregroundColor(.white)
                }
                .position(x: screenWidth / 2, y: 40)
            }
        }
    }
}

//#-hidden-code
// Playgrounds ライブビューの設定
PlaygroundPage.current.setLiveView(ContentView())
PlaygroundPage.current.needsIndefiniteExecution = true
//#-end-hidden-code

//傾き検出＆サウンド
class MotionManager: ObservableObject {
    private var motionManager = CMMotionManager()
    private var audioPlayer: AVAudioPlayer?
    private var startAudioPlayer: AVAudioPlayer?
    private var goalAudioPlayer: AVAudioPlayer?
    private var tiltTimer: Timer?
    private var timeElapsed: TimeInterval = 0.0 // 傾きが外れた時間を計測
    
    @Published var pitch: Double = 0.0
    @Published var roll: Double = 0.0
    @Published var isTilted: Bool = false
    @Published var gameOver: Bool = false // ゲームオーバーの状態
    @Published var gameClear: Bool = false // ゲームクリアーの状態
    
    init() {
        prepareSound()
        prepareStartSound()   // スタート音を準備
        prepareGoalSound()    // ゴール音を準備
        
        if motionManager.isDeviceMotionAvailable {
            motionManager.deviceMotionUpdateInterval = 0.1
            motionManager.startDeviceMotionUpdates(to: .main) { [weak self] (motion, error) in
                guard let self = self, let motion = motion else { return }
                
                let attitude = motion.attitude
                let orientation = UIDevice.current.orientation
                
                // 向きに応じてpitchとrollを計算
                switch orientation {
                case .landscapeLeft:
                    self.pitch = attitude.roll
                    self.roll = -attitude.pitch
                case .landscapeRight:
                    self.pitch = -attitude.roll
                    self.roll = attitude.pitch
                case .portrait:
                    self.pitch = attitude.pitch
                    self.roll = attitude.roll
                case .portraitUpsideDown:
                    self.pitch = -attitude.pitch
                    self.roll = -attitude.roll
                default:
                    self.pitch = attitude.roll
                    self.roll = -attitude.pitch
                }
                
                // 傾き判定
                if abs(self.pitch) > degreeValue || abs(self.roll) > degreeValue {
                    self.isTilted = true
                    self.startWarningSound()
                    
                    // 傾きが外れた場合にタイマーを開始
                    if self.timeElapsed == 0.0 {
                        self.startTiltTimer()
                    }
                } else {
                    self.isTilted = false
                    self.stopWarningSound()
                    
                    // 傾きが戻った場合にタイマーをリセット
                    self.resetTiltTimer()
                }
            }
        }
    }

    // 傾きが外れたときにタイマーを開始
    private func startTiltTimer() {
        timeElapsed = 0.0
        tiltTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
            self.timeElapsed += 0.1
            if self.timeElapsed >= 2.0 { // 2.0秒経過したらゲームオーバー
                if !self.gameClear  {self.gameOver = true}
            }
        }
    }
    
    // 傾きが戻ったらタイマーをリセット
    private func resetTiltTimer() {
        tiltTimer?.invalidate()
        tiltTimer = nil
        timeElapsed = 0.0
    }
    
    // 警告音を準備
    private func prepareSound() {
        guard let url = Bundle.main.url(forResource: "warning", withExtension: "mp3") else {
            print("warning.mp3 が見つかりません")
            return
        }
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: url)
            audioPlayer?.numberOfLoops = -1 // ループ再生
            audioPlayer?.prepareToPlay()
        } catch {
            print("音声ファイルの読み込みに失敗しました: \(error)")
        }
    }
    // スタート音を準備
    private func prepareStartSound() {
        guard let url = Bundle.main.url(forResource: "voice_start", withExtension: "mp3") else {
            print("voice_start.mp3 が見つかりません")
            return
        }
        do {
            startAudioPlayer = try AVAudioPlayer(contentsOf: url)
            startAudioPlayer?.prepareToPlay()
        } catch {
            print("スタート音声ファイルの読み込みに失敗しました: \(error)")
        }
    }
    
    // ゴール音を準備
    private func prepareGoalSound() {
        guard let url = Bundle.main.url(forResource: "voice_goal", withExtension: "mp3") else {
            print("voice_goal.mp3 が見つかりません")
            return
        }
        do {
            goalAudioPlayer = try AVAudioPlayer(contentsOf: url)
            goalAudioPlayer?.prepareToPlay()
        } catch {
            print("ゴール音声ファイルの読み込みに失敗しました: \(error)")
        }
    }
    
    // 警告音を再生
    private func startWarningSound() {
        if audioPlayer?.isPlaying == false {
            audioPlayer?.play()
        }
    }

    // 警告音を停止
    private func stopWarningSound() {
        if audioPlayer?.isPlaying == true {
            audioPlayer?.stop()
            audioPlayer?.currentTime = 0
        }
    }
    // スタート音を再生
    func playStartSound() {
            startAudioPlayer?.play()
    }
    
    // ゴール音を再生
    func playGoalSound() {
        goalAudioPlayer?.play()
    }
    
    // モーションのアップデートを停止する関数
    public func stopMotionUpdates() {
        motionManager.stopDeviceMotionUpdates()
        stopWarningSound()
        tiltTimer?.invalidate()
    }
}



////////////////////////////
// QRコードスキャナー


struct QRCodeScannerView: UIViewControllerRepresentable {
    var onScan: (String) -> Void

    func makeCoordinator() -> Coordinator {
        Coordinator(onScan: onScan)
    }

    func makeUIViewController(context: Context) -> UIViewController {
        let viewController = ScannerViewController()
        viewController.delegate = context.coordinator
        return viewController
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}

    class Coordinator: NSObject, ScannerViewControllerDelegate {
        var onScan: (String) -> Void

        init(onScan: @escaping (String) -> Void) {
            self.onScan = onScan
        }

        func didFind(code: String) {
            onScan(code)
        }
    }
}


protocol ScannerViewControllerDelegate: AnyObject {
    func didFind(code: String)
}

class ScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
    var captureSession: AVCaptureSession!
    weak var delegate: ScannerViewControllerDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.black
        captureSession = AVCaptureSession()

        guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
        let videoInput: AVCaptureDeviceInput

        do {
            videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
        } catch {
            return
        }

        if captureSession.canAddInput(videoInput) {
            captureSession.addInput(videoInput)
        } else {
            return
        }

        let metadataOutput = AVCaptureMetadataOutput()

        if captureSession.canAddOutput(metadataOutput) {
            captureSession.addOutput(metadataOutput)

            metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            metadataOutput.metadataObjectTypes = [.qr]
        } else {
            return
        }

        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.frame = view.layer.bounds
        previewLayer.videoGravity = .resizeAspectFill

        // カメラの向きを横向き右（landscapeRight）に設定
        previewLayer.connection?.videoOrientation = .landscapeRight

        view.layer.addSublayer(previewLayer)

        captureSession.startRunning()
    }

    func metadataOutput(_ output: AVCaptureMetadataOutput,
                        didOutput metadataObjects: [AVMetadataObject],
                        from connection: AVCaptureConnection) {
        if let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
           let code = metadataObject.stringValue {
            delegate?.didFind(code: code)
            //captureSession.stopRunning()
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        if captureSession.isRunning {
            captureSession.stopRunning()
        }
    }
}

