text:
extern crate lazy_static;

use anyhow::{anyhow, Result};
use clap::{Parser, Subcommand};
use directories::UserDirs;
use enum_default::EnumDefault;
use flexi_logger::{AdaptiveFormat, Logger};
use home_dir::HomeDirExt;
use itertools::Itertools;
use log::{debug, error, info, trace};
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use shlex::Shlex;
use std::{
    env,
    ffi::{OsStr, OsString},
    fs::File,
    io::{self, BufRead, BufReader, BufWriter, Write},
    path::{Path, PathBuf},
    process::Command,
};
use tmux_interface::TmuxCommand;

#[derive(Debug, Parser)]
#[clap(author, version, about)]
#[clap(propagate_version = true)]
#[clap(arg_required_else_help = true)]
#[clap(dont_collapse_args_in_usage = true)]
/// Options for tm, they are closely resembling (ought to be compatible to) the ones from the old shell script.
struct Cli {
    /// subcommands
    #[clap(subcommand)]
    command: Option<Commands>,

    /// List running sessions
    ///
    /// This is basically `tmux ls`
    #[clap(short, display_order = 10, takes_value = false)]
    ls: bool,

    /// Open SSH session to the destination
    ///
    /// The arguments are the destinations for `ssh(1)`, which may be
    /// specified as either \[user@]hostname or a URI of the form
    /// ssh://\[user@]hostname\[:port].
    ///
    /// When multiple destinations are specified, they are all opened
    /// into seperate tmux windows (not sessions!).
    #[clap(
        short = 's',
        display_order = 15,
        multiple_values = true,
        min_values = 1
    )]
    sshhosts: Option<Vec<String>>,

    /// Open multi SSH sessions to hosts, synchronizing input.
    ///
    /// The same details for the arguments as for [Cli::sshhosts] applies.
    ///
    /// When multiple destinations are specified, they are all opened
    /// into one single tmux window with many panes in there.
    /// Additionally, the "synchronize-input" option is turned on, so
    /// that anything entered will be send to every host.
    #[clap(
        short = 'm',
        display_order = 20,
        multiple_values = true,
        min_values = 1
    )]
    multihosts: Option<Vec<String>>,

    /// Open as second session to the same set of hosts as an existing
    /// one, instead of attaching to the existing
    ///
    /// This way more than one session to the same set of ssh
    /// destinations can be opened and used.
    #[clap(short = 'n', display_order = 25)]
    second: bool,

    /// Group session - attach to an existing session, but keep
    /// seperate window config
    ///
    /// This will show the same set of windows, but allow different
    /// handling of the session according to client. This way one
    /// client could display the first, another the second window.
    /// Without this option, a second client would always show the
    /// same content as the first.
    #[clap(short = 'g', display_order = 30)]
    group: bool,

    /// Kill a session, Session name as shown by ls
    #[clap(short = 'k', display_order = 35)]
    kill: Option<String>,

    /// Setup session according to config file in TMDIR
    #[clap(short = 'c', display_order = 40)]
    config: Option<String>,

    #[clap(flatten)]
    verbose: clap_verbosity_flag::Verbosity,

    /// Either plain tmux session name, or session/file found in TMDIR
    ///
    /// If this exists as a tmux session, it behaves like `tmux
    /// attach`. Otherwise it checks TMDIR for existance of a config
    /// file and will open a session as specified in there.
    #[clap(display_order = 50)]
    session: Option<String>,

    /// Value to use for replacing in session files (see their help)
    #[clap(display_order = 55)]
    replace: Option<String>,

    /// Break a multi-session pane into single windows
    #[clap(short = 'b', display_order = 60)]
    breakw: Option<String>,

    /// Join multiple windows into one single one with many panes
    #[clap(short = 'j', display_order = 65)]
    joinw: Option<String>,
}

#[derive(Subcommand, Debug, PartialEq)]
/// Holds list of subcommands in use for tm
enum Commands {
    /// List running sessions
    ///
    /// This is basically `tmux ls`
    #[clap(display_order = 10)]
    Ls {},

    /// Open SSH session to the destination
    ///
    /// When multiple destinations are specified, they are all opened
    /// into seperate tmux windows (not sessions!).
    #[clap(display_order = 15)]
    S {
        /// Target destinations for `ssh(1)`, which may be specified as
        /// either \[user@]hostname or a URI of the form
        /// ssh://\[user@]hostname\[:port].
        #[clap(multiple_values = true, required = true)]
        hosts: Vec<String>,
    },

    /// Open multi SSH sessions to hosts, synchronizing input.
    ///
    /// When multiple destinations are specified, they are all opened
    /// into one single tmux window and many panes in there.
    /// Additionally, the "synchronize-input" option is turned on, so
    /// that anything entered will be send to every host.
    #[clap(display_order = 20)]
    Ms {
        /// List of target destinations for `ssh(1)`, the same details
        /// for the arguments as for [Cli::sshhosts] applies.
        #[clap(multiple_values = true, required = true)]
        hosts: Vec<String>,
    },

    /// Kill a session
    #[clap(display_order = 25)]
    K {
        /// Session name as shown by ls to kill, same as [Session](Cli::kill)
        #[clap(required = true)]
        sesname: String,
    },

    /// Break a multi-session pane into single windows
    #[clap(display_order = 30)]
    B {
        /// Sessiion name for which to break panes into windows
        #[clap(required = true)]
        sesname: String,
    },

    /// Join multiple windows into one single one with many panes
    #[clap(display_order = 35)]
    J {
        /// Sessiion name for which to join windows into panes
        #[clap(required = true)]
        sesname: String,
    },
}

////////////////////////////////////////////////////////////////////////
// Macros
////////////////////////////////////////////////////////////////////////
/// Help setting up static variables based on user environment.
///
/// We allow the user to configure certain properties/behaviours of tm
/// using environment variables. To reduce boilerplate in code, we use a
/// macro for setting them. We use [mod@`lazy_static`] to define them as
/// global variables, so they are available throughout the whole program -
/// they aren't going to change during runtime, ever, anyways.
///
/// # Examples
///
/// ```
/// # fn main() {
/// static ref TMPDIR: String = fromenvstatic!(asString "TMPDIR", "/tmp");
/// static ref TMSORT: bool = fromenvstatic!(asBool "TMSORT", true);
/// static ref TMWIN: u8 = fromenvstatic!(asU32 "TMWIN", 1);
/// # }
/// ```
macro_rules! fromenvstatic {
    (asString $envvar:literal, $default:expr) => {
        match env::var($envvar) {
            Ok(val) => val,
            Err(_) => $default.to_string(),
        }
    };
    (asBool $envvar:literal, $default:literal) => {
        match env::var($envvar) {
            Ok(val) => match val.to_ascii_lowercase().as_str() {
                "true" => true,
                "false" => false,
                &_ => {
                    // Test run as "cargo test -- --nocapture" will print this
                    if cfg!(test) {
                        println!(
                            "Variable {} expects true or false, not {}, assuming {}",
                            $envvar, val, $default
                        );
                    }
                    error!(
                        "Variable {} expects true or false, not {}, assuming {}",
                        $envvar, val, $default
                    );
                    return $default;
                }
            },
            Err(_) => $default,
        }
    };
    (asU32 $envvar:literal, $default:literal) => {
        match env::var($envvar) {
            Ok(val) => {
                return val.parse::<u32>().unwrap_or_else(|err| {
                    if cfg!(test) {
                        println!(
                            "Couldn't parse variable {} (value: {}) as number (error: {}), assuming {}",
                            $envvar, val, err, $default
                        );
                    }
                    error!(
                        "Couldn't parse variable {} (value: {}) as number (error: {}), assuming {}",
                        $envvar, val, err, $default
                    );
                    $default
                });
            }
            Err(_) => $default,
        }
    };
}

////////////////////////////////////////////////////////////////////////

/// Some additional functions for Cli, to make our life easier
impl Cli {
    /// Return a session name
    ///
    /// This checks
    /// - [struct@TMSORT], to maybe sort the hostnames,
    /// - the [Cli::second] option, to maybe add a random number in
    /// the range of [u16] using [rand::thread_rng].
    ///
    /// It also "cleans" the session name, that is, it replaces
    /// spaces, :, " and . with _ (underscores).
    fn session_name_from_hosts(&self) -> Result<String> {
        trace!("In session_name_from_hosts");
        let mut hosts = self.get_hosts()?;
        trace!(
            "Need to build session name from: {:?}, TMSESSHOST: {}",
            hosts,
            *TMSESSHOST
        );

        if *TMSORT {
            hosts.sort();
        }
        hosts.insert(0, self.get_insert());

        if self.second {
            let mut rng = rand::thread_rng();
            let insert: u16 = rng.gen();
            trace!(
                "Second session wanted, inserting {} into session name",
                insert
            );
            hosts.insert(1, insert.to_string());
        }
        let name = hosts.join("_");
        debug!("Generated session name: {}", name);
        Ok(name)
    }

    /// Find (and set) a session name. Appears we have many
    /// possibilities to get at one, depending how we are called.
    fn find_session_name(&self, session: &mut Session) -> Result<String> {
        trace!("find_session_name");
        let possiblename: String = {
            if self.kill.is_some() {
                self.kill.clone().unwrap()
            } else if self.session.is_some() {
                self.session.clone().unwrap()
            } else if self.config.is_some() {
                self.config.clone().unwrap()
            } else if self.sshhosts != None || self.multihosts != None {
                self.session_name_from_hosts()?
            } else if self.breakw.is_some() {
                self.breakw.as_ref().unwrap().clone()
            } else if self.joinw.is_some() {
                self.joinw.as_ref().unwrap().clone()
            } else if self.command != None {
                match &self.command.as_ref().unwrap() {
                    Commands::S { hosts: _ } | Commands::Ms { hosts: _ } => {
                        self.session_name_from_hosts()?
                    }
                    Commands::K { sesname } | Commands::B { sesname } | Commands::J { sesname } => {
                        sesname.to_string()
                    }
                    &_ => "Unknown".to_string(),
                }
            } else {
                "Unhandled command so unknown session name".to_string()
            }
        };
        let sesname = session.set_name(&possiblename)?;
        Ok(sesname.to_string())
    }

    /// Returns a string depending on subcommand called, to adjust
    /// session name with.
    fn get_insert(&self) -> String {
        match &self.sshhosts {
            Some(_) => "s".to_string(),
            None => match &self.multihosts {
                Some(_) => "ms".to_string(),
                None => match &self.command.as_ref().unwrap() {
                    Commands::S { hosts: _ } => 's'.to_string(),
                    Commands::Ms { hosts: _ } => "ms".to_string(),
                    &_ => "".to_string(),
                },
            },
        }
    }

    /// Return a hostlist.
    ///
    /// The list can either be from the s or ms command.
    fn get_hosts(&self) -> Result<Vec<String>> {
        match &self.sshhosts {
            Some(v) => Ok(v.to_vec()),
            None => match &self.multihosts {
                Some(v) => Ok(v.to_vec()),
                None => match &self.command.as_ref().unwrap() {
                    Commands::S { hosts } | Commands::Ms { hosts } => Ok(hosts.clone()),
                    &_ => Err(anyhow!("No hosts supplied, can not get any")),
                },
            },
        }
    }
}

#[derive(EnumDefault, Debug)]
/// Possible Session types
enum SessionType {
    #[default]
    /// Simple - 2 initial lines, followed by 1 to many SSH destinations
    Simple,
    /// Extended - 2 initial lines, followed by 1 to many tmux commands
    Extended,
}

#[derive(Debug, Default)]
/// Store session related information
struct Session {
    /// The session name
    sesname: String,
    /// Should this be "grouped" - shares the same set of windows, new
    /// windows are linked to all sessions in the group, any window
    /// closed is removed from all sessions. But sessions are
    /// seperate, as are their current/previous window and session
    /// options.
    grouped: bool,
    /// The session name when grouped
    gsesname: String,
    /// The path to a session file
    sesfile: PathBuf,
    /// Type of session (file), from extension of file
    sestype: SessionType,
    /// Synchronized session? (Synchronized - input goes to all visible panes in tmux at once)
    synced: bool,
    /// List of SSH Destinations / commands for the session
    targets: Vec<String>,
    /// Token for the string replacement in session files
    replace: Option<String>,
}

impl Session {
    /// Takes a string, applies some cleanup, then stores it as
    /// session name, returning the cleaned up value
    fn set_name(&mut self, newname: &str) -> Result<&String> {
        trace!("Session.set_name(), input: {}", newname);
        // Replace a set of characters we do not want in the session name with _
        self.sesname = newname.replace(&[' ', ':', '"', '.'][..], "_");
        trace!("Session name now: {}", self.sesname);
        Ok(&self.sesname)
    }

    /// Kill session
    fn realkill(&self, tokill: &str) -> Result<bool> {
        trace!("session.realkill()");
        if TmuxCommand::new()
            .has_session()
            .target_session(tokill)
            .output()
            .unwrap()
            .0
            .status
            .success()
        {
            debug!("Asked to kill session {}", tokill);
            if TmuxCommand::new()
                .kill_session()
                .target_session(tokill)
                .output()
                .unwrap()
                .0
                .status
                .success()
            {
                info!("Session {} is no more", tokill);
                Ok(true)
            } else {
                info!("Session {} could not be killed!", tokill);
                Err(anyhow!("Session {} could not be killed!", tokill))
            }
        } else {
            debug!("No such session {}", tokill);
            Err(anyhow!("No such session {}", tokill))
        }
    }

    /// Kill current known session
    fn kill(&self) -> Result<bool> {
        trace!("Session.kill()");
        self.realkill(&self.sesname)
    }

    /// Check if a session exists
    fn exists(&self) -> bool {
        TmuxCommand::new()
            .has_session()
            .target_session(&self.sesname)
            .output()
            .unwrap()
            .0
            .status
            .success()
    }

    /// Attach to a running (or just prepared) tmux session
    fn attach(&mut self) -> Result<bool> {
        trace!("Entering attach()");
        let ret = if self.exists() {
            if self.grouped {
                let mut rng = rand::thread_rng();
                let insert: u16 = rng.gen();
                self.gsesname = format!("{}_{}", insert, self.sesname);
                debug!(
                    "Grouped session wanted, setting new session {}, linking it with {}",
                    self.gsesname, self.sesname
                );
                if cfg!(test) {
                    TmuxCommand::new()
                        .new_session()
                        .detached()
                        .session_name(&self.gsesname)
                        .group_name(&self.sesname)
                        .output()?;
                } else {
                    TmuxCommand::new()
                        .new_session()
                        .session_name(&self.gsesname)
                        .group_name(&self.sesname)
                        .output()?;
                }
                info!(
                    "Removing grouped session {} (not the original!)",
                    &self.gsesname
                );
                if cfg!(test) {
                    println!("Not removing grouped session {}", self.gsesname);
                } else {
                    self.realkill(&self.gsesname)?;
                }
                true
            } else if cfg!(test) {
                println!("Can not attach in test mode");
                match self.sesname.as_str() {
                    "fakeattach" => true,
                    &_ => false,
                }
            } else {
                TmuxCommand::new()
                    .attach_session()
                    .target_session(&self.sesname)
                    .output()?;
                true
            }
        } else {
            false
        };
        trace!("Leaving attach with result {}", ret);
        Ok(ret)
    }

    /// Read and parse a session file, run an action to create the session, then attach to it.
    ///
    /// This will create a new tmux session according to the session file, either a _simple_ or an _extended_
    /// style config file found at `sesfile`.
    ///
    /// # Config file formats
    /// ## Simple
    /// The _simple_ config style is a line based one, with the following properties:
    ///
    /// 1. Session name
    /// 1. Extra tmux command line options, most commonly **NONE**. _(currently options are unsupported in the rust tm version)_
    /// 1. Either an SSH destination (\[user@]hostname) **or** the LIST command.
    /// 1. [...] As many SSH destinations/LIST commands as wanted and needed.
    ///
    /// ### SSH destination
    /// Taken from the ssh(1) manpage:
    /// ssh connects and logs into the specified destination, which may be
    /// specified as either \[user@]hostname or a URI of the form
    /// ssh://\[user@]hostname\[:port].
    ///
    /// ### LIST command
    /// Instead of an SSH destination, the command **LIST** followed by an
    /// argument is also accepted. The argument must be a runnable command
    /// that outputs a list of SSH destinations on stdout and exits
    /// successfully.
    ///
    /// The command will be run in the same directory the simple config
    /// file is found in.
    ///
    /// **Note**: This is recursive, so if output of a command contains
    /// **LIST** again, it will also be executed and its output used.
    /// There is no limit (except stack size) on recursion, so yes, you
    /// can build a loop and then watch tm race to a panic..
    ///
    /// ## Extended
    /// The _extended_ config style is also line based, with the following properties:
    ///
    /// 1. Session name
    /// 1. Extra tmux command line options, most commonly **NONE**. _(currently options are unsupported in the rust tm version)_
    /// 1. Any tmux(1) command with whatever option tmux supports.
    /// 1. [...] As many tmux(1) commands as wanted and needed
    ///
    /// ### Replacement tags
    /// While parsing the commands, the following tags get replaced:
    ///
    /// | TAG     | Replacement
    /// |---------|------------
    /// | SESSION | Session name (first line value)
    /// | TMWIN   | Current window (starts with whatever tmux option base-index has, increases on every new-window found)
    /// | $HOME   | User home directory
    /// | ${HOME} | Same as $HOME
    ///
    /// # Examples
    /// The following will open a tmux session named `examplesession`, connection to two hosts.
    /// ```
    /// examplesession
    /// NONE
    /// ganneff@host1
    /// user@host2
    /// ```
    ///
    /// The following will open a tmux session `anotherexample`,
    /// connecting to at least one host, but possibly more, depending on
    /// how many lines of SSH destinations the `cat foo.list` command will
    /// print to stdout.
    /// ```text
    /// anotherexample
    /// NONE
    /// ganneff@host3
    /// LIST cat foo.list
    /// ```
    ///
    /// The following will open a tmux session `logmon`, with a window
    /// split in 3 panes, one tail-ing the messages log, another
    /// showing current date/time and a third showing htop. It will
    /// forbid tmux to rename the window.
    /// ```text
    /// nagioscfg
    /// NONE
    /// new-session -d -s SESSION -n SESSION ssh -t localhost 'TERM=xterm tail -f /var/log/messages'
    /// split-window -h -p 50 -d -t SESSION:TMWIN ssh -t localhost  'watch -n1 -d date -u'
    /// split-window -v -p 70 -d -t SESSION:TMWIN ssh -t localhost  'TERM=xterm htop'
    /// set-window-option -t SESSION:TMWIN automatic-rename off
    /// set-window-option -t SESSION:TMWIN allow-rename off
    /// ```
    fn read_session_file_and_attach(&mut self) -> Result<()> {
        trace!("Entering read_session_file");
        // Get the path of the session file
        let sesfile = self.sesfile.clone();
        let sesfilepath = sesfile.parent().ok_or_else(|| {
            anyhow!(
                "Could not determine directory for {}",
                self.sesfile.display()
            )
        })?;
        // Check if the file exists
        if !self.sesfile.exists() {
            return Err(anyhow!("Session file {} not found", self.sesfile.display()));
        }

        match self.sesfile.extension().and_then(OsStr::to_str) {
            None => self.sestype = SessionType::Simple,
            Some(v) => match v {
                "cfg" => self.sestype = SessionType::Extended,
                &_ => return Err(anyhow!("Unknown file extension {v}")),
            },
        }

        // Want to read the session config file
        let sesreader = BufReader::new(File::open(&self.sesfile)?);
        // Need session name later
        let mut sesname: String;

        let mut tmwin = *TMWIN;

        for (index, line) in sesreader.lines().enumerate() {
            trace!("Read line {}: {:?}", index + 1, line);
            // Replace token, if exists
            let line = tmreplace(&line?, &self.replace)?;
            debug!("Processing line {}: '{}'", index + 1, line);
            // Action to come depends on line we read and SessionType
            match index {
                0 => {
                    // First line is session name
                    debug!("Possible session name: {}", &line);
                    // Before we get to this place, we already tried
                    // looking for a session with the name the user gave
                    // us as argument. And it did not exist.
                    //
                    // Unlucky us, this first line may NOT be the same
                    // as that session name. So we check again, and if
                    // a session name like this already exists, we try
                    // attaching to it.
                    sesname = self.set_name(&line)?.to_string();
                    if self.exists() {
                        info!("Session matches existing one, attaching");
                        self.attach()?;
                        return Ok(());
                    } else {
                        debug!("Calculated session name: {}", sesname);
                    }
                }
                1 => trace!("Ignoring 2nd line"),
                _ => {
                    debug!("Content line");
                    // Third and following lines are "content", so for
                    // simple configs either hostnames or LIST
                    // commands, for extended ones they are commands.
                    match &self.sestype {
                        SessionType::Simple => {
                            self.targets
                                .append(&mut parse_line(&line, &self.replace, sesfilepath)?)
                        }
                        SessionType::Extended => {
                            if line.contains("new-window") {
                                tmwin += 1;
                            }
                            let modline = line
                                .replace("SESSION", &self.sesname)
                                .replace("$HOME", "~/")
                                .replace("${HOME}", "~/")
                                .replace("TMWIN", &tmwin.to_string())
                                .expand_home()?
                                .into_os_string()
                                .into_string()
                                .expect("String convert failed");
                            self.targets.push(modline);
                        }
                    }
                }
            }
        }
        trace!("Finished parsing session file");
        debug!("Targets: {:#?}", self.targets);
        // Depending on session type, different action will happen
        match &self.sestype {
            SessionType::Simple => {
                // We have a nice set of hosts and a session name, lets set it all up
                self.synced = true;
                self.setup_simple_session()?;
            }
            SessionType::Extended => {
                self.setup_extended_session()?;
            }
        }
        self.attach()?;
        Ok(())
    }

    /// Create a tmux session from an "extended" config.
    ///
    /// This just goes over all entries in [Session::targets] and executes
    /// them using tmux run-shell ability. Whatever the user setup in
    /// .cfg is executed - provided that tmux(1) knows it, ie. it is a
    /// valid tmux command.
    fn setup_extended_session(&mut self) -> Result<bool> {
        trace!("Entering setup_extended_session");
        debug!("Creating session {}", self.sesname);

        if self.targets.is_empty() {
            return Err(anyhow!("No targets setup, can not open session"));
        }

        for mut command in self.targets.clone() {
            debug!("Command: {}", command);
            // The trick with run-shell later is nice, but if we
            // happen to be the very first tmux session to start, it
            // will break with "No tmux server running". So whenever
            // we see a "new-session" command, we setup a fake session
            // which closes itself after one second, just so that one
            // is there and makes run-shell work.
            //
            // Alternative would be parsing the new-session line to
            // correctly run new-session ourself, but I do not want
            // to parse.
            //
            // FIXME: We should check if a tmux is running and only
            // then do the trick.
            let first = command.split_whitespace().next();
            match first {
                Some("new-session") => {
                    debug!("New Session");
                    let tempsesname: String = thread_rng()
                        .sample_iter(&Alphanumeric)
                        .take(30)
                        .map(char::from)
                        .collect();
                    TmuxCommand::new()
                        .new_session()
                        .detached()
                        .session_name(&tempsesname)
                        .window_name("to be killed")
                        .shell_command("sleep 1")
                        .output()?;
                }
                Some(&_) => {
                    debug!("Whatever else");
                }
                None => {}
            }
            command.insert_str(0, "tmux ");
            trace!("Actually running: {}", command);
            let output = TmuxCommand::new()
                .run_shell()
                .shell_command(command)
                .output()?;
            trace!("Shell: {:?}", output);
        }
        Ok(true)
    }

    /// Create a simple tmux session, that is, a session with one or
    /// multiple windows or panes opening SSH connections to a set of
    /// targets.
    ///
    /// Depending on value of session field [Session::synced] it will
    /// setup multiple windows, or one window with multiple panes.
    fn setup_simple_session(&mut self) -> Result<bool> {
        trace!("Entering setup_simple_session");
        debug!("Creating session {}", self.sesname);

        if self.targets.is_empty() {
            return Err(anyhow!("No targets setup, can not open session"));
        }

        // And start the session by opening it with the shell command
        // directly going to the first target
        TmuxCommand::new()
            .new_session()
            .detached()
            .session_name(&self.sesname)
            .window_name(&self.targets[0])
            .shell_command(format!("{} {}", *TMSSHCMD, &self.targets[0]))
            .output()?;

        // Which window are we at? Start with TMWIN, later on count up (if
        // we open more than one)
        let mut wincount = *TMWIN;
        self.setwinopt(wincount, "automatic-rename", "off")?;
        self.setwinopt(wincount, "allow-rename", "off")?;

        // Next check if there was more than one host, if so, open windows/panes
        // for them too.
        if self.targets.len() >= 2 {
            debug!("Got more than 1 target");
            let mut others = self.targets.clone().into_iter();
            // Skip the first, we already opened a connection
            others.next();
            for target in others {
                // For the syncssh session, we count how often we tried to create a pane
                let mut count = 1;
                loop {
                    debug!(
                        "Opening window/pane for {}, destination {}",
                        self.sesname, target
                    );
                    match self.synced {
                        true => {
                            // split pane
                            let output = TmuxCommand::new()
                                .split_window()
                                .size(&tmux_interface::commands::PaneSize::Percentage(1))
                                .detached()
                                .target_window(&self.sesname)
                                .shell_command(format!("{} {}", *TMSSHCMD, &target))
                                .output()
                                .unwrap();

                            trace!("New pane: {:?}", output);
                            if output.0.status.success() {
                                // Exit the loop, we made it and got the window
                                debug!("Pane opened successfully");
                                break;
                            } else {
                                // Didn't work, lets help tmux along and then retry this
                                debug!("split-window did not work");
                                if count >= 3 {
                                    return Err(anyhow!("Could not successfully create another pane for {}, tried {} times", target, count));
                                }
                                count += 1;

                                let reason: String = String::from_utf8(output.0.stderr)
                                    .expect("Could not parse tmux fail reason");

                                debug!("Failure reason: {}", reason.trim());
                                if reason.trim().eq_ignore_ascii_case("no space for new pane") {
                                    debug!("Panes getting too small, need to adjust layout");
                                    // No space for new pane -> redo the layout so windows are equally sized again
                                    let out = TmuxCommand::new()
                                        .select_layout()
                                        .target_pane(format!("{}:{}", self.sesname, wincount))
                                        .layout_name("main-horizontal")
                                        .output()
                                        .expect("Could not spread out layout");
                                    trace!("Layout result: {:#?}", out);
                                };
                            };
                            // And one more round
                            continue;
                        }
                        false => {
                            // For the plain ssh session, we count the window we are in
                            wincount += 1;
                            // new window
                            TmuxCommand::new()
                                .new_window()
                                .detached()
                                .add()
                                .window_name(&target)
                                .target_window(&self.sesname)
                                .shell_command(format!("{} {}", *TMSSHCMD, &target))
                                .output()?;
                            debug!("Window/Pane {} opened", wincount);
                            self.setwinopt(wincount, "automatic-rename", "off")?;
                            self.setwinopt(wincount, "allow-rename", "off")?;
                            break;
                        }
                    }
                }
            }
            match self.synced {
                true => {
                    // Now synchronize their input
                    self.setwinopt(wincount, "synchronize-pane", "on")?;
                    // And select a final layout that all of them have roughly the same size
                    if TmuxCommand::new()
                        .select_layout()
                        .layout_name("tiled")
                        .output()
                        .unwrap()
                        .0
                        .status
                        .success()
                    {
                        trace!("synced setup successful");
                        return Ok(true);
                    } else {
                        trace!("synced setup failed");
                        return Err(anyhow!("Setting layout failed"));
                    }
                }
                false => {
                    return Ok(true);
                }
            }
        }
        Ok(true)
    }

    /// Set an option for a tmux window
    ///
    /// tmux windows can have a large set of options attached. We do
    /// regularly want to set some.
    ///
    /// # Example
    /// ```
    /// # fn main() {
    /// session.setwinopt(windowindex, "automatic-rename", "off");
    /// # }
    /// ```
    fn setwinopt(&mut self, index: u32, option: &str, value: &str) -> Result<bool> {
        trace!("setwinopt");
        let target = format!("{}:{}", self.sesname, index);
        debug!("Setting Window ({}) option {} to {}", target, option, value);
        match TmuxCommand::new()
            .set_option()
            .window()
            .target(&target)
            .option(option)
            .value(value)
            .output()
        {
            Ok(_) => {
                debug!("Window option successfully set");
                Ok(true)
            }
            Err(error) => {
                return Err(anyhow!(
                    "Could not set window option {}: {:#?}",
                    option,
                    error
                ))
            }
        }
    }

    /// Break a session with many panes in one window into one with
    /// many windows.
    fn break_panes(&mut self) -> Result<bool> {
        // List of panes
        let panes: Vec<(String, String)> = String::from_utf8(
            TmuxCommand::new()
                .list_panes()
                .format("#{s/ssh //:pane_start_command} #{pane_id}")
                .session()
                .target(&self.sesname)
                .output()?
                .stdout(),
        )?
        .split_terminator('\n')
        .map(|x| {
            x.split_whitespace()
                .map(|y| y.trim().to_string())
                .collect_tuple::<(String, String)>()
                .unwrap_or_else(|| panic!("Could not split pane information: {}", x))
        })
        .collect();
        trace!("{:#?}", panes);

        // Go over all panes, break them out into new windows. Window
        // name is whatever they had, minus a (possible) ssh in front
        for (pname, pid) in panes {
            trace!("Breaking off pane {pname}, id {pid}");
            TmuxCommand::new()
                .break_pane()
                .detached()
                .window_name(&pname)
                .src_pane(&pid)
                .dst_window(self.sesname.to_string())
                .output()?;
        }

        Ok(true)
    }

    /// Join many windows into one window with many panes
    fn join_windows(&mut self) -> Result<bool> {
        let windowlist: Vec<String> = String::from_utf8(
            TmuxCommand::new()
                .list_windows()
                .format("#{window_id}")
                .target_session(&self.sesname)
                .output()?
                .stdout(),
        )?
        .split_terminator('\n')
        .map(|s| s.to_string())
        .collect();
        debug!("Window IDs: {:#?}", windowlist);
        let first = windowlist.clone().into_iter().next().unwrap();
        debug!("First: {first:#?}");
        for id in windowlist {
            if id != first {
                let mut count = 1;
                loop {
                    trace!("Joining {} to {}", &id, &first);
                    let output = TmuxCommand::new()
                        .join_pane()
                        .detached()
                        .src_pane(&id)
                        .dst_pane(format!("{}:{}", self.sesname, first))
                        .output()?;
                    trace!("Output: {:?}", output);
                    if output.0.status.success() {
                        // Exit the loop, we made it and got the window joined as a pane
                        debug!("Window {} joined successfully", &id);
                        break;
                    } else {
                        // Didn't work, lets help tmux along and then retry this
                        debug!("join-pane did not work");
                        if count >= 3 {
                            return Err(anyhow!(
                                "Could not successfully join window {} into {}, tried {} times",
                                id,
                                first,
                                count
                            ));
                        }
                        count += 1;

                        let reason: String = String::from_utf8(output.0.stderr)
                            .expect("Could not parse tmux fail reason");

                        debug!("Failure reason: {}", reason.trim());
                        if reason
                            .trim()
                            .eq_ignore_ascii_case("create pane failed: pane too small")
                        {
                            debug!("Panes getting too small, need to adjust layout");
                            // No space for new pane -> redo the layout so windows are equally sized again
                            let out = TmuxCommand::new()
                                .select_layout()
                                .target_pane(&first)
                                .layout_name("main-horizontal")
                                .output()
                                .expect("Could not spread out layout");
                            trace!("Layout result: {:?}", out);
                        };
                    };
                    // And one more round
                    continue;
                }
            }
        }
        self.setwinopt(*TMWIN, "synchronize-pane", "on")?;
        if TmuxCommand::new()
            .select_layout()
            .target_pane(&first)
            .layout_name("tiled")
            .output()
            .unwrap()
            .0
            .status
            .success()
        {
            trace!("joining windows successful");
            Ok(true)
        } else {
            trace!("joining windows in pane {} failed", &first);
            return Err(anyhow!("Setting layout failed"));
        }
    }
}

// A bunch of "static" variables, though computed at program start, as they
// depend on the users environment.
lazy_static! {
    ///  We want a useful tmpdir, so set one if it isn't already. Thats
    ///  the place where tmux puts its socket, so you want to ensure it
    ///  doesn't change under your feet - like for those with a
    ///  daily-changing tmpdir in their home...
    static ref TMPDIR: String = fromenvstatic!(asString "TMPDIR", "/tmp");

    /// Do you want me to sort the arguments when opening an
    /// ssh/multi-ssh session? The only use of the sorted list is for
    /// the session name, to allow you to get the same session again no
    /// matter how you order the hosts on commandline.
    static ref TMSORT: bool = fromenvstatic!(asBool "TMSORT", true);

    /// Want some extra options given to tmux? Define TMOPTS in your
    /// environment. Note, this is only used in the final tmux call
    /// where we actually attach to the session!
    static ref TMOPTS: String = fromenvstatic!(asString "TMOPTS", "-2");

    /// The following directory can hold session config for us, so you
    /// can use it as a shortcut.
    static ref TMDIR: OsString = if let Some(user_dirs) = UserDirs::new() {
        Path::join(
            user_dirs.home_dir(),
            Path::new(".tmux.d"))
        .into_os_string()
    } else {
        error!("No idea where your homedir is, using /tmp");
        Path::new("/tmp").as_os_str().to_owned()
    };

    /// Prepend the hostname to autogenerated session names?
    ///
    /// Example: Call `tm ms host1 host2`.
    /// * TMSESSHOST=true  -> session name is `HOSTNAME_host1_host2`
    /// * TMSESSHOST=false -> session name is `host1_host2`
    static ref TMSESSHOST: bool = fromenvstatic!(asBool "TMSESSHOST", false);

    /// Allow to globally define a custom ssh command line.
    static ref TMSSHCMD: String = fromenvstatic!(asString "TMSSHCMD", "ssh");

    /// From where does tmux start numbering its windows. Old shell
    /// script used a stupid way of config parsing or setting it via
    /// environment var. We now just use show_options, and in the
    /// unlikely case this fails, try parsing the old environment var
    /// TMWIN, and if that doesn't exist (quite likely now), just use
    /// 1.
    // FIXME: This depends on a running tmux daemon. None running -> data fetching
    // fails. Could fix to detect that and start one first, later killing that.
    static ref TMWIN: u32 = match TmuxCommand::new()
        .show_options()
        .global()
        .quiet()
        .value()
        .option("base-index")
        .output()
    {
        Ok(v) => v.to_string().trim().parse().unwrap_or(1),
        Err(_) => fromenvstatic!(asU32 "TMWIN", 1),
    };
}

/// Run ls
///
/// Simply runs `tmux list-sessions`
fn ls<W: Write>(handle: &mut BufWriter<W>) -> Result<()> {
    trace!("Entering ls");
    let sessions = TmuxCommand::new()
        .list_sessions()
        .output()
        .unwrap()
        .to_string();
    writeln!(handle, "{sessions}")?;
    trace!("Leaving ls");
    Ok(())
}

/// Tiny helper to replace the magic ++TMREPLACETM++
#[doc(hidden)]
fn tmreplace(input: &str, replace: &Option<String>) -> Result<String> {
    match replace {
        Some(v) => Ok(input.replace("++TMREPLACETM++", v)),
        None => Ok(input.to_string()),
    }
}

/// Parse a line of a simple_config file.
///
/// If a LIST command is found, execute that, and parse its output -
/// if that contains LIST again, recurse.
///
/// Return all found hostnames.
fn parse_line(line: &str, replace: &Option<String>, current_dir: &Path) -> Result<Vec<String>> {
    trace!("Entered parse_line");
    // We are interested in the first word to decide what we see
    let first = line.split_whitespace().next();
    match first {
        // LIST, we are asked to execute something and read its stdout
        Some("LIST") => {
            debug!("LIST command found");
            // The rest of the line (command and arguments)
            let mut cmdparser = {
                let rest = line.trim_start_matches("LIST");
                debug!("Parsing command line: {:?}", rest);
                // Do a shell-conform split of the command and arguments,
                // ie take care of " and things.
                Shlex::new(rest)
            };

            // The command ought to be the second word on the line,
            // but obviously people may mistype and have a single LIST
            // in a line.
            // Also, ~ and $HOME/${HOME} expansion are supported.
            let cmd: String = cmdparser
                .next()
                .ok_or_else(|| anyhow!("Empty LIST found - no command given"))?
                .replace("$HOME", "~/")
                .replace("${HOME}", "~/")
                .expand_home()?
                .into_os_string()
                .into_string()
                .expect("String convert failed");
            // Next we want the arguments.
            // Also, ~ and $HOME/${HOME} expansion are supported.
            let args: Vec<String> = cmdparser
                .map(|l| l.replace("$HOME", "~/").replace("${HOME}", "~/"))
                .map(|l| {
                    l.expand_home()
                        .expect("Could not successfully expand ~ for arguments of LIST call")
                        .into_os_string()
                        .into_string()
                        .expect("String convert failed")
                })
                .collect();
            debug!("cmd is {}", cmd);
            debug!("args are {:?}", args);

            // Our process spawner, pleased to hand us results as a nice
            // string seperated by newline (well, if output contains newlines)
            let cmdout = String::from_utf8(
                Command::new(&cmd)
                    .current_dir(&current_dir)
                    .args(&args)
                    .output()?
                    .stdout,
            )?;
            debug!("Command returned: {:?}", cmdout);
            if !cmdout.is_empty() {
                let mut plhosts: Vec<String> = Vec::new();
                for plline in cmdout.lines() {
                    trace!("Read line: '{}'", plline);
                    // Replace magic token, if exists and asked for
                    let plline = tmreplace(plline, replace)?;
                    debug!("Processing line: '{}'", plline);
                    // And process the line, may contain another command
                    // OR just a hostname
                    plhosts.append(&mut parse_line(&plline, replace, current_dir)?);
                }
                Ok(plhosts)
            } else {
                Err(anyhow!(
                    "LIST command {cmd} {args:?} produced no output, can not build session"
                ))
            }
        }
        Some(&_) => {
            trace!("SSH destination, returning");
            Ok(vec![line.to_string()])
        }
        None => {
            trace!("Empty line, ignoring");
            Ok(vec![])
        }
    }
}

// Can't sensibly test main()
#[cfg(not(tarpaulin_include))]
/// main, start it all off
fn main() -> Result<()> {
    let cli = Cli::parse();
    Logger::try_with_env_or_str(cli.verbose.log_level_filter().as_str())
        .unwrap()
        .adaptive_format_for_stderr(AdaptiveFormat::Opt)
        .set_palette("b1;3;2;4;6".to_owned())
        .start()
        .unwrap();

    trace!("Program started");
    debug!("Cli options: {:#?}", cli);
    debug!("TMPDIR: {}", *TMPDIR);
    debug!("TMSORT: {}", *TMSORT);
    debug!("TMOPTS: {}", *TMOPTS);
    debug!("TMDIR: {:?}", *TMDIR);
    debug!("TMSESSHOST: {}", *TMSESSHOST);
    debug!("TMSSHCMD: {}", *TMSSHCMD);
    debug!("TMWIN: {}", *TMWIN);

    let mut session = Session {
        ..Default::default()
    };

    // First get a session name
    let sesname = cli.find_session_name(&mut session)?;
    // Should, if we attach, this be grouped?
    if cli.group {
        session.grouped = true;
    }

    // Store the replacement token
    session.replace = cli.replace.clone();

    // First we check what the tm shell called "getopt-style"
    if cli.ls {
        let stdout = io::stdout();
        let mut handle = BufWriter::new(stdout.lock());
        ls(&mut handle)?;
        handle.flush()?;
    } else if cli.kill.is_some() {
        session.kill()?;
    } else if cli.session.is_some() || cli.config.is_some() {
        let sespath = if cli.session.is_some() {
            Path::join(Path::new(&*TMDIR), Path::new(&cli.session.clone().unwrap()))
        } else {
            Path::join(Path::new(&*TMDIR), Path::new(&cli.config.clone().unwrap()))
        };

        if Path::new(&sespath).exists() {
            trace!(
                "Should attach session {} or configure session from {:?}",
                sesname,
                sespath
            );
            session.sesfile = sespath;
            session.read_session_file_and_attach()?;
        } else {
            trace!("Should attach or create session {}", sesname);
            match session.attach() {
                Ok(true) => debug!("Successfully attached"),
                Ok(false) => {
                    debug!("Session not found, creating new one");
                    TmuxCommand::new()
                        .new_session()
                        .session_name(&session.sesname)
                        .output()?;
                }
                Err(val) => error!("Error: {val}"),
            }
        };
    } else if cli.sshhosts != None || cli.multihosts != None {
        trace!("Session connecting somewhere");
        if session.exists() {
            session.attach()?;
        } else {
            if cli.sshhosts != None {
                // sshhosts -> Multiple windows, not synced input
                session.synced = false;
            } else {
                // not sshhost, aka multihosts -> One window, many panes, synced input
                session.synced = true;
            }
            session.targets = cli.get_hosts()?;
            if session.setup_simple_session()? {
                session.attach()?;
            }
        }
    } else if cli.breakw.is_some() {
        trace!("Breaking up session");
        if session.exists() {
            session.break_panes()?;
        } else {
            info!("No session {} exists, can not break", session.sesname);
            println!("No session {sesname}");
        }
    } else if cli.joinw.is_some() {
        trace!("Joining session");
        if session.exists() {
            session.join_windows()?;
        } else {
            info!("No session {} exists, can not join", session.sesname);
            println!("No session {sesname}");
        }
    };

    // Now we check what the tm shell called traditional Yeah, this
    // can run things twice, e.g. if we are called as tm -l ls it will
    // show ls twice. But we want to be able to combine the two
    // styles, e.g. call as tm -n ms SOMETHING.
    // I bet there is a way to get rid of this double thing and merge
    // the above and the following, so:
    // FIXME: Merge the above if and the match here, somehow, dedupe
    // the code.
    if cli.command != None {
        match &cli.command.as_ref().unwrap() {
            Commands::Ls {} => {
                let stdout = io::stdout();
                let mut handle = BufWriter::new(stdout.lock());
                ls(&mut handle)?;
                handle.flush()?;
            }
            Commands::S { hosts: _ } => {
                trace!("ssh subcommand called");
                if session.exists() {
                    session.attach()?;
                } else {
                    session.synced = false;
                    session.targets = cli.get_hosts()?;
                    session.setup_simple_session()?;
                    session.attach()?;
                }
            }
            Commands::Ms { hosts: _ } => {
                trace!("ms subcommand called");
                if session.exists() {
                    session.attach()?;
                } else {
                    session.synced = true;
                    session.targets = cli.get_hosts()?;
                    session.setup_simple_session()?;
                    session.attach()?;
                }
            }
            Commands::K { sesname } => {
                trace!("k subcommand called, killing {sesname}");
                session.kill()?;
            }
            Commands::B { sesname } => {
                trace!("b subcommand called, breaking panes into windows for {sesname}");
                if session.exists() {
                    session.break_panes()?;
                } else {
                    info!("No session {sesname} exists, can not break");
                    println!("No session {sesname}");
                }
            }
            Commands::J { sesname } => {
                trace!("j subcommand called, joining windows into panes for {sesname}");
                session.join_windows()?;
            }
        }
    }
    info!("All done, end");
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use regex::Regex;

    #[test]
    fn test_cmdline_getopts_simpleopt() {
        let mut session = Session {
            ..Default::default()
        };

        // Just a session
        let mut cli = Cli::parse_from("tm foo".split_whitespace());
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "foo".to_string()
        );

        // -l is ls
        cli = Cli::parse_from("tm -l".split_whitespace());
        assert!(cli.ls);

        // -k to kill a session
        cli = Cli::parse_from("tm -k killsession".split_whitespace());
        assert_eq!(cli.kill, Some("killsession".to_string()));
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "killsession".to_string()
        );

        // -b to break a session into many windows
        cli = Cli::parse_from("tm -b breaksession".split_whitespace());
        assert_eq!(cli.breakw, Some("breaksession".to_string()));
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "breaksession".to_string()
        );

        // -j to join many windows into one pane
        cli = Cli::parse_from("tm -j joinsession".split_whitespace());
        assert_eq!(cli.joinw, Some("joinsession".to_string()));
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "joinsession".to_string()
        );

        // -k to kill a session - second value on commandline should not
        // adjust session name.
        cli = Cli::parse_from("tm -k session ses2".split_whitespace());
        assert_eq!(cli.session, Some("ses2".to_string()));
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "session".to_string()
        );

        // -v/-q goes via clap_verbosity, just check that we did not suddenly redefine it
        assert_eq!(cli.verbose.log_level_filter(), log::LevelFilter::Error);
        assert!(!cli.verbose.is_silent());
        cli = Cli::parse_from("tm -v".split_whitespace());
        assert_eq!(cli.verbose.log_level_filter(), log::LevelFilter::Warn);
        assert!(!cli.verbose.is_silent());
        cli = Cli::parse_from("tm -vvvv".split_whitespace());
        assert_eq!(cli.verbose.log_level_filter(), log::LevelFilter::Trace);
        cli = Cli::parse_from("tm -q".split_whitespace());
        assert_eq!(cli.verbose.log_level_filter(), log::LevelFilter::Off);
        assert!(cli.verbose.is_silent());

        // -n wants a second session to same hosts as existing one
        let mut cli = Cli::parse_from("tm -n".split_whitespace());
        assert!(cli.second);

        // -g attaches existing session, but different window config
        cli = Cli::parse_from("tm -g".split_whitespace());
        assert!(cli.group);
    }

    #[test]
    fn test_cmdline_getopts_s() {
        let mut session = Session {
            ..Default::default()
        };
        // -s is ssh to one or more hosts
        let mut cli = Cli::parse_from("tm -s testhost".split_whitespace());
        assert_eq!(cli.sshhosts, Some(vec!["testhost".to_string()]));
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "s_testhost".to_string()
        );
        cli = Cli::parse_from("tm -s testhost morehost andonemore".split_whitespace());
        assert_eq!(
            cli.sshhosts,
            Some(vec![
                "testhost".to_string(),
                "morehost".to_string(),
                "andonemore".to_string()
            ])
        );
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "s_andonemore_morehost_testhost".to_string()
        );

        // Combine with -n
        cli = Cli::parse_from("tm -n -s testhost".split_whitespace());
        let sesname = cli.find_session_name(&mut session).unwrap();
        // -n puts a random number into the name, so check with regex
        let re = Regex::new(r"^s_\d+_testhost$").unwrap();
        assert!(re.is_match(&sesname));
        assert_ne!(
            cli.find_session_name(&mut session).unwrap(),
            "s_testhost".to_string()
        );
    }

    #[test]
    fn test_cmdline_getopts_ms() {
        let mut session = Session {
            ..Default::default()
        };

        // -m is ssh to one or more hosts, synchronized input
        let mut cli = Cli::parse_from("tm -m testhost".split_whitespace());
        assert_eq!(cli.multihosts, Some(vec!["testhost".to_string()]));
        cli = Cli::parse_from("tm -m testhost morehost andonemore".split_whitespace());
        assert_eq!(
            cli.multihosts,
            Some(vec![
                "testhost".to_string(),
                "morehost".to_string(),
                "andonemore".to_string()
            ])
        );
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "ms_andonemore_morehost_testhost".to_string()
        );

        // Combine with -n
        cli = Cli::parse_from("tm -n ms testhost morehost".split_whitespace());
        let sesname = cli.find_session_name(&mut session).unwrap();
        // -n puts a random number into the name, so check with regex
        let re = Regex::new(r"^ms_\d+_morehost_testhost$").unwrap();
        assert!(re.is_match(&sesname));
        assert_ne!(
            cli.find_session_name(&mut session).unwrap(),
            "ms_morehost_testhosts".to_string()
        );
    }

    #[test]
    #[allow(clippy::bool_assert_comparison)]
    fn test_cmdline_ls() {
        let mut cli = Cli::parse_from("tm ls".split_whitespace());
        assert!(!cli.ls);
        assert_eq!(cli.command, Some(Commands::Ls {}));

        // -v/-q goes via clap_verbosity, just check that we did not suddenly redefine it
        assert_eq!(cli.verbose.log_level_filter(), log::LevelFilter::Error);
        assert!(!cli.verbose.is_silent());
        cli = Cli::parse_from("tm -v".split_whitespace());
        assert_eq!(cli.verbose.log_level_filter(), log::LevelFilter::Warn);
        assert!(!cli.verbose.is_silent());
    }

    #[test]
    #[allow(clippy::bool_assert_comparison)]
    fn test_cmdline_s() {
        let mut session = Session {
            ..Default::default()
        };
        // s is ssh to one or more hosts
        let mut cli = Cli::parse_from("tm s testhost".split_whitespace());
        let mut cc = cli.command.as_ref().unwrap();
        let mut val = vec!["testhost".to_string()];
        assert_eq!(cc, &Commands::S { hosts: val });
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "s_testhost".to_string()
        );
        cli = Cli::parse_from("tm s testhost morehost andonemore".split_whitespace());
        cc = cli.command.as_ref().unwrap();
        val = vec![
            "testhost".to_string(),
            "morehost".to_string(),
            "andonemore".to_string(),
        ];
        assert_eq!(cc, &Commands::S { hosts: val });
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "s_andonemore_morehost_testhost".to_string()
        );
    }

    #[test]
    fn test_cmdline_k() {
        let mut session = Session {
            ..Default::default()
        };
        // k is kill that session
        let cli = Cli::parse_from("tm k session".split_whitespace());
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "session".to_string()
        );
        let cc = cli.command.as_ref().unwrap();
        assert_eq!(
            cc,
            &Commands::K {
                sesname: "session".to_string()
            }
        );
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "session".to_string()
        );
    }

    #[test]
    fn test_cmdline_b() {
        let mut session = Session {
            ..Default::default()
        };
        // k is kill that session
        let cli = Cli::parse_from("tm b session".split_whitespace());
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "session".to_string()
        );
        let cc = cli.command.as_ref().unwrap();
        assert_eq!(
            cc,
            &Commands::B {
                sesname: "session".to_string()
            }
        );
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "session".to_string()
        );
    }

    #[test]
    fn test_cmdline_j() {
        let mut session = Session {
            ..Default::default()
        };
        // k is kill that session
        let cli = Cli::parse_from("tm j session".split_whitespace());
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "session".to_string()
        );
        let cc = cli.command.as_ref().unwrap();
        assert_eq!(
            cc,
            &Commands::J {
                sesname: "session".to_string()
            }
        );
        assert_eq!(
            cli.find_session_name(&mut session).unwrap(),
            "session".to_string()
        );
    }

    #[test]
    fn test_set_name() {
        let mut session = Session {
            ..Default::default()
        };

        assert_eq!("test", session.set_name("test").unwrap());
        assert_eq!("test_second", session.set_name("test second").unwrap());
        assert_eq!("test_third", session.set_name("test:third").unwrap());
        assert_eq!(
            "test_fourth_fifth",
            session.set_name("test fourth fifth").unwrap()
        );
        assert_eq!(
            "test_fourth_fifth_more_words_here_set_in",
            session
                .set_name("test fourth_fifth:more words here\"set in")
                .unwrap()
        );
    }

    #[test]
    fn test_fromenvstatic() {
        // Testing with env vars isn't nice - users may set them randomly.
        // So pre-define a known set, so we at least can test the code
        // around fromenvstatic
        env::set_var("TMPDIR", "/tmp");
        env::set_var("TMOPTS", "-2");
        env::set_var("TMSORT", "true");
        env::set_var("TMSESSHOST", "false");
        env::set_var("TMSSHCMD", "ssh");
        env::set_var("TMWIN", "1");
        assert_eq!(*TMPDIR, "/tmp");
        assert_eq!(*TMOPTS, "-2");
        assert!(*TMSORT);
        assert!(!*TMSESSHOST);
        assert_eq!(*TMSSHCMD, "ssh");
        assert_eq!(*TMWIN, 1);
    }

    #[test]
    fn test_attach() {
        let mut session = Session {
            ..Default::default()
        };
        // We want a new session
        session.set_name("fakeattach").unwrap();
        // Shouldn't exist
        assert!(!session.exists());
        // Lets create it
        TmuxCommand::new()
            .new_session()
            .session_name(&session.sesname)
            .detached()
            .shell_command("/bin/bash")
            .output()
            .unwrap();
        // Is it there?
        assert!(session.exists());
        // Now try the attach. if cfg!(test) code should just return true
        assert!(session.attach().unwrap());
        // Grouped sessions are nice
        session.grouped = true;
        // Try attach again
        assert!(session.attach().unwrap());
        // gsesname will contain session name plus random string
        // FIXME: Better check with a regex to be written
        assert_ne!(session.sesname, session.gsesname);
        println!("Grouped session name: {}", session.gsesname);
        // Get rid of session - this will remove the original one
        session.kill().unwrap();
        // FIXME: Check that it only removed the original one
        // Now get rid of the grouped session too.
        assert!(session.realkill(&session.gsesname).unwrap());

        assert!(!session.exists());
        // And now we test something that shouldn't work for attach
        session.set_name("notfakeattach").unwrap();
        // Not grouped
        session.grouped = false;
        // Shouldn't exist
        assert!(!session.exists());
        // Lets create it
        TmuxCommand::new()
            .new_session()
            .session_name(&session.sesname)
            .detached()
            .shell_command("/bin/bash")
            .output()
            .unwrap();
        // Is it there?
        assert!(session.exists());
        // Now try the attach. if cfg!(test) code should just return false here
        assert!(!session.attach().unwrap());
        // Get rid of session
        session.kill().unwrap();
    }

    #[test]
    fn test_kill_ls_and_exists() {
        let mut session = Session {
            ..Default::default()
        };
        session.set_name("tmtestsession").unwrap();
        assert!(!session.exists());
        TmuxCommand::new()
            .new_session()
            .session_name(&session.sesname)
            .detached()
            .shell_command("/bin/bash")
            .output()
            .unwrap();
        assert!(session.exists());

        // We want to check the output of ls contains our session from
        // above, so have it "write" it to a variable, then check if
        // the variable contains the session name.
        let lstext = Vec::new();
        let mut handle = BufWriter::new(lstext);
        ls(&mut handle).unwrap();
        handle.flush().unwrap();

        assert!(session.kill().unwrap());
        assert!(session.kill().is_err());
        assert!(!session.exists());

        // And now check what got "written" into the variable
        let (recovered_writer, _buffered_data) = handle.into_parts();
        let output = String::from_utf8(recovered_writer).unwrap();
        assert!(output.contains(&session.sesname));
    }

    #[test]
    fn test_tmreplace() {
        assert_eq!(tmreplace("test", &None).unwrap(), "test".to_string());
        assert_eq!(
            tmreplace("test", &Some("foo".to_string())).unwrap(),
            "test".to_string()
        );
        assert_eq!(
            tmreplace("test++TMREPLACETM++", &Some("foo".to_string())).unwrap(),
            "testfoo".to_string()
        );
    }

    #[test]
    fn test_parse_line() {
        let mut line = "justonehost";
        let mut replace = None;
        let mut current_dir = Path::new("/");
        let mut res = parse_line(line, &replace, current_dir).unwrap();
        assert_eq!(res, vec!["justonehost".to_string()]);
        line = "LIST /bin/echo \"onehost\ntwohost\nthreehost\"";
        replace = None;
        current_dir = Path::new("/");
        res = parse_line(line, &replace, current_dir).unwrap();
        assert_eq!(
            res,
            vec![
                "onehost".to_string(),
                "twohost".to_string(),
                "threehost".to_string()
            ]
        );
        line = "LIST /bin/echo \"onehost\ntwohost\nthreehost\nfoobar\nLIST /bin/echo \"LIST /bin/echo \"bar\nbaz\n\"\n\"\"";
        replace = None;
        current_dir = Path::new("/");
        res = parse_line(line, &replace, current_dir).unwrap();
        assert_eq!(
            res,
            vec![
                "onehost".to_string(),
                "twohost".to_string(),
                "threehost".to_string(),
                "foobar".to_string(),
                "bar".to_string(),
                "baz".to_string()
            ]
        );
        line = " ";
        replace = None;
        current_dir = Path::new("/");
        res = parse_line(line, &replace, current_dir).unwrap();
        let empty: Vec<String> = vec![];
        assert_eq!(res, empty);
    }

    #[test]
    fn test_setup_extended_session() {
        let mut session = Session {
            ..Default::default()
        };
        session.set_name("testextended").unwrap();
        // Fail, we have no data in session.targets yet
        assert!(session.setup_extended_session().is_err());

        // Put two lines in
        session.targets.push(format!(
            "new-session -d -s {0} -n {0} /bin/bash",
            session.sesname
        ));
        session.targets.push(format!(
            "split-window -h -p 50 -d -t {}:{} /bin/bash -c 'watch -n1 -d date -u'",
            session.sesname, *TMWIN
        ));
        session.targets.push(format!(
            "new-window -d -t {}:{} /bin/bash -c 'watch -n1 -d date -u'",
            session.sesname, 3
        ));

        // This should work out
        session.setup_extended_session().unwrap();

        // // We want to check the output of ls contains our session from
        // // above, so have it "write" it to a variable, then check if
        // // the variable contains the session name and that it has two windows
        // let lstext = Vec::new();
        // let mut handle = BufWriter::new(lstext);
        // ls(&mut handle).unwrap();
        // handle.flush().unwrap();

        // // And now check what got "written" into the variable
        // let (recovered_writer, _buffered_data) = handle.into_parts();
        // let output = String::from_utf8(recovered_writer).unwrap();
        // let checktext = format!("{}: 2 windows", session.sesname);
        // assert!(
        //     output.contains(&checktext),
        //     "Could not correctly setup extended session, output is {:#?}",
        //     output
        // );

        // At the end, get rid of the test session
        assert!(session.kill().unwrap());
    }
}

redpajama_set_name: RedPajamaGithub


text:
Sending messages
================

Using ``console``:

.. code-block:: bash

    # Get the list of devices
    $ python manage.py fcm_messenger --devices
    > Devices list:
    > (#1) My phone

    # python manage.py fcm_messenger --device_id=<device_id> --msg=<message> [--collapse-key <key>]
    $ python manage.py fcm_messenger --device_id=1 --msg='my test message'

Using ``Django orm``::

    from fcm.utils import get_device_model
    Device = get_device_model()

    my_phone = Device.objects.get(name='My phone')
    my_phone.send_message({'message':'my test message'}, collapse_key='something')

``collapse_key`` parameter is optional (default message).

If you want to send additional arguments like ``delay_while_idle`` or other, add them as named variables e.g.::

    my_phone.send_message({'message':'my test message'}, delay_while_idle=True, time_to_live=5)

.. _Lifetime of a Message: https://developer.android.com/google/gcm/server.html#lifetime
.. _Sending a downstream message: https://developer.android.com/google/gcm/server-ref.html#send-downstream

.. note:: For more information, see `Lifetime of a Message`_ and `Sending a downstream message`_ docs.

Multicast message
-----------------

``django-fcm`` supports sending messages to multiple devices at once. E.g.::

    from fcm.utils import get_device_model
    Device = get_device_model()

    Device.objects.all().send_message({'message':'my test message'})

Topic messaging
-----------------------

``django-fcm`` supports sending messages to multiple devices that have opted in to a particular fcm topic::

    from fcm.utils import FCMMessage

    FCMMessage().send({'message':'my test message'}, to='/topics/my-topic')

.. _Send messages to topics: https://developers.google.com/cloud-messaging/topic-messaging

.. note:: For more information, see `Send messages to topics`_.


Pacific Coast Center for the Arts (PCCA) was founded as Pacific Coast Academy of Dance(PCAD) in 2002 in RanchoSan Clemente, California. Our program began in a 7,100 sq. ft building with 4 studios and a curriculum based in TechnicalExcellence and Performance Artistry through high level classical ballet training. The studio was quickly recognized withawards for "Outstanding School" and "Outstanding Teachers" by the international ballet competition, Youth America Grand Prix.


text:
The Curse of Martha Splatterhead is an LP by Oak Harbor, Washington crossover thrash band the Accüsed. It was released in 2009 with a reformed line-up on Southern Lord Records.

Reception
According to Sputnikmusic, "The songs on the album are awesome, there really is no other way to put it. This album is nothing but fourteen songs of pure crossover thrash, stripped down so that you experience the full brutality of The Curse Of Martha Splatterhead. Each song is propelled along by Tommy Niemeyer's riffs which go from thrash to hardcore to punk with ease and he even slips in the odd sloppy solo which works superbly in the context of these songs. Most songs are clearly thrash, although they have a punky sound to them as well. Niemeyer is backed up by Dorando Hodous on bass and Mike Peterson on drums, who just keep adding to this deadly combination. With a new vocalist as well, known only as Brad Mowen, his performance on the album is superb. He goes from yells, to rasps that all fit in perfectly with each song. The main lyrical theme of most songs include zombies, gore and society."

Track listing
The Curse of Martha Splatterhead
 The Splatterbeast – 2:38
 Stomped to Death – 1:35
 Bodies Are Rising – 2:01
 Festival of Flesh – 2:39
 Elijah Black – 2:09
 Scotty Came Back – 1:37
 Hemline – 2:34
 Die Violently – 2:00
 By the Hook – 1:36
 Avenue of the Dead – 1:39
 Fuck Sorry – 2:14
 Martha's Disciples – 1:54
 Seriously Dead – 1:39
 Splatter Rock II – 3:35

References

The Accüsed albums
2009 albums

redpajama_set_name: RedPajamaWikipedia


text:
Southern Baptist Convention
About Baptist Press About BP
Media Relations Media
Your Daily Joe by Joe McKeever
Brother Blooper by Dennis Daniel
Church of the Covered Dish by Thom Tapp
Fletch by Dennis Fletcher
P.K. (Preacher's Kids) by David Ayers
Beyond the Ark by Doug Michael
Photo & Video Library
Search Baptist Press
1948-1996 Archive
About Baptist Press
Subscribe to Baptist Press
Copyright (c) 2019 Baptist Press. Reprinted from Baptist Press (www.baptistpress.com), news service of the Southern Baptist Convention.
The original story can be found at http://www.bpnews.net/43673/travel-risks-prompt-baptists-to-improve-training
Travel risks prompt Baptists to improve training
by Shawn Hendricks, posted Thursday, November 06, 2014 (4 years ago)
Tags: Fort ShermanDavid DosetravelMideast Monitor
NASHVILLE (BP) -- Reggie Quimby thought he and his fellow mission team members were safe.
In the company of local guides and a caravan of vehicles, they traveled at night on a rural road in Guatemala. While there was some risk involved with this particular road after sunset, Quimby believed the odds of something bad happening were too small.
A group of bandits with AK-47s, however, blocked their path with rocks, forced them to pull over and robbed them. No one was harmed that night several years ago. But Quimby, global missions director for the Alabama Baptist Convention, was reminded that a crime can happen when it's least expected.
"... People get comfortable and let their guard down...," he said. "We had been there. We had traveled on these roads.... [We thought] if we traveled together ... that risk would go away. Well, obviously it did not."
As the ISIS terrorist group and Ebola scare continue to make headlines, the Alabama convention and other Baptist organizations are continuing to assess the growing risks involved with overseas travel.
Many of them have turned to an organization based in Pinehurst, Idaho, called Fort Sherman Academy. Its president, David Dose, is a former Department of Defense consultant and instructor who has helped train government, corporate and faith-based groups how to handle travel risks for the past 17 years. The group is one of the largest -- if not the largest -- providers in the United States of travel security courses, crime survival training and crisis management.
Dose and his team have interviewed numerous survivors of overseas crime looking for ways these situations could have been avoided. Among those survivors is Gracia Burnham. She and her husband Martin, who were missionaries to the Philippines, were taken hostage by Muslim extremists in 2001 and held in captivity for more than a year before Gracia was rescued. Martin lost his life when he got caught in the crossfire of a gun battle that broke out during the rescue.
"During my time in this work, the Burnham case particularly touched me, and I've been privileged to work with Gracia some since," Dose said. "... I saw opportunities where probably using existing knowledge we could have helped some people avoid, survive or better survive and encourage folks...."
Fort Sherman currently works with about 200 clients. Included in that list is the Southern Baptist International Mission Board, nine Baptist state conventions and numerous churches. The group works with organizations from other denominations as well.
Fort Sherman has a team of about 50 full-time, part-time and contract workers and its Idaho headquarters also includes a 40-acre guest facility.
Helping better equip faith-based groups for overseas ministry is what motivated Dose and his wife Elizabeth to start Fort Sherman.
"My wife and I made the decision that probably a lot of missionaries and their families have made -- to throw everything in the basket. We started our own company with a major focus on ... faith-based [groups].... I find enjoyment in that audience. I find purpose in their purpose ... and so we did like everybody else [with] our finances and future plans and cashed those things in and started this little organization."
And the "little" organization has grown. Since it began more than 10 years ago, Fort Sherman has trained about 42,000 long- and short-term mission workers as well as corporate and humanitarian workers.
Fort Sherman specializes in providing different levels of training -- including Web-based training -- on how to handle various types of crisis situations overseas. It also provides a 24-hour service for clients to call if they need assistance.
Some of those situations may include:
-- What to do if someone on a mission team gets sick overseas, needs immediate medical care and is unable to travel back to the States.
-- Calling for help if you or a team member is robbed and your wallet and passport have been taken.
-- What to do if an unexpected natural disaster hits the area you're traveling in and derails travel back to the United States for several days.
-- How to handle a team member being detained by an overseas government for several days.
Dose said an "upsurge" of interest is developing among faith-based groups as evangelical groups and organizations send more teams into high-risk areas.
"This is just one more tool," Dose said, "... to focus on the basics of avoiding crime, surviving crime, being organized in advance for avoiding those things and even having a crisis plan in place if something goes wrong -- whether it's a volcano interrupts travel ... or somebody stops me at a border."
Dose says travelers need to realize that finding a "safe place" to travel is becoming increasingly difficult.
"... I'll get the guy who says, 'Oh we don't have a problem. We go to the same place every year,'" said Dose, noting he read a U.S. State Department report showing that crime has increased by nearly 15 percent worldwide. "Yeah, but if crime is going up every year and you're doing the same thing you always did, eventually someone might fall victim. We're trying to prep them for that."
Days of 'winging it' are gone
And sometimes unexpected challenges can arise before a mission team even arrives at their destination.
As airport authorities continue to crack down on a variety of criminal activity -- drug and sex trafficking, terrorism -- more mission teams are being detained in airports everywhere, Dose said.
Teams often don't plan ahead for handling unexpected questions in an airport, he said. Travelers need to be able to better articulate why they are traveling to a country and what they plan to do while they are there.
"They throw out the good ol', 'I'm just an American tourist' and that doesn't work anymore," he said, noting that some travelers have used "American tourist" when traveling to a war-torn country to help with relief work. "... Winging it doesn't help because security has changed."
Dose said those types of mistakes can get expensive if a team member is detained. In some situations, organizations can spend anywhere from $10,000 to more than $50,000 in fees and legal expenses triggered by issues with overseas governments.
"I've seen up to $100,000 in a month," Dose said. "... That's a lot of money that would have gone to next year's mission trip."
Quimby, Alabama Baptists' global mission director, said stewardship was a major part of the decision for the state's convention to develop an ongoing partnership with Fort Sherman. He described the training they've received in developing crisis management strategies as a way of "protecting ministry, protecting our future ministry and helping our churches to protect their ministry."
"... We're seeing that if we ... do the training and we invest up front, then we may not have to pay those big expenses ... because we have been trying to do things correctly," he said.
Ken Rhodes, director of missions mobilization for the Mississippi Baptist Convention, said they require mission teams and personnel who work with them to go through at least a basic level of training from Fort Sherman -- which can involve an eight-hour course.
"... In Mississippi we are really pressing the boundaries to send people to some of the harder ... places to get to in the world," Rhodes said. "We have trained right around 2,000 people at this point."
In addition to the general risks involved with overseas travel, Rhodes said there also are liability issues to consider. Organizations should be prepared to resolve conflicts that could surface between them and the family members of those involved in an unexpected crisis, he said.
"We send husbands of unbelieving wives ... we send students whose parents are unbelievers and they do not understand what is happening," Rhodes said. "So I think that is a huge liability when you're sending family members who are from families who are not supportive and may not even be believers...."
More and more Baptist colleges and universities also are working with Fort Sherman to train students planning to study abroad or participate in mission trips.
Cynthia Jayne, associate provost for intercultural and international studies at Union University, noted, "... We decided that it really didn't matter where students were going. [Students] had the potential to get themselves into really serious difficulties if they didn't understand some of the basic things."
With a total enrollment of more than 4,000 students on its main campus, satellite campuses and students utilizing their online programs, the university sends about 250-300 students overseas annually for academic programs and about 200-300 overseas for mission projects.
Jayne said the university continues to push its leadership to be more prepared when working with students overseas.
"The bottom line is, If you're in charge of this group you'd better have more information than they have to be able to make decisions," she said.
"... Our faculty [is] now asking questions about assessing the risk of what they are doing in ways that help them think through how they're going to put the trip together."
The training also has helped them prepare to better handle potential crisis situations on campus. Last February, Baptist Press reported that a Union University student had been charged with first-degree murder in the shooting death of another student on its campus in Jackson, Tenn.
"It raised a lot of other questions for us about ... preparing our students to deal with many kinds of life situations," Jayne said.
The campus is now working with Fort Sherman to provide online training to freshmen this year, Jayne said.
"Ultimately our goal is to have every student have that basic online ... training," Jayne said. "... You can fence in. You can lock the gates. You can do all of those things but if the students themselves don't understand how to protect themselves, it isn't gonna help."
For more information about Fort Sherman and the training they provide, contact them at 888-211-8674 or info@fortsherman.org.
Shawn Hendricks is managing editor of Baptist Press.
Get Baptist Press headlines and breaking news on Twitter (@BaptistPress), Facebook (Facebook.com/BaptistPress) and in your email (baptistpress.com/SubscribeBP.asp).
Formed in 1946 by the Southern Baptist Convention, and supported with Cooperative Program funds, Baptist Press (BP) is a daily (Monday-Friday) international news service. Operating from a central bureau in Nashville, Tenn., BP works with a large network of contributing writers, photographers and editorial providers to produce BP News.
Copyright © 2019 Baptist Press, Southern Baptist Convention
Version: 1.8 ßuild - 2017.12.01
This page was generated on 7/19/2019 5:25:19 AM

redpajama_set_name: RedPajamaCommonCrawl


As the award-winning Isle of Arran Distillers celebrates its 21st Birthday, the Arran Malt reaches maturity with the first bottling of the year - the 18 Year Old Single Malt.
Anticipation for the return of the 18 Year Old Single Malt is high after the first limited run of 9,000 bottles last year rapidly sold out. The Arran Malt reaches full maturity at 18 years making this an incredibly complex and sophisticated dram.
Gold in colour, caramelised citrus and tropical notes mingle on a background of toasted oak.
The classic Arran flavours of citrus and orchard fruits combine with milk brioche and dark chocolate for a luxurious finish that lingers on the palette. A drop of water reveals light floral notes and the delicious aroma of orange fondant creams.
Bottled at 46% (vol), it is distilled without chill-filtration or artificial colouring for a pure yet intricate taste.
James MacTaggart, master distiller at the Isle of Arran Distillery, says: "This is a very special bottling for us. As the most mature expression of the Arran Malt it represents the very best of the distillery in this, our 21st year.


text:
Q: Google map not showing in tab fragment I am using a Tab layout in my project. One of the tab fragments should show World map. My app is integrated with Google map API and I used ViewPager for setting the adapter.  Below is the activity.
public class HomeActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_home);

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    // Get the ViewPager and set it's PagerAdapter so that it can display items
    ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
    PagerAdapter pagerAdapter =
            new PagerAdapter(getSupportFragmentManager(), HomeActivity.this);
    viewPager.setAdapter(pagerAdapter);

    // Give the TabLayout the ViewPager
    TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
    tabLayout.setupWithViewPager(viewPager);

    // Iterate over all tabs and set the custom view
    for (int i = 0; i < tabLayout.getTabCount(); i++) {
        TabLayout.Tab tab = tabLayout.getTabAt(i);
        tab.setCustomView(pagerAdapter.getTabView(i));
    }
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_home, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();

    if (id == R.id.action_settings) {
        return true;
    }

    return super.onOptionsItemSelected(item);
}

class PagerAdapter extends FragmentPagerAdapter {

    String tabTitles[] = new String[] { "Cloud", "Home", "Report" };
    Context context;

    public PagerAdapter(FragmentManager fm, HomeActivity context) {
        super(fm);
        this.context = context;
    }

    @Override
    public int getCount() {
        return tabTitles.length;
    }

    @Override
    public Fragment getItem(int position) {

        switch (position) {
            case 0:
                return new MapFragment();
            case 1:
                return new OneFragment();
            case 2:
                return new TwoFragment();
        }

        return null;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        // Generate title based on item position
        return tabTitles[position];
    }

    public View getTabView(int position) {
        View tab = LayoutInflater.from(HomeActivity.this).inflate(R.layout.custom_tab, null);
        TextView tv = (TextView) tab.findViewById(R.id.custom_text);
        tv.setText(tabTitles[position]);
        return tab;
    }

}

    }

And my fragment is
public class MapFragment extends Fragment  {

private MapView mMapView;

private GoogleMap mMap;

View view;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    view = inflater.inflate(R.layout.fragment_map, null, false);
    mMapView = (MapView) view.findViewById(R.id.mapView);
    mMapView.onCreate(savedInstanceState);
    mMapView.onResume();
    try {
        MapsInitializer.initialize(getActivity().getApplicationContext());
    } catch (Exception e) {
        e.printStackTrace();
    }

    mMapView.getMapAsync(new OnMapReadyCallback() {

        @Override
        public void onMapReady(GoogleMap googleMap) {
            mMap = googleMap;

            // Add a marker in Sydney and move the camera
            LatLng sydney = new LatLng(-34, 151);
            mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
            mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
            mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
                @Override
                public void onMapClick(LatLng latLng) {

                    Toast.makeText(
                            getActivity(),
                            "Lat : " + latLng.latitude + " , "
                                    + "Long : " + latLng.longitude,
                            Toast.LENGTH_SHORT).show();

                }
             });

            if (mMap == null) {
                mMapView = (MapView) view.findViewById(R.id.mapView);
                mMapView.onCreate(getArguments());
                mMapView.onResume();

                try {
                    MapsInitializer.initialize(getActivity());
                    mMapView.getMapAsync(new OnMapReadyCallback() {
                        @Override
                        public void onMapReady(GoogleMap gmap) {
                            mMap = gmap;
                            mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);

                            //fill markers
                            //add marker listener

                        }
                    });

                } catch (Exception e) {
                    e.printStackTrace();
                }

            }

         }

    });
    return view;
}

    @Override
    public void onResume(){
        super.onResume();
        mMapView.onResume();
    }
    @Override
    public void onPause(){
        super.onPause();
        mMapView.onPause();
    }
    @Override
    public void onDestroy(){
        super.onDestroy();
        mMapView.onDestroy();
    }

    @Override
    public void onLowMemory(){
        super.onLowMemory();
        mMapView.onLowMemory();
    }

}

my application running successfully. and in the first tab the fragment can be loaded and am able to see the latitude and longitude values also when clicking on the screen. but not able to see the google map. Shared below the image. the same code is working perfectly in separate activity. Please help to find what I have missed to add.
screenshot

A: I have resolved this issue by generating a new key for Google map API and updated it in manifest file. And now am able to see the google map in one of my tab fragments working perfectly along with other fragments.

redpajama_set_name: RedPajamaStackExchange


text:
Arapeí es un municipio al este del estado de São Paulo, en la microrregión de Bananal. La población estimada en 2005 era de 2.821 habitantes y el área es de 156 km², con una densidad demográfica de 18,08 hab/km².

Geografía

Sus municipios limítrofes son Resende y Barra Mansa (ambos en Río de Janeiro) al norte, Bananal al sudeste y San José del Barreiro al oeste.

Demografía

Censo - 2000

Población total: 2.618
 Urbana: 1.899
 Rural: 719
 Hombres: 1.305
 Mujeres: 1.313
Densidad demográfica (hab./km²): 17,08

Mortalidad infantil hasta 1 año (por mil): 28,54

Expectativa de vida (años): 65,57

Taza de fecundidad (hijos por mujer): 3,49

Taza de alfabetización: 85,53%

Índice de Desarrollo Humano (IDH-M): 0,716
 IDH-M Salario: 0,628
 IDH-M Longevidad: 0,676
 IDH-M Educación: 0,845
(Fuente: IPEADATA)

Hidrografía
 Río Mambucaba
 Río Bracuí

Carreteras
 SP-68

Referencias

Enlaces externos
 Página de la prefectura

Localidades de São Paulo
Toponimia guaraní de Brasil