// Copyright Rivtower Technologies LLC.
//
// 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.

use cita_types::Address;
use serde_derive::Deserialize;
use std::collections::BTreeMap;
use std::io::Read;

#[derive(Debug, PartialEq, Deserialize)]
pub struct Test(BTreeMap<String, Vm>);

impl IntoIterator for Test {
    type Item = <BTreeMap<String, Vm> as IntoIterator>::Item;
    type IntoIter = <BTreeMap<String, Vm> as IntoIterator>::IntoIter;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

impl Test {
    pub fn load<R>(reader: R) -> Result<Self, serde_json::Error>
    where
        R: Read,
    {
        serde_json::from_reader(reader)
    }
}

#[derive(Debug, PartialEq, Deserialize)]
pub struct Vm {
    #[serde(rename = "callcreates")]
    pub call_creates: Option<Vec<Callcreates>>,

    #[serde(rename = "env")]
    pub env: Env,

    #[serde(rename = "exec")]
    pub exec: Exec,

    #[serde(rename = "gas")]
    pub gas: Option<String>,

    #[serde(rename = "logs")]
    pub logs: Option<String>,

    #[serde(rename = "out")]
    pub out: Option<String>,

    #[serde(rename = "post")]
    pub post: Option<State>,

    #[serde(rename = "pre")]
    pub pre: Option<State>,
}

#[derive(Debug, PartialEq, Deserialize)]
pub struct Callcreates {}

#[derive(Debug, PartialEq, Deserialize)]
pub struct Env {
    #[serde(rename = "currentCoinbase")]
    pub current_coinbase: Address,

    #[serde(rename = "currentDifficulty")]
    pub current_difficulty: String,

    #[serde(rename = "currentGasLimit")]
    pub current_gas_limit: String,

    #[serde(rename = "currentNumber")]
    pub current_number: String,

    #[serde(rename = "currentTimestamp")]
    pub current_timestamp: String,
}

#[derive(Debug, PartialEq, Deserialize)]
pub struct Exec {
    #[serde(rename = "address")]
    pub address: Address,

    #[serde(rename = "caller")]
    pub caller: Address,

    #[serde(rename = "code")]
    pub code: String,

    #[serde(rename = "data")]
    pub data: String,

    #[serde(rename = "gas")]
    pub gas: String,

    #[serde(rename = "gasPrice")]
    pub gas_price: String,

    #[serde(rename = "origin")]
    pub origin: Address,

    #[serde(rename = "value")]
    pub value: String,
}

#[derive(Debug, PartialEq, Deserialize, Clone)]
pub struct Account {
    pub balance: String,
    pub code: String,
    pub nonce: String,
    pub storage: BTreeMap<String, String>,
}

#[derive(Debug, PartialEq, Deserialize, Clone)]
pub struct State(BTreeMap<Address, Account>);

impl IntoIterator for State {
    type Item = <BTreeMap<Address, Account> as IntoIterator>::Item;
    type IntoIter = <BTreeMap<Address, Account> as IntoIterator>::IntoIter;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

#[cfg(test)]
mod tests {
    use super::{Address, Test};
    use std::fs;

    #[test]
    fn test_json_tests_parse() {
        let f = fs::File::open("../jsondata/VMTests/vmArithmeticTest/add0.json").unwrap();
        let t = Test::load(f).unwrap();
        assert!(t.0.contains_key("add0"));
        let v = &t.0["add0"];

        assert_eq!(
            v.env.current_coinbase,
            Address::from("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba")
        );
        assert_eq!(v.env.current_difficulty, String::from("0x0100"));
        assert_eq!(v.env.current_gas_limit, String::from("0x0f4240"));
        assert_eq!(v.env.current_number, String::from("0x00"));
        assert_eq!(v.env.current_timestamp, String::from("0x01"));

        assert_eq!(
            v.exec.address,
            Address::from("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6")
        );
        assert_eq!(
            v.exec.caller,
            Address::from("0xcd1722f2947def4cf144679da39c4c32bdc35681")
        );
        assert_eq!(v.exec.data, String::from("0x"));
        assert_eq!(v.exec.gas, String::from("0x0186a0"));
        assert_eq!(v.exec.gas_price, String::from("0x5af3107a4000"));
        assert_eq!(
            v.exec.origin,
            Address::from("0xcd1722f2947def4cf144679da39c4c32bdc35681")
        );
        assert_eq!(v.exec.value, String::from("0x0de0b6b3a7640000"));

        assert_eq!(v.gas, Some(String::from("0x013874")));
        assert_eq!(
            v.logs,
            Some(String::from(
                "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
            ))
        );
        assert_eq!(v.out, Some(String::from("0x")));

        if let Some(data) = &v.post {
            let post_account =
                &data.0[&Address::from("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6")];
            assert_eq!(post_account.balance, String::from("0x0de0b6b3a7640000"));
            assert_eq!(
                post_account.storage[&String::from("0x00")],
                String::from("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe")
            )
        }

        if let Some(data) = &v.pre {
            let pre_account = &data.0[&Address::from("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6")];
            assert_eq!(pre_account.balance, String::from("0x0de0b6b3a7640000"));
        }
    }
}
