/*
 * Copyright IBM Corporation 2018
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import Foundation
import LoggerAPI
import Configuration
import MongoKitten

#if os(Linux)
    import Glibc
#else
    import Darwin
#endif

// We will load our database configuration from config.json, but this can be
// overridden with the TFB_DB_CONFIG environment variable.
let configurationFilename: String = ProcessInfo.processInfo.environment["TFB_DB_CONFIG"] ?? "config.json"
let manager = ConfigurationManager().load(file: configurationFilename, relativeFrom: .pwd).load(.environmentVariables)

let dbHost = manager["DB_HOST"] as? String ?? manager["mongodb:host"] as? String ?? "localhost"
let dbPort = Int32(manager["DB_PORT"] as? String != nil ? Int(manager["DB_PORT"] as! String) ?? 27017 : manager["mongodb:port"] as? Int ?? 27017)
let dbName = manager["mongodb:name"] as? String ?? "hello_world"
let dbUser = manager["mongodb:user"] as? String ?? "benchmarkdbuser"
let dbPass = manager["mongodb:password"] as? String ?? "benchmarkdbpass"

let dbRows = 10000
let maxValue = 10000

var myDatabase: MongoKitten.Database?
var world: MongoKitten.Collection?
var fortune: MongoKitten.Collection?

func connectToDB() throws {
    let connectString = "mongodb://\(dbHost):\(dbPort)/\(dbName)"
    Log.info("Connect string = \(connectString)")
    //myDatabase = try MongoKitten.Database("mongodb://\(dbUser):\(dbPass)@\(dbHost):\(dbPort)/\(dbName)")
    myDatabase = try MongoKitten.Database(connectString)
    guard let myDatabase = myDatabase else {
        throw AppError.MongoError("Nil MongoDB connection to \(connectString)")
    }
    guard myDatabase.server.isConnected else {
        throw AppError.MongoError("Not connected to \(connectString)")
    }
    world = myDatabase["world"]
    fortune = myDatabase["fortune"]
}

// Return a random number within the range of rows in the database
func randomNumberGenerator(_ maxVal: Int) -> Int {
    #if os(Linux)
        return Int(random() % maxVal) + 1
    #else
        return Int(arc4random_uniform(UInt32(maxVal))) + 1
    #endif
}

// Allow construction of a Fortune from a MongoKitten Document
extension Fortune {
    init(document: Document) throws {
        if let id = Int(document["_id"]), let message = String(document["message"]) {
            self.init(id: id, message: message)
        } else {
            throw AppError.DataFormatError("Expected fields of Fortune document could not be retreived")
        }
    }
}

func getFortunes() throws -> [Fortune] {
    guard let fortune = fortune else {
        throw AppError.MongoError("Fortune collection not initialized")
    }
    
//    let allFortunes: [Document] = Array(try fortune.find())
    let allFortunes = try fortune.find()
    let resultFortunes: [Fortune] = try allFortunes.map { try Fortune.init(document: $0) }
    return resultFortunes
}

// Get a random row (range 1 to 10,000) from DB: id(int),randomNumber(int)
// Convert to object using object-relational mapping (ORM) tool
// Serialize object to JSON - example: {"id":3217,"randomNumber":2149}
func getRandomRow() throws -> [String:Int] {
    guard let world = world else {
        throw AppError.MongoError("World collection not initialized")
    }

    let rnd = randomNumberGenerator(dbRows)
    let result = try world.findOne("_id" == rnd)
    guard let document = result else {
        throw AppError.DataFormatError("World entry id=\(rnd) not found")
    }
    guard let id = Int(document["_id"]), let randomNumber = Int(document["randomNumber"]) else {
        throw AppError.DataFormatError("Expected fields of World document could not be retreived")
    }
    return ["id":id, "randomNumber":Int(randomNumber)]
}

// Updates a row of World to a new value.
func updateRandomRow() throws -> [String:Int] {
    guard let world = world else {
        throw AppError.MongoError("World collection not initialized")
    }
    
    let rnd = randomNumberGenerator(dbRows)
    let rndValue = randomNumberGenerator(maxValue)
    let document = try world.findAndUpdate("_id" == rnd, with: ["randomNumber": rndValue])
    guard let id = Int(document["_id"]), let randomNumber = Int(document["randomNumber"]) else {
        throw AppError.DataFormatError("Expected fields of World document could not be retreived")
    }
    return ["id":id, "randomNumber":Int(randomNumber)]
}



