// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of OpenEthereum.

// OpenEthereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// OpenEthereum 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 General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with OpenEthereum.  If not, see <http://www.gnu.org/licenses/>.

use ethcore::client::Mode as ClientMode;
use journaldb::Algorithm;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::{de::from_reader, ser::to_string};
use std::{fs::File, io::Write, path::Path, time::Duration};

#[derive(Clone)]
pub struct Seconds(Duration);

impl Seconds {
    pub fn value(&self) -> u64 {
        self.0.as_secs()
    }
}

impl From<u64> for Seconds {
    fn from(s: u64) -> Seconds {
        Seconds(Duration::from_secs(s))
    }
}

impl From<Duration> for Seconds {
    fn from(d: Duration) -> Seconds {
        Seconds(d)
    }
}

impl Into<Duration> for Seconds {
    fn into(self) -> Duration {
        self.0
    }
}

impl Serialize for Seconds {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_u64(self.value())
    }
}

impl<'de> Deserialize<'de> for Seconds {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let secs = u64::deserialize(deserializer)?;
        Ok(Seconds::from(secs))
    }
}

#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase", tag = "mode")]
pub enum Mode {
    Active,
    Passive {
        #[serde(rename = "mode.timeout")]
        timeout: Seconds,
        #[serde(rename = "mode.alarm")]
        alarm: Seconds,
    },
    Dark {
        #[serde(rename = "mode.timeout")]
        timeout: Seconds,
    },
    Offline,
}

impl Into<ClientMode> for Mode {
    fn into(self) -> ClientMode {
        match self {
            Mode::Active => ClientMode::Active,
            Mode::Passive { timeout, alarm } => ClientMode::Passive(timeout.into(), alarm.into()),
            Mode::Dark { timeout } => ClientMode::Dark(timeout.into()),
            Mode::Offline => ClientMode::Off,
        }
    }
}

impl From<ClientMode> for Mode {
    fn from(mode: ClientMode) -> Mode {
        match mode {
            ClientMode::Active => Mode::Active,
            ClientMode::Passive(timeout, alarm) => Mode::Passive {
                timeout: timeout.into(),
                alarm: alarm.into(),
            },
            ClientMode::Dark(timeout) => Mode::Dark {
                timeout: timeout.into(),
            },
            ClientMode::Off => Mode::Offline,
        }
    }
}

#[derive(Serialize, Deserialize)]
pub struct UserDefaults {
    pub is_first_launch: bool,
    #[serde(with = "algorithm_serde")]
    pub pruning: Algorithm,
    pub tracing: bool,
    pub fat_db: bool,
    #[serde(flatten)]
    mode: Mode,
}

impl UserDefaults {
    pub fn mode(&self) -> ClientMode {
        self.mode.clone().into()
    }

    pub fn set_mode(&mut self, mode: ClientMode) {
        self.mode = mode.into();
    }
}

mod algorithm_serde {
    use journaldb::Algorithm;
    use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S>(algorithm: &Algorithm, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        algorithm.as_str().serialize(serializer)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Algorithm, D::Error>
    where
        D: Deserializer<'de>,
    {
        let pruning = String::deserialize(deserializer)?;
        pruning
            .parse()
            .map_err(|_| Error::custom("invalid pruning method"))
    }
}

impl Default for UserDefaults {
    fn default() -> Self {
        UserDefaults {
            is_first_launch: true,
            pruning: Algorithm::OverlayRecent,
            tracing: false,
            fat_db: false,
            mode: Mode::Active,
        }
    }
}

impl UserDefaults {
    pub fn load<P>(path: P) -> Result<Self, String>
    where
        P: AsRef<Path>,
    {
        match File::open(path) {
            Ok(file) => match from_reader(file) {
                Ok(defaults) => Ok(defaults),
                Err(e) => {
                    warn!("Error loading user defaults file: {:?}", e);
                    Ok(UserDefaults::default())
                }
            },
            _ => Ok(UserDefaults::default()),
        }
    }

    pub fn save<P>(&self, path: P) -> Result<(), String>
    where
        P: AsRef<Path>,
    {
        let mut file: File =
            File::create(path).map_err(|_| "Cannot create user defaults file".to_owned())?;
        file.write_all(to_string(&self).unwrap().as_bytes())
            .map_err(|_| "Failed to save user defaults".to_owned())
    }
}
