use std::sync::{
    Arc,
    atomic::{AtomicBool, Ordering},
};

use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};

use crate::{data::BaseData, storage::visitor_data::VisitorData, utils::version::SemVer};

pub(crate) struct Visitor {
    data: Arc<RwLock<VisitorData>>,
    is_unique_identifier: AtomicBool,
}

impl Clone for Visitor {
    fn clone(&self) -> Self {
        Self {
            data: self.data.clone(),
            is_unique_identifier: self.is_unique_identifier().into(),
        }
    }
}

impl Visitor {
    pub fn new() -> Self {
        return Self {
            data: Arc::new(RwLock::new(VisitorData::new())),
            is_unique_identifier: AtomicBool::new(false),
        };
    }

    pub fn update_last_activity_time(&self) {
        self.data.read().update_last_activity_time();
    }

    pub fn data(&self) -> &Arc<RwLock<VisitorData>> {
        return &self.data;
    }

    pub fn rdata(&self) -> RwLockReadGuard<'_, VisitorData> {
        self.data.read()
    }

    pub fn wdata(&self) -> RwLockWriteGuard<'_, VisitorData> {
        self.data.write()
    }

    pub fn is_unique_identifier(&self) -> bool {
        return self.is_unique_identifier.load(Ordering::Relaxed);
    }

    pub fn add_data(&self, data: impl Iterator<Item = BaseData>, overwrite: bool) {
        let mut vd = self.wdata();
        for d in data {
            match d {
                BaseData::AssignedVariation(av) => vd.assigned_variations.put(av, overwrite),
                BaseData::Browser(b) => vd.browser.set(b, overwrite),
                BaseData::CBScores(cbs) => vd.cbscores.set(Box::new(cbs), overwrite),
                BaseData::Conversion(c) => vd.conversions.push(c),
                BaseData::Cookie(c) => vd.cookie.set(Box::new(c)),
                BaseData::CustomData(cd) => vd.custom_data_map.put(cd, overwrite),
                BaseData::Device(d) => vd.device.set(d, overwrite),
                BaseData::ForcedVariation(fv) => vd.forced_variations.put(fv),
                BaseData::Geolocation(g) => vd.geolocation.set(Box::new(g), overwrite),
                BaseData::KcsHeat(kcs) => vd.kcs_heat.set(Box::new(kcs)),
                BaseData::OperatingSystem(os) => vd.operating_system.set(os, overwrite),
                BaseData::PageView(pv) => vd.page_view_visits.overwrite(pv),
                BaseData::PageViewVisit(pvv) => vd.page_view_visits.merge(pvv),
                BaseData::Personalization(p) => vd.personalizations.put(p, overwrite),
                BaseData::UniqueIdentifier(ui) => self.is_unique_identifier.store(ui.value, Ordering::Relaxed),
                BaseData::UserAgent(ua) => vd.user_agent.set(ua),
                BaseData::VisitorVisits(vv) => vd.visitor_visits.set(Box::new(vv), overwrite),
                BaseData::ApplicationVersion(av) => {
                    vd.application_version.set_opt(SemVer::parse(&av.version), overwrite)
                }
            }
        }
    }
}

// TODO: Add unit tests
