//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

//! Current meta datas

use crate::blocks::fork_tree::ForkTree;
use crate::constants::*;
use crate::*;
use crate::{DbReadable, DbValue};
use dubp_common_doc::{Blockstamp, CurrencyName};
use durs_dbs_tools::DbError;
use durs_wot::WotId;

#[derive(Clone, Copy, Debug)]
/// Current meta data key
pub enum CurrentMetaDataKey {
    /// Version of the database structure
    DbVersion,
    /// Currency name
    CurrencyName,
    /// Current blockstamp
    CurrentBlockstamp,
    /// Current "blokchain" time
    CurrentBlockchainTime,
    /// Fork tree
    ForkTree,
    /// Greatest wot id
    NextWotId,
    /// Block by member median in current frame
    MedianFrameMember,
}

impl CurrentMetaDataKey {
    /// To u32
    pub fn to_u32(self) -> u32 {
        match self {
            Self::DbVersion => 0,
            Self::CurrencyName => 1,
            Self::CurrentBlockstamp => 2,
            Self::CurrentBlockchainTime => 3,
            Self::ForkTree => 4,
            Self::NextWotId => 5,
            Self::MedianFrameMember => 6,
        }
    }
}

/// Get DB version
pub fn get_db_version<DB: DbReadable>(db: &DB) -> Result<usize, DbError> {
    db.read(|r| {
        if let Some(v) = db
            .get_int_store(CURRENT_METAS_DATAS)
            .get(r, CurrentMetaDataKey::DbVersion.to_u32())?
        {
            if let DbValue::U64(db_version) = v {
                Ok(db_version as usize)
            } else {
                Err(DbError::DBCorrupted)
            }
        } else {
            Err(DbError::DBCorrupted)
        }
    })
}

/// Get currency name
pub fn get_currency_name<DB: DbReadable>(db: &DB) -> Result<Option<CurrencyName>, DbError> {
    db.read(|r| {
        if let Some(v) = db
            .get_int_store(CURRENT_METAS_DATAS)
            .get(r, CurrentMetaDataKey::CurrencyName.to_u32())?
        {
            if let DbValue::Str(curency_name) = v {
                Ok(Some(CurrencyName(curency_name.to_owned())))
            } else {
                Err(DbError::DBCorrupted)
            }
        } else {
            Ok(None)
        }
    })
}

/// Get current blockstamp
#[inline]
pub fn get_current_blockstamp<DB: DbReadable>(db: &DB) -> Result<Option<Blockstamp>, DbError> {
    db.read(|r| get_current_blockstamp_(db, r))
}

/// Get current blockstamp
pub fn get_current_blockstamp_<DB: DbReadable, R: DbReader>(
    db: &DB,
    r: &R,
) -> Result<Option<Blockstamp>, DbError> {
    if let Some(v) = db
        .get_int_store(CURRENT_METAS_DATAS)
        .get(r, CurrentMetaDataKey::CurrentBlockstamp.to_u32())?
    {
        if let DbValue::Blob(current_blockstamp_bytes) = v {
            Ok(Some(
                Blockstamp::from_bytes(current_blockstamp_bytes)
                    .map_err(|_| DbError::DBCorrupted)?,
            ))
        } else {
            Err(DbError::DBCorrupted)
        }
    } else {
        Ok(None)
    }
}

/// Get current common time (also named "blockchain time")
#[inline]
pub fn get_current_common_time<DB: DbReadable>(db: &DB) -> Result<u64, DbError> {
    db.read(|r| get_current_common_time_(db, r))
}

/// Get current common time (also named "blockchain time")
pub fn get_current_common_time_<DB: DbReadable, R: DbReader>(
    db: &DB,
    r: &R,
) -> Result<u64, DbError> {
    if let Some(v) = db
        .get_int_store(CURRENT_METAS_DATAS)
        .get(r, CurrentMetaDataKey::CurrentBlockchainTime.to_u32())?
    {
        if let DbValue::U64(current_common_time) = v {
            Ok(current_common_time)
        } else {
            Err(DbError::DBCorrupted)
        }
    } else {
        Ok(0u64)
    }
}

/// Get fork tree root
pub fn get_fork_tree<DB: DbReadable>(db: &DB) -> Result<ForkTree, DbError> {
    db.read(|r| {
        if let Some(v) = db
            .get_int_store(CURRENT_METAS_DATAS)
            .get(r, CurrentMetaDataKey::ForkTree.to_u32())?
        {
            Ok(DB::from_db_value::<ForkTree>(v)?)
        } else {
            Ok(ForkTree::default())
        }
    })
}

/// Get greatest wot id
#[inline]
pub fn get_greatest_wot_id_<DB: DbReadable, R: DbReader>(db: &DB, r: &R) -> Result<WotId, DbError> {
    if let Some(v) = db
        .get_int_store(CURRENT_METAS_DATAS)
        .get(r, CurrentMetaDataKey::NextWotId.to_u32())?
    {
        if let DbValue::U64(greatest_wot_id) = v {
            Ok(WotId(greatest_wot_id as usize))
        } else {
            Err(DbError::DBCorrupted)
        }
    } else {
        Ok(WotId(0))
    }
}