package com.agilex.healthcare.schedulingmanager.mdws.translator ;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.cxf.common.util.StringUtils;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import com.agilex.healthcare.schedulingmanager.domain.SchedulingClinic;
import com.agilex.healthcare.schedulingmanager.domain.SchedulingClinics;
import com.agilex.healthcare.schedulingmanager.domain.SchedulingSpecialties;
import com.agilex.healthcare.schedulingmanager.domain.SchedulingSpecialty;
import com.agilex.healthcare.schedulingmanager.mdws.MdwsResponseException;
import com.agilex.healthcare.schedulingmanager.mdws.generatedwsdl.queryservice.TextArray;
import com.agilex.healthcare.schedulingmanager.mdws.generatedwsdl.schedulingservice.ArrayOfHospitalLocationTO;
import com.agilex.healthcare.schedulingmanager.mdws.generatedwsdl.schedulingservice.HospitalLocationTO;
import com.agilex.healthcare.schedulingmanager.mdws.generatedwsdl.schedulingservice.TaggedHospitalLocationArray;
import com.agilex.healthcare.schedulingmanager.mdws.generatedwsdl.schedulingservice.TaggedText;
import com.agilex.healthcare.utility.DateHelper;
import com.google.common.collect.Maps;

@Service
@Scope(value = "singleton")
public class SpecialtyWithClinicTranslator {

    private Log logger = LogFactory.getLog(SpecialtyWithClinicTranslator.class);

    private static Map<String, String> serviceMap = new HashMap<String, String>() {{
        put("M", "MEDICINE");
        put("S", "SURGERY");
        put("P", "PSYCHIATRY");
        put("R", "REHAB MEDICINE");
        put("N", "NEUROLOGY");
        put("0", "NONE");
    }};

	public SchedulingSpecialties translate( TaggedHospitalLocationArray arr , String facility ) {

		SchedulingSpecialties specialtiesWithClinic = new SchedulingSpecialties( ) ;
		
		if( arr == null ) { 
			
			throw new MdwsResponseException( 
					MdwsResponseException.UserMessages.INVALID_DATA_RETURNED , "TaggedHospitalLocationArray was null" ) ;
		}
		
		if( arr.getFault( ) != null ) { 
			
			throw new MdwsResponseException( arr.getFault( ) ) ;
		}
		
		if( arr.getLocations() == null ) {
			
			return specialtiesWithClinic ;
		}
		
		return translateArrayOfHospitalLocationTO(arr.getLocations(), specialtiesWithClinic, facility) ;
	}

    public SchedulingSpecialties translate1( final TextArray arr, final int start, final int end, final String facility, Map<String, String> specialtyMap) {
        logger.debug(String.format("Translating: start(%d), end(%d)", start, end));

        SchedulingSpecialties specialtiesWithClinic = new SchedulingSpecialties( ) ;

        if( arr == null ) {
            throw new MdwsResponseException(
                    MdwsResponseException.UserMessages.INVALID_DATA_RETURNED , "TaggedHospitalLocationArray was null" ) ;
        }

        if( arr.getFault( ) != null ) {
            throw new MdwsResponseException( arr.getFault( ) ) ;
        }

        if( arr.getText() == null ) {
            return specialtiesWithClinic;
        }

        return translateArrayOfStringTO(arr.getText().getString(), start, end, specialtiesWithClinic, facility, specialtyMap) ;
    }

    private static final int ID = 0;
    private static final int CLINIC_NAME = 1;
    private static final int SERVICE = 2;
    private static final int SPECIALTY = 3;
    private static final int INACTIVATE = 4;
    private static final int REACTIVATE = 5;

    private SchedulingSpecialties translateArrayOfStringTO(final List<String> stringList, final int start, final int end, final SchedulingSpecialties specialtiesWithClinic, final String facilityCode, final Map<String, String> specialtyMap) {
        if (null == stringList) {
            return specialtiesWithClinic;
        }

        if (StringUtils.isEmpty(stringList)) {
            return specialtiesWithClinic;
        }

        SchedulingSpecialtyBuilder builder = new SchedulingSpecialtyBuilder();
        builder.setSpecialtyMap(specialtyMap)
               .setSpecialtiesWithClinic(specialtiesWithClinic)
               .setFacilityCode(facilityCode);

        for (int idx = start; idx < end; idx++) {
            String str = stringList.get(idx);
            if (!StringUtils.isEmpty(str)) {
                String[] fields = str.split("\\^");
                for (int i = 0; i < fields.length; i++) {
                    String value = fields[i];
                    switch (i) {
                        case ID:
                            builder.setId(value);
                            break;
                        case CLINIC_NAME:
                            builder.setClinicName(value);
                            break;
                        case SERVICE:
                            builder.setService(value);
                            break;
                        case SPECIALTY:
                            builder.setSpecialty(value);
                            break;
                        case INACTIVATE:
                            builder.setInactivate(value);
                            break;
                        case REACTIVATE:
                            builder.setReactivate(value);
                            break;
                    }
                }
            }
            builder.build();
        }
        return specialtiesWithClinic;
    }

    private SchedulingSpecialties translateArrayOfHospitalLocationTO( ArrayOfHospitalLocationTO arrayOfHospitalLocationTo ,
																															SchedulingSpecialties specialtiesWithClinic , String facility ) {
		
		if( arrayOfHospitalLocationTo == null ) { 
			
			return( specialtiesWithClinic ) ; 
		}

		List< HospitalLocationTO > hospitalLocationTo = arrayOfHospitalLocationTo.getHospitalLocationTO( ) ;
		
		if( hospitalLocationTo == null || hospitalLocationTo.isEmpty( ) ) {
			
			return specialtiesWithClinic ;
		}
		
		Iterator< HospitalLocationTO > iteratorHospitalLocationTo = hospitalLocationTo.iterator( ) ;
		
		ArrayList< String > specialityServiceList = new ArrayList< String >( ) ;
		
		while( iteratorHospitalLocationTo.hasNext( ) ) {
			
			HospitalLocationTO hospitalLocationTO = iteratorHospitalLocationTo.next( ) ;
			if( hospitalLocationTO == null ) { 
				
				continue ; 
			}
			
			if( hospitalLocationTO.getFault( ) != null ) {
				
				throw new MdwsResponseException( hospitalLocationTO.getFault( ) ) ;
			}

			TaggedText specialtyTag = hospitalLocationTO.getSpecialty( ) ;
			if( specialtyTag != null ) {

				if( specialtyTag.getFault( ) != null ) {

					throw new MdwsResponseException( specialtyTag.getFault( ) ) ;
				}
				if( specialtyTag.getText( ) != null && !specialtyTag.getText( ).equals( "" ) ) {

					addServiceOrSpecialty( specialtyTag.getText( ) , specialtiesWithClinic , specialityServiceList , hospitalLocationTO , facility ) ;
				}
			}
		
			TaggedText serviceTag = hospitalLocationTO.getService( ) ;
			if( serviceTag != null ) {

				if( serviceTag.getFault( ) != null ) {

					throw new MdwsResponseException( serviceTag.getFault( ) ) ;
				}
				if( serviceTag.getText( ) != null && !serviceTag.getText( ).equals( "" ) ) {

					addServiceOrSpecialty( serviceTag.getText( ) , specialtiesWithClinic , specialityServiceList , hospitalLocationTO , facility ) ;
				}
			}
			
		}
		
		return specialtiesWithClinic ;
	}

	/**
	 * @param serviceOrSpecialty
	 *           : Can never be null
	 * @param specialtiesWithClinic
	 * @param specialityServiceList
	 * @param hospitalLocationTO
	 * @param facility
	 * @return
	 */
	private SchedulingSpecialties addServiceOrSpecialty( String serviceOrSpecialty , SchedulingSpecialties specialtiesWithClinic , 
																									ArrayList< String > specialityServiceList , HospitalLocationTO hospitalLocationTO , String facility ) {

		if( specialtiesWithClinic == null ) { 
			
			return( specialtiesWithClinic ) ; 
		}
		
		SchedulingSpecialty schedulingSpecialty = new SchedulingSpecialty( ) ;
		if( serviceOrSpecialty != null && specialityServiceList.contains( serviceOrSpecialty ) ) {
			
			schedulingSpecialty.setName( serviceOrSpecialty ) ;
			
			int index = specialtiesWithClinic.indexOf( schedulingSpecialty ) ;
			schedulingSpecialty = specialtiesWithClinic.get( index ) ;
			addClinics( hospitalLocationTO , schedulingSpecialty , facility ) ;
			
		} else if( serviceOrSpecialty != null ) {
			
			schedulingSpecialty.setName( serviceOrSpecialty ) ;
			
			specialtiesWithClinic.getSpecialty( ).add( addClinics( hospitalLocationTO , schedulingSpecialty , facility ) ) ;
			specialityServiceList.add( serviceOrSpecialty ) ;
		}
		
		return specialtiesWithClinic ;
	}

	
	private SchedulingSpecialty addClinics( HospitalLocationTO hospitalLocationTO , SchedulingSpecialty schedulingSpecialty , String facility ) {
		
		// If schedulingSpecialty comes in as null, return it instead of getting a NPE.
		if( schedulingSpecialty == null ) { 

			return( null ) ; 
		}

		SchedulingClinic schedulingClinic = new SchedulingClinic( ) ;
		
		// Making sure hospitalLocationTO is not null.
		if( hospitalLocationTO != null ) { 
			
			schedulingClinic.setId( hospitalLocationTO.getId( ) ) ;
			schedulingClinic.setName( hospitalLocationTO.getName( ) ) ;
		}
	
		schedulingClinic.setFacilityCode( facility ) ;
		
		if( schedulingSpecialty.getSpecialtyClinics( ) != null ) {
			
			schedulingSpecialty.getSpecialtyClinics( ).add( schedulingClinic ) ;
			
		} else {
			
			SchedulingClinics schedulingClinics = new SchedulingClinics( ) ;
			schedulingClinics.getSchedulingClinic( ).add( schedulingClinic ) ;
			schedulingSpecialty.setSpecialtyClinics( schedulingClinics ) ;
		}
		
		return schedulingSpecialty ;
	}

    private class SchedulingSpecialtyBuilder {
        private Map<String, SchedulingSpecialty> specialityServiceMap = Maps.newHashMap();

        private Map<String, String> specialtyMap;
        private SchedulingSpecialties specialtiesWithClinic;
        private String facilityCode;

        private String id;
        private String clinicName;
        private String service;
        private String specialty;
        private String inactivate;
        private String reactivate;

        public SchedulingSpecialtyBuilder setSpecialtyMap(final Map<String, String> specialtyMap) {
            this.specialtyMap = specialtyMap;
            return this;
        }

        public SchedulingSpecialtyBuilder setSpecialtiesWithClinic(final SchedulingSpecialties specialtiesWithClinic) {
            this.specialtiesWithClinic = specialtiesWithClinic;
            return this;
        }

        public SchedulingSpecialtyBuilder setFacilityCode(final String facilityCode) {
            this.facilityCode = facilityCode;
            return this;
        }

        public SchedulingSpecialtyBuilder setId(final String id) {
            this.id = id;
            return this;
        }

        public SchedulingSpecialtyBuilder setClinicName(final String clinicName) {
            this.clinicName = clinicName;
            return this;
        }

        public SchedulingSpecialtyBuilder setService(final String service) {
            this.service = service;
            return this;
        }

        public SchedulingSpecialtyBuilder setSpecialty(final String specialty) {
            this.specialty = specialty;
            return this;
        }

        public SchedulingSpecialtyBuilder setInactivate(final String inactivate) {
            this.inactivate = inactivate;
            return this;
        }

        public SchedulingSpecialtyBuilder setReactivate(String reactivate) {
            this.reactivate = reactivate;
            return this;
        }

        public SchedulingSpecialtyBuilder build() {
            Date now = new Date();
            Date invalidateDate = DateHelper.parseVistaDate(inactivate);
            Date revalidateDate = DateHelper.parseVistaDate(reactivate);

            if (null == invalidateDate || (null != invalidateDate &&
                                           null != revalidateDate &&
                                           invalidateDate.before(revalidateDate) &&
                                           revalidateDate.before(now))) {
                if (StringUtils.isEmpty(service) && StringUtils.isEmpty(specialty)) {
                    build("NONE");
                    return this;
                }

                if( !StringUtils.isEmpty(service)) {
                    build(mapService(service));
                }

                if (!StringUtils.isEmpty(specialty)) {
                    build(mapSpecialty(specialty));

                }
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug(String.format("Skipped: facilityCode(%s), id(%s), service(%s), specialty(%s), clinicName(%s), inactivate(%s), reactivate(%s)\n",
                            value(facilityCode), value(id), value(service), value(specialty), value(clinicName), value(inactivate), value(reactivate)));
                }
            }
            resetInstanceFields();
            return this;
        }

        private String value(String val) {
            if (null == val) {
                return "null";
            }
            return val;
        }

        private SchedulingSpecialtyBuilder build(final String serviceOrSpecialty) {
            SchedulingSpecialty schedulingSpecialty = null;
            if (specialityServiceMap.containsKey(serviceOrSpecialty)) {
                schedulingSpecialty = specialityServiceMap.get(serviceOrSpecialty);
            } else {
                schedulingSpecialty = new SchedulingSpecialty();
                schedulingSpecialty.setName(serviceOrSpecialty);
                SchedulingClinics schedulingClinics = new SchedulingClinics( ) ;
                schedulingSpecialty.setSpecialtyClinics(schedulingClinics) ;
                specialtiesWithClinic.getSpecialty().add(schedulingSpecialty);
                specialityServiceMap.put(serviceOrSpecialty, schedulingSpecialty);
            }
            SchedulingClinic schedulingClinic = new SchedulingClinic();
            if (!StringUtils.isEmpty(id)) {
                schedulingClinic.setId(id);
            }

            if (!StringUtils.isEmpty(facilityCode)) {
                schedulingClinic.setFacilityCode(facilityCode);
            }

            if (!StringUtils.isEmpty(clinicName)) {
                schedulingClinic.setName(clinicName);
            }

            if (!StringUtils.isEmpty(service)) {
                schedulingClinic.setService(serviceOrSpecialty);
            }

            if (!StringUtils.isEmpty(specialty)) {
                schedulingClinic.setSpecialty(specialty);
            }

            schedulingSpecialty.getSpecialtyClinics().add(schedulingClinic);

            return this;
        }

        private String mapService(final String value) {
            if (StringUtils.isEmpty(value) || !serviceMap.containsKey(value)) {
                // Should never get here, but just in case
                logger.warn(String.format("The service '%s' is null or does not have a mapping. Using 'NONE'.", value));
                return "NONE";
            }
            return serviceMap.get(value);
        }

        private String mapSpecialty(final String value) {
            if (StringUtils.isEmpty(value) || !specialtyMap.containsKey(value)) {
                // Should never get here
                logger.warn(String.format("The specialty '%s' is null or does not have a mapping. Using 'NONE'.", value));
                return "NONE"; //todo fix!
            }
            return specialtyMap.get(value);
        }

        public void resetInstanceFields() {
            this.id = null;
            this.clinicName = null;
            this.service = null;
            this.specialty = null;
            this.inactivate = null;
            this.reactivate = null;
        }
    }
}
