package org.coderepos.nori090.peercast;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.io.IOUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class EventCollector
    extends Thread
    implements ShutdownListener {

    private static String tmpdir;

    private static final String fname = "org.coderepos.nori090.peercast.EventCollector";

    private static final String URL = "http://listeners.peca.jp/?cmd=rssevent";

    static volatile Map<EventDate, EventDetail> currentMap = null;

    static {
        currentMap = file_read();
    }

    static Object lock = new Object();

    @Override
    public void run() {
        try {
            while ( true ) {
                buildNewMap();
                Thread.sleep( ( ( 1000 * 60 ) * 60 ) * 3 );
                file_out();
            }
        }
        catch ( InterruptedException e ) {
            e.printStackTrace();
        }
        finally {
            System.err.println( "EventCollectorが終了しました。" );
        }
    }

    private void buildNewMap() {
        synchronized ( lock ) {
            Map<EventDate, EventDetail> newMap = new HashMap<EventDate, EventDetail>( currentMap );
            String s = EventCollectorUtil.getEventRssStream( URL );
            if ( s != null ) {
                StringReader reader = null;
                try {
                    reader = new StringReader( s );
                    DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
                    Element root = builder.parse( new InputSource( reader ) ).getDocumentElement();
                    XPath x = XPathFactory.newInstance().newXPath();
                    NodeList list = (NodeList) x.evaluate( "//item", root, XPathConstants.NODESET );
                    for ( int i = 0; i < list.getLength(); i++ ) {
                        Node n = list.item( i );
                        String[] ymd = ( x.evaluate( "title", n, XPathConstants.STRING ) ).toString().split( "-" );
                        EventDate date =
                            new EventDate( Integer.parseInt( ymd[0] ), Integer.parseInt( ymd[1] ),
                                           Integer.parseInt( ymd[2] ) );
                        date.url = x.evaluate( "link", n, XPathConstants.STRING ).toString();
                        EventDetail detail = null;
                        if ( newMap.containsKey( date ) ) {
                            detail = newMap.get( date );
                        }
                        else {
                            detail = new EventDetail();
                        }
                        List<EventData> ed = parse( x.evaluate( "description", n, XPathConstants.STRING ).toString() );
                        detail.addAll( ed );
                        newMap.put( date, detail );
                    }
                }
                catch ( Exception e ) {
                    e.printStackTrace();
                }
                finally {
                    IOUtils.closeQuietly( reader );
                }
            }
            currentMap = newMap;
        }
    }

    private static List<EventData> parse( String s ) {
        ArrayList<EventData> list = new ArrayList<EventData>();
        String[] data = s.split( "[\r\n]" );
        EventData event = null;
        for ( int i = 0; i < data.length; i++ ) {
            String d = data[i];
            if ( d.length() == 0 ) {
                if ( event != null ) {
                    list.add( event );
                    event = null;
                }
            }
            else {
                if ( event == null )
                    event = new EventData();
                if ( d.startsWith( "開催時間" ) || d.startsWith( "開催時刻" ) ) {
                    int index = d.indexOf( "：" ) != -1 ? d.indexOf( "：" ) : d.indexOf( ":" );
                    event.eventTime = d.substring( index + 1 );
                }
                else if ( d.startsWith( "イベント名" ) ) {
                    int index = d.indexOf( "：" ) != -1 ? d.indexOf( "：" ) : d.indexOf( ":" );
                    event.eventName = d.substring( index + 1 );
                }
                else if ( d.startsWith( "主催者" ) ) {
                    int index = d.indexOf( "：" ) != -1 ? d.indexOf( "：" ) : d.indexOf( ":" );
                    event.author = d.substring( index + 1 );
                }
                else {
                    int index = d.indexOf( "：" ) != -1 ? d.indexOf( "：" ) : d.indexOf( ":" );
                    if ( index != -1 && d.substring( index + 1 ).startsWith( "http" ) && event.url == null ) {
                        event.url = d.substring( index + 1 );
                    }
                    event.description = event.description == null ? d : event.description + "\n" + d;
                }
            }
        }
        if ( event != null && event.author != null ) {
            list.add( event );
        }
        return list;
    }

    public static Map<EventDate, EventDetail> getMap() {
        return file_read();
    }

    @SuppressWarnings( "unchecked" )
    private static Map<EventDate, EventDetail> file_read() {
        FileInputStream fis = null;
        try {
            File f = new File( getTempDir() + fname );
            if ( f.exists() ) {
                XStream xs = new XStream( new DomDriver( "UTF-8" ) );
                Map<EventDate, EventDetail> map = new HashMap<EventDate, EventDetail>();
                fis = new FileInputStream( f );
                map = (Map<EventDate, EventDetail>) xs.fromXML( fis, map );
                return map;
            }
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
        finally {
            IOUtils.closeQuietly( fis );
        }
        return new HashMap<EventDate, EventDetail>();
    }

    private void file_out() {
        OutputStreamWriter osw = null;
        FileOutputStream fos = null;
        try {
            XStream xs = new XStream();
            Map<EventDate, EventDetail> map = currentMap;
            fos = new FileOutputStream( getTempDir() + fname );
            osw = new OutputStreamWriter( fos, "UTF-8" );
            xs.toXML( map, osw );
            fos.flush();
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
        finally {
            IOUtils.closeQuietly( fos );
            IOUtils.closeQuietly( osw );
        }
    }

    private static String getTempDir() {
        if ( tmpdir == null ) {
            tmpdir = System.getProperty( "java.io.tmpdir" );
            if ( File.separator.equals( "\\" ) ) {
                tmpdir = tmpdir.replaceAll( "\\\\", "/" );
            }
        }
        return tmpdir;
    }

    @Override
    public String getProcessName() {
        return getClass().getSimpleName();
    }

    @Override
    public boolean shutdown( ShutdownThread resource ) {
        file_out();
        return true;
    }

    public static class EventDetail
        extends ArrayList<EventData>
        implements Serializable {

        @Override
        public boolean add( EventData e ) {
            if ( super.contains( e ) ) {
                return false;
            }
            for ( int i = 0; i < this.size(); i++ ) {
                EventData d = this.get( i );
                if ( e.author.equals( d.author ) ) {
                    this.remove( i );
                }
            }
            return super.add( e );
        }

        @Override
        public boolean addAll( Collection<? extends EventData> c ) {
            boolean ret = false;
            for ( Object o : c ) {
                ret |= this.add( (EventData) o );
            }
            return ret;
        }

        @Override
        public void add( int index, EventData element ) {
            throw new RuntimeException( "no support for add method." );
        }

        @Override
        public boolean addAll( int index, Collection<? extends EventData> c ) {
            throw new RuntimeException( "no support for addAll method." );
        }

        @Override
        public EventData set( int index, EventData element ) {
            throw new RuntimeException( "no support for set method." );
        }

    }

    public static class EventData
        implements Serializable {
        String eventName;

        String eventTime;

        String author;

        String url;

        String description;

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ( ( author == null ) ? 0 : author.hashCode() );
            result = prime * result + ( ( description == null ) ? 0 : description.hashCode() );
            result = prime * result + ( ( eventName == null ) ? 0 : eventName.hashCode() );
            result = prime * result + ( ( eventTime == null ) ? 0 : eventTime.hashCode() );
            result = prime * result + ( ( url == null ) ? 0 : url.hashCode() );
            return result;
        }

        @Override
        public boolean equals( Object obj ) {
            if ( this == obj )
                return true;
            if ( obj == null )
                return false;
            if ( getClass() != obj.getClass() )
                return false;
            final EventData other = (EventData) obj;
            if ( author == null ) {
                if ( other.author != null )
                    return false;
            }
            else if ( !author.equals( other.author ) )
                return false;
            if ( description == null ) {
                if ( other.description != null )
                    return false;
            }
            else if ( !description.equals( other.description ) )
                return false;
            if ( eventName == null ) {
                if ( other.eventName != null )
                    return false;
            }
            else if ( !eventName.equals( other.eventName ) )
                return false;
            if ( eventTime == null ) {
                if ( other.eventTime != null )
                    return false;
            }
            else if ( !eventTime.equals( other.eventTime ) )
                return false;
            if ( url == null ) {
                if ( other.url != null )
                    return false;
            }
            else if ( !url.equals( other.url ) )
                return false;
            return true;
        }
    }

    public static class EventDate
        implements Serializable {

        int year;

        int month;

        int date;

        String url;

        EventDate( int year, int month, int date ) {
            super();
            this.year = year;
            this.month = month;
            this.date = date;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + date;
            result = prime * result + month;
            result = prime * result + year;
            return result;
        }

        @Override
        public boolean equals( Object obj ) {
            if ( this == obj )
                return true;
            if ( obj == null )
                return false;
            if ( getClass() != obj.getClass() )
                return false;
            final EventDate other = (EventDate) obj;
            if ( date != other.date )
                return false;
            if ( month != other.month )
                return false;
            if ( year != other.year )
                return false;
            return true;
        }
    }

    public static void main( String[] args ) {
        new Thread( new EventCollector() ).start();
        while ( true ) {
            System.out.println( EventCollector.getMap() );
            try {
                Thread.sleep( 1000 * 60 );
            }
            catch ( InterruptedException e ) {
                e.printStackTrace();
            }
        }
    }
}
