use std::cell::{Ref, RefCell};
use std::net::SocketAddr;
use std::rc::Rc;

use actix_http::Extensions;
use actix_router::ResourceDef;
use actix_service::{boxed, IntoNewService, NewService};

use crate::guard::Guard;
use crate::rmap::ResourceMap;
use crate::service::{ServiceRequest, ServiceResponse};

type Guards = Vec<Box<Guard>>;
type HttpNewService<P> =
    boxed::BoxedNewService<(), ServiceRequest<P>, ServiceResponse, (), ()>;

/// Application configuration
pub struct ServiceConfig<P> {
    config: AppConfig,
    root: bool,
    default: Rc<HttpNewService<P>>,
    services: Vec<(
        ResourceDef,
        HttpNewService<P>,
        Option<Guards>,
        Option<Rc<ResourceMap>>,
    )>,
}

impl<P: 'static> ServiceConfig<P> {
    /// Crate server settings instance
    pub(crate) fn new(config: AppConfig, default: Rc<HttpNewService<P>>) -> Self {
        ServiceConfig {
            config,
            default,
            root: true,
            services: Vec::new(),
        }
    }

    /// Check if root is beeing configured
    pub fn is_root(&self) -> bool {
        self.root
    }

    pub(crate) fn into_services(
        self,
    ) -> Vec<(
        ResourceDef,
        HttpNewService<P>,
        Option<Guards>,
        Option<Rc<ResourceMap>>,
    )> {
        self.services
    }

    pub(crate) fn clone_config(&self) -> Self {
        ServiceConfig {
            config: self.config.clone(),
            default: self.default.clone(),
            services: Vec::new(),
            root: false,
        }
    }

    /// Service configuration
    pub fn config(&self) -> &AppConfig {
        &self.config
    }

    /// Default resource
    pub fn default_service(&self) -> Rc<HttpNewService<P>> {
        self.default.clone()
    }

    pub fn register_service<F, S>(
        &mut self,
        rdef: ResourceDef,
        guards: Option<Vec<Box<Guard>>>,
        service: F,
        nested: Option<Rc<ResourceMap>>,
    ) where
        F: IntoNewService<S>,
        S: NewService<
                Request = ServiceRequest<P>,
                Response = ServiceResponse,
                Error = (),
                InitError = (),
            > + 'static,
    {
        self.services.push((
            rdef,
            boxed::new_service(service.into_new_service()),
            guards,
            nested,
        ));
    }
}

#[derive(Clone)]
pub struct AppConfig(pub(crate) Rc<AppConfigInner>);

impl AppConfig {
    pub(crate) fn new(inner: AppConfigInner) -> Self {
        AppConfig(Rc::new(inner))
    }

    /// Set server host name.
    ///
    /// Host name is used by application router aa a hostname for url
    /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
    /// html#method.host) documentation for more information.
    ///
    /// By default host name is set to a "localhost" value.
    pub fn host(&self) -> &str {
        &self.0.host
    }

    /// Returns true if connection is secure(https)
    pub fn secure(&self) -> bool {
        self.0.secure
    }

    /// Returns the socket address of the local half of this TCP connection
    pub fn local_addr(&self) -> SocketAddr {
        self.0.addr
    }

    /// Resource map
    pub fn rmap(&self) -> &ResourceMap {
        &self.0.rmap
    }

    /// Application extensions
    pub fn extensions(&self) -> Ref<Extensions> {
        self.0.extensions.borrow()
    }
}

pub(crate) struct AppConfigInner {
    pub(crate) secure: bool,
    pub(crate) host: String,
    pub(crate) addr: SocketAddr,
    pub(crate) rmap: ResourceMap,
    pub(crate) extensions: RefCell<Extensions>,
}

impl Default for AppConfigInner {
    fn default() -> AppConfigInner {
        AppConfigInner {
            secure: false,
            addr: "127.0.0.1:8080".parse().unwrap(),
            host: "localhost:8080".to_owned(),
            rmap: ResourceMap::new(ResourceDef::new("")),
            extensions: RefCell::new(Extensions::new()),
        }
    }
}