// See http://d.hatena.ne.jp/fits/20090815/1250352312

// How to run:
//   nodeNum の数だけ solverNode を起動する。
//
// console 1
//   $ scala -cp . perm.solverNode 9001
//
// console 2
//   $ scala -cp . perm.solverNode 9002
//
// メインを起動する。
// console 0
//   $ scala -cp . perm.solverCenter
//

package perm

import scala.actors.Actor
import scala.actors.Actor._
import scala.actors.remote.Node
import scala.actors.remote.RemoteActor.select

object solverCenter {
    def main(args: Array[String]) {
        val name = Symbol("Magicsquare") // シンボルの設定
        val host = "localhost"
        val defaultPort = 9000
        val port = if (args.length > 0) Integer.parseInt(args(0)) else defaultPort  //ポート番号の設定

        Console.println("Start client.")
        val mazeSize = 3                // EDIT: 魔法陣の次元 3, 4, ...
        val nodeNum = 2                 // EDIT: 分散させるノード数
        val jobDiv =  9                 // EDIT: 1 ノード当たりへの割り当てリクエスト数
        // 細かくしすぎると、magicsquareCheck の枝刈り方法のバグで全解が得られなくなる。

        val store = new Store(nodeNum * jobDiv)
        store.start

        // 分散ノードのリストを作る
        // ここでは　localhost 上に port だけを変えたものにしてある。
        // 適宜 環境に応じて変更すること。
        val nodeList = List.range(1, nodeNum+1).map(x => (Node(host, port + x)))
        println(nodeList)

        val f = perm.Fact.factsMem( mazeSize * mazeSize )
        for (j <- 1 to jobDiv) {
            for (i <- 1 to nodeNum) {
                // len, start, end
                val len = f / (nodeNum * jobDiv)
                val start = f * (i - 1) / nodeNum + f * (j - 1) / (nodeNum * jobDiv)
                val query = List(BigInt(mazeSize), start, start + len)

                val node = nodeList.apply(i - 1)
                actor {
                    // RemoteActor に送信
                    println("Sending " + query + " to " + node.toString)
                    select(node, name) !?(query) match {
                        // RemoteActor からの返信を待つ
                        case (ans, size) => {
                                store !StoreAnswer(query, node,
                                                   ans.asInstanceOf[List[Int]], size.asInstanceOf[Int])
                                store !StoreNodeFinish(query, node)
                            }
                        case x:Any => {
                                store !StoreError(x, query, node)
                                exit
                            }
                    }
                }
            }
        }

        // 全ノードの終了を待つ
        while((store !?StoreCanRead) != true) { Thread.sleep(1000 * 1) }
        // 解を表示
        val ans = store.ls
        println("ANSWER= " + ans.mkString("\n\t"))
        println("TOTLA = " + ans.size)
    }
}

case class StoreAnswer(query:List[BigInt], node:Node, ls:List[Int],size:Int)
case class StoreNodeFinish(query:List[BigInt], node:Node)
case class StoreError(x:Any, query:List[BigInt], node:Node)
case class StoreCanRead()

// remote からの答えを蓄える
class Store(n:Int) extends Actor {

    var runningCount = n  // 処理中のタスク数
    var ls = List[Int]()  // 回答

    override def act(): Unit = loop()

    def loop(): Unit = {
        receive {
            case x:StoreAnswer => {
                    ls = ls ++ x.ls
                    println("Got answer = " + x.size +
                            " for " + x.query + " from " + x.node.toString )
                    loop
                }
            case x:StoreNodeFinish => {
                    runningCount -= 1
                    println("Finish for " + x.query + " from " + x.node.toString)
                    println("runningCount = " + runningCount)
                    loop
                }
            case StoreCanRead => {
                    if (runningCount > 0) {
                        reply(false)
                        loop
                    } else {
                        reply(true)
                    }
                }
            case err:StoreError => {
                    println(err.x)
                    println(" for " + err.query + " from " + err.node.toString)
                    loop
                }
            case x => {
                    println("---------")
                    println(x)
                    loop
                }
        }
    }
}