#!/usr/bin/env python3
# Line too long - pylint: disable=C0301
# Invalid name  - pylint: disable=C0103
#
# Copyright (c) EMC/Greenplum Inc 2011. All Rights Reserved.
# Copyright (c) Greenplum Inc 2008. All Rights Reserved.
#
"""
Internal Use Function.
"""

# THIS IMPORT MUST COME FIRST
from gppylib.mainUtils import simple_main, addStandardLoggingAndHelpOptions

import os, pickle, base64, time

from gppylib.gpparseopts import OptParser, OptChecker
from gppylib import gparray, gplog
from gppylib.commands import base, gp
from gppylib.utils import parseKeyColonValueLines
from gppylib.commands.pg import PgControlData

logger = gplog.get_default_logger()


DESCRIPTION = """
This utility is NOT SUPPORTED and is for internal-use only.
Starts a set of one or more segment databases.
"""

HELP = ["""
Utility should only be used by other GP utilities.

Return codes:
  0 - All segments started successfully
  1 - At least one segment didn't start successfully

"""]




class StartResult:
    """
    Recorded result information from an attempt to start one segment.
    """

    def __init__(self, datadir, started, reason, reasoncode):
        """
        @param datadir
        @param started
        @param reason
        @param reasoncode one of the gp.SEGSTART_* values
        """
        self.datadir    = datadir
        self.started    = started
        self.reason     = reason
        self.reasoncode = reasoncode

    def __str__(self):
        return "".join([
                "STATUS",
                "--DIR:", str(self.datadir),
                "--STARTED:", str(self.started),
                "--REASONCODE:", str(self.reasoncode),
                "--REASON:", str(self.reason)
                ])


class OverallStatus:
    """
    Mapping and segment status information for all segments on this host.
    """

    def __init__(self, dblist):
        """
        Build the datadir->segment mapping and remember the original size.
        Since segments which fail to start will be removed from the mapping,
        we later test the size of the map against the original size when
        returning the appropriate status code to the caller.
        """
        self.dirmap          = dict([(seg.getSegmentDataDirectory(), seg) for seg in dblist])
        self.original_length = len(self.dirmap)
        self.results         = []
        self.logger          = logger


    def mark_failed(self, datadir, msg, reasoncode):
        """
        Mark a segment as failed during some startup process.
        Remove the entry for the segment from dirmap.

        @param datadir
        @param msg
        @param reasoncode one of the gp.SEGSTART_* constant values
        """
        self.logger.info("Marking failed %s, %s, %s" % (datadir, msg, reasoncode))
        self.results.append( StartResult(datadir=datadir, started=False, reason=msg, reasoncode=reasoncode) )
        del self.dirmap[datadir]


    def remaining_items_succeeded(self):
        """
        Add results for all remaining items in our datadir->segment map.
        """
        for datadir in list(self.dirmap.keys()):
            self.results.append( StartResult(datadir=datadir, started=True, reason="Start Succeeded", reasoncode=gp.SEGSTART_SUCCESS ) )


    def log_results(self):
        """
        Log info messages with our results
        """
        status = '\nCOMMAND RESULTS\n' + "\n".join([str(result) for result in self.results])
        self.logger.info(status)


    def exit_code(self):
        """
        Return an appropriate exit code: 0 if no failures, 1 if some segments failed to start.
        """
        if len(self.dirmap) != self.original_length:
            return 1
        return 0



class GpSegStart:
    """
    Logic to start segment servers on this host.
    """

    def __init__(self, dblist, gpversion, mirroringMode, num_cids, era,
                 timeout, pickledTransitionData, specialMode, wrapper, wrapper_args,
                 coordinator_checksum_version, parallel, logfileDirectory=False):

        # validate/store arguments
        #
        self.dblist                = list(map(gparray.Segment.initFromString, dblist))

        expected_gpversion         = gpversion
        actual_gpversion           = gp.GpVersion.local('local GP software version check', os.path.abspath(os.pardir))
        if actual_gpversion != expected_gpversion:
            raise Exception("Local Software Version does not match what is expected.\n"
                            "The local software version is: '%s'\n"
                            "But we were expecting it to be: '%s'\n"
                            "Please review and correct" % (actual_gpversion, expected_gpversion))

        self.mirroringMode         = mirroringMode
        self.num_cids              = num_cids
        self.era                   = era
        self.timeout               = timeout
        self.pickledTransitionData = pickledTransitionData

        assert(specialMode in [None, 'upgrade', 'maintenance'])
        self.specialMode           = specialMode

        self.wrapper               = wrapper
        self.wrapper_args          = wrapper_args

        # initialize state
        #
        self.pool                  = base.WorkerPool(numWorkers=min(len(dblist), parallel))
        self.logger                = logger
        self.overall_status        = None

        self.logfileDirectory      = logfileDirectory
        self.coordinator_checksum_version = coordinator_checksum_version

    def getOverallStatusKeys(self):
        return list(self.overall_status.dirmap.keys())

    # return True if all running
    # return False if not all running
    def checkPostmasters(self, must_be_running):
        """
        Check that segment postmasters have been started.
        @param must_be_running True if postmasters must be running by now.
        """
        self.logger.info("Checking segment postmasters... (must_be_running %s)" % must_be_running)

        all_running = True
        for datadir in self.getOverallStatusKeys():
            pid     = gp.read_postmaster_pidfile(datadir)
            running = gp.check_pid(pid)
            msg     = "Postmaster %s %srunning (pid %d)" % (datadir, "is " if running else "NOT ", pid)
            self.logger.info(msg)

            if not running:
                all_running = False

            if must_be_running and not running: